From 92b4dbb250bc71b3bfd2ad44d7fbf73d7d885dc0 Mon Sep 17 00:00:00 2001 From: serinko Date: Thu, 13 Jul 2023 16:51:04 +0200 Subject: [PATCH 001/361] initilizing operators guides mdbook --- documentation/operators/book.toml | 123 +++ documentation/operators/src/SUMMARY.md | 10 + documentation/operators/src/faq.md | 7 + .../src/images/wallet-screenshots/bonding.png | Bin 0 -> 56520 bytes .../wallet-screenshots/node_settings.png | Bin 0 -> 55864 bytes .../images/wallet-screenshots/wallet-sign.png | Bin 0 -> 111066 bytes documentation/operators/src/introduction.md | 34 + .../operators/src/nodes/mix-node-setup.md | 700 ++++++++++++++++++ .../operators/src/nodes/setup-guides.md | 18 + 9 files changed, 892 insertions(+) create mode 100644 documentation/operators/book.toml create mode 100644 documentation/operators/src/SUMMARY.md create mode 100644 documentation/operators/src/faq.md create mode 100644 documentation/operators/src/images/wallet-screenshots/bonding.png create mode 100644 documentation/operators/src/images/wallet-screenshots/node_settings.png create mode 100644 documentation/operators/src/images/wallet-screenshots/wallet-sign.png create mode 100644 documentation/operators/src/introduction.md create mode 100644 documentation/operators/src/nodes/mix-node-setup.md create mode 100644 documentation/operators/src/nodes/setup-guides.md diff --git a/documentation/operators/book.toml b/documentation/operators/book.toml new file mode 100644 index 0000000000..c39bb1c6a1 --- /dev/null +++ b/documentation/operators/book.toml @@ -0,0 +1,123 @@ +[book] +title = "Nym Operators Guides" +authors = ["Max Hampshire", "serinko"] +description = "Guides to setup Nym infrastructure" +language = "en" +multilingual = false # for the moment - ideally work on chinese, brazillian, spanish next +src = "src" + +################# +# PREPROCESSORS # +################# + +# Note if changing these fields: +# - make sure you aren't running `mdbook serve` +# - change value of `turn-off = true` to `false`. This is set to `true` usually to avoid a known constant-reload bug with `mdbook serve` and this preprocessor +# - delete `./theme/` dir +# - change the preprocessor values that you want to edit +# - run `mdbook build`: this will rebuild the `./theme` directory +# - change value of `turn-off` back to `true` +[preprocessor.theme] +pagetoc = true +# some variables related (defined in theme/css/variables.css) +pagetoc-width = "13%" +content-max-width = "70%" # 82 +pagetoc-fontsize = "14.5px" +sidebar-width = "300px" +menu-bar-height = "40px" # memu-bar = the bar on the top +page-padding = "20px" +mobile-content-max-width = "98%" + +# layout +content-padding = "0 10px" +content-main-margin-left = "10%" +content-main-margin-right = "10%" +nav-chapters-max-width = "auto" +nav-chapters-min-width = "auto" +chapter-line-height = "2em" +section-line-height = "1.5em" + +# if true, never read and touch the files in theme dir: this is used to stop the looping reload issue referred to in the readme +turn-off = false + +[preprocessor.admonish] +command = "mdbook-admonish" +assets_version = "2.0.0" # do not edit: managed by `mdbook-admonish install` + +# variables preprocessor: import variables into files +# https://gitlab.com/tglman/mdbook-variables/ +[preprocessor.variables.variables] +minimum_rust_version = "1.66" +# vars for links: TODO think on how to streamline updating +platform_release_version = "v1.1.22" +wallet_release_version = "v1.2.5" + +[preprocessor.last-changed] +command = "mdbook-last-changed" +renderer = ["html"] + +# used for grabbing output of binary commands for automation +# https://github.com/FauconFan/mdbook-cmdrun +[preprocessor.cmdrun] + +# more pre-processor plugins to look into from https://github.com/rust-lang/mdBook/wiki/Third-party-plugins & https://lib.rs/keywords/mdbook-preprocessor +# mdbook-i18n + +######### +# BUILD # +######### + +[build] +build-dir = "book" # the directory where the output is placed +create-missing = true # whether or not to create missing pages +use-default-preprocessors = true # use the default preprocessors +extra-watch-dirs = [] # directories to watch for triggering builds + +########## +# OUTPUT # +########## + +[output.html] +theme = "theme" # this is the default if not explicitly set +default-theme = "coal" +preferred-dark-theme = "coal" +curly-quotes = true +# mathjax-support = false # useful if we want to pull equations in +copy-fonts = true +no-section-label = false +additional-css = ["theme/pagetoc.css", "././mdbook-admonish.css", "last-changed.css"] +additional-js = ["theme/pagetoc.js"] +git-repository-url = "https://github.com/nymtech/nym" +git-repository-icon = "fa-github" +# edit-url-template = "https://github.com/rust-lang/mdBook/edit/master/guide/{path}" +# site-url = "/docs/" +# cname = "nymtech.net" +# input-404 = "not-found.md" # todo make custom 404 page + +[output.html.fold] +enable = true # whether or not to enable section folding +level = 0 # the depth to start folding + +# controlling rust sample code blocks +[output.html.playground] +editable = false # allows editing the source code +copyable = true # include the copy button for copying code snippets +copy-js = true # includes the JavaScript for the code editor +line-numbers = false # displays line numbers for editable code +runnable = true # displays a run button for rust code + +# options for the built in text search +[output.html.search] +enable = true # enables the search feature +limit-results = 30 # maximum number of search results +teaser-word-count = 30 # number of words used for a search result teaser +use-boolean-and = true # multiple search terms must all match +boost-title = 2 # ranking boost factor for matches in headers +boost-hierarchy = 1 # ranking boost factor for matches in page names +boost-paragraph = 1 # ranking boost factor for matches in text +expand = true # partial words will match longer terms +heading-split-level = 3 # link results to heading levels +copy-js = true # include Javascript code for search + +# [output.linkcheck] +# warning-policy = "warn" diff --git a/documentation/operators/src/SUMMARY.md b/documentation/operators/src/SUMMARY.md new file mode 100644 index 0000000000..7bb81c8800 --- /dev/null +++ b/documentation/operators/src/SUMMARY.md @@ -0,0 +1,10 @@ +- [Introduction](introduction.md) + +# Operators Guides + +- [Node Setup Guides](./nodes/setup-guides.md) + - [Mix Node](./nodes/mix-node-setup.md) + - [Gateway](./nodes/gateway-setup.md) + - [Network Requester](./nodes/network-requester-setup.md) +- [Troubleshooting](./nodes/troubleshooting.md) +- [FAQ](./faq.md) diff --git a/documentation/operators/src/faq.md b/documentation/operators/src/faq.md new file mode 100644 index 0000000000..d6b32f3e77 --- /dev/null +++ b/documentation/operators/src/faq.md @@ -0,0 +1,7 @@ + diff --git a/documentation/operators/src/images/wallet-screenshots/bonding.png b/documentation/operators/src/images/wallet-screenshots/bonding.png new file mode 100644 index 0000000000000000000000000000000000000000..b5cef3bb89bdde0e2d865001d14f9b4d1b1b28cc GIT binary patch literal 56520 zcmeFZWmr^S_%=EQ1_($htwV!!w*u1Ljgpc>cZfqln_;dKpta4AP-ozA^p>#DyN&OW(ILwLmt*sssC**EPp+Z%AJnrmVI z(Whs5+x$BT+!Jyzw2bfc=>79p0JNSb!egA|I$ZwJVw_Fmc zKZOpV*{=0qwGyTZk*IlS_8JQ-TU7am@8?0L1XE=0E2nGl!gq?>+uNwVPzHwL-$X|( zo%u>*<-cbk2iMp*%f}qe2^^|Ka)~)R?orUq4pP+f{z)qEpKj#qNCndsXs*zSm8Q@y z^+ga5A$%BN1sK0j1b1R|Ye@P`k#fdV-&%# z$$Hg3chd8y$f)B|f+qKwSq-kGL%Y;D`lz0L*Eu2EZ3i#*UmkVk)L~>x$1VxZw-wW^ z2{_{YGO{Yt7$iLFjfOQxySQD4O*;)(Bj5jsuMp>IaamVdpR$@v{%tFnb=vEB`P(5s zQXFH?m~mMbb11cPB5F z$P7$UW>tI?%cwh=vYwTCSiFQl{4%6vkj!R_N7(s|G5eESqhEuXgLD*f)=y9@q>UJm3Qf@wAF6-o_NWNd6~ z=zkx(Uoe<^hC*IZdPzxVw1v6(%Mnke{kT#+HcE0oOcu5QZaa_8%AO}zSG>HugoK2% z&U`&C+myN!PBtMEUc`AZ01Isc#j|c?KPGb{L(# z$sD+^w0e0#o#on$eXfoxe`pG7vk}pZ?R@8*Pp>6sUqs%`qoBxtp&St z$aZI8`MR&ZoNIQU?Cz!^LQ-`Ey}G`+lwPR3zy$B_TG`gU%jaCN$HBoeU1k;ZFeO=O z2Wjb`fX)yC4#OE-PtT_Cbgs7_U_bHbV>ABJ$1yD(y0DoJ4J@_a_0LVSot`*6#U$eK zTqoYx<;xw27x;9*EFduNYHvb^G;-X6Ko-fO5))^@gzCP9&QoVclXUqUaeNgI%FFjMmm#QSeO)+7U`W) zk@?0Kd8pj)G(ZS1R4+`FT^ksj?O29QkM0mCyXr7voo>xnD0QI9EafoF;$Y*jk!>P3 zhAi3hU>$2-e6m&y#pbGo%(H?BdWcbapA)qJL#W`>NHcO)3#n1jpC`uoztma$yUzsb zDJiM->pkFYZDY;iT*jA)s(s=wi0pR#W=GcwZyr4q78a>sYV=s!UN)w8;a^yK#MtXS zI!(08oC)k&HYuePVY5G)zj9IbD`0bQ-W}{8X&IRvE*ps!ACYzgTfbj9gYomf)DiJh zQ*$2e`&g*H42iRX&pbBQc7MG)IXpc+J%*Xgs_5#b(DrPPu009sN@BC-}scFA4l*m>pl4~s~DS5mxNVgbUq+N0J1G!{UEsJ(x zZx6c_qt0a*yi8%n9ZZziVAOF)X7(dM8Lx33szbv!Rb>;rL!Rlnn+(NEiN-L{oo+6y z&4eVahIZF4p`UldV z1Ndl-j24nB`z4H|-dY=(q?CnjQ}sC8|LIsVy!9I5_k%If!$kv=4YVI8jO6-mO!$wF)#I$Lm&pM>Q}6M;^@B!188k zrK^Zhmr+QVDvE^L<>F#Qq`>~Ips#RO$S_2Mt_{#js);o-x*`1o~Z|E|wJ1WHPZW+~8sXge`6(L11s z^0n{tKY9vTd1@$QoQS4={oFM!=|ThI{n{&ezZwWcp*H^S5)THJItUXhJ^GCvaM+(k z_CTX+HEMW9%ResPvqoR?F1eE}`1iJ@y!_|84bJl^r+-RMPEL#qC+c@*#6&@y)a7$F z>MhlCMsqn}F8nn*N`XlxXg&U05R1d|Q`P?BXOr*K$lah&lx`*S7iJ0nRG?ns`a;-P zb(q(Rubq;5orhdLak`ytii!5czEg>H*c!LpI*RFyuq)6&qr`rW_5ATe<3K+@H%t7s zAJJ%1%<(E2&wd63M@wbn;}eM6YN`~_L`cLJ2^LCI$CS&G^*oGvODyCT(6#Ya6&MGv zlCttsjLS0orQCyqu(xkhc%7!2?a#ZyUqb103sk5Z&i;}Kii(P1N0_hOOyntwFhp$L zUZ>aWFT9%aFR$O11JeCPa$994?3SvlEOQ`AeOVJcEv(Zy!Y%|t?#v8lll}QZ84iMKR|8(i=P?x?%aADGdK4T` z^o}8nlrN*`-%?vwzwiFS(;lPpM0g>+Vd85dM6+j{Yf@}%&UsVP;PCzDVE#ItSYC}P z?)cJ|$4MU=G1qKgBa2iEA>vSx)BRmuH2M3(Ez)|=%*_oeC7@L#m`L9gR8t9`HUAS>t$ zzS~;DM7arIR~yTbN#ypjY|cSdsU5NtblZ6x#SovEI5jh4r0=?xh#o@3sZc`O;Eec7 z?2qkweI*ZtLebGf2;l`_gEG@k(&3`Ee0}OY=W${B(M2)PL@4IRJk|FCmHL6O$Xa!f zBzLrcFe`*gUg6~-Rl}Ha$-h7vcYDsi$405Qu<-rK8Cb;K8OItv=Y7Zh3Cg!DEZp2v znM&CfTyr29?b34D3_zwoxHT{^{Qm9x_1PX}RFBi{Wi{2*O7751m6^bo_y5utKlx*| z*-d{WA|%WyE*5YsHyqy>ieuDQWTaCFk1f?vM)aEhThKKf(!V)4G~9}$Y;fFt7aFGD zY{#+Yu;q1H5l#((;MxKYWxdBG9!kpPy;tPr>4v3v;E#zzE#KgZtgf$jYnD^#e#1ez z1`N;Zkw0_Ag~`wbtu8I43==kq)h zMl76(Qs8$r>Q+O_~^W5?+&o`(i4Lt~?o|i`*V}071Q2Z`LqhqdW-@4zECpc8H zDMHR_adIRa92^TzQONi`egtTs#l6$UA|pezz+k$%^RJlnPj|-j>qrkrjp!lv9cDJrrl{)Cj|uTxx2;C#kF95L3{o@h28N9R(^hdijaLdNQK1wp2$>(uiR(3 z-Xb6*G&pR#+$~cYKNU{aDB7Wumz8aDMLL0{VA8Lqo1^7WzJ z&`NV#yzSne&;;FljcKlM#wM4|vtuW)263f2rw0f9Eu8$WSC%uu)3R1Yu8qmBFk*UE zodx*qS~Aa>TyL)&>t#6IJJ}N>Y}LIGt$pc><-YCh?WOuH*os;+VZ=0?jC(g7eY-Q2 zD#Z!O{R^=0*w{`{iCzQ8SH|E%7+#~NM*=owg3cb<%Wg|*3JRK4JG$`77k9i&IuaS_ zKc@w(Zjx%Kx7O}FU_<}J6%L;IkE|5EL&8f`kF|`v<^{k@WKz-c@QCDoC^HZe=%LZ2 z>TA(M$;Qc*OXGIrZ4sgr-~ay2{cI1>KOvS^j&G2$sFH7{S<3*qzzKYQ$wxm~$)ZYh zI7a+LRdVUXlFJPbRQN}}$fZXGo@fd*;S7He3@sHX(B*^fN|pq6s$0}7z2E?vQ?!nz zUlQ^^52#R5QC>hG;X?7IPqRI}7Z`C-jnEF9>AzY&00S+0({DT{1_$>jz>ohUX|;yDk7!%7LwU$Sn%1>+z@6M`4RT4y zdpTTGB^smh6Nh{iy12e0X4Z0)uW5Pt7SA5Hwp!oM^B<-~n}D_@TVH;k_cXF`IoUa@ zsz$-04=+B5i2P8wU$#2~;?VbR#)rpy3thk(H7hEQEgp`{0xSpOEkGKUn^O`&odHm^ zmn`;pKQ=*<0(SY2uS|<}uHK|i0hWdp@jhK`N*0`lbn}6sXfBSK!8ic{E{*&W&G&cs zi9yPQ>8C-i4S54y1~}0D2x$IEPlZk+z_3_LW%pR(LD%&o{P?&RSmtEn4f6n{^C*#e*0Bgl>8$>~XF7zw;W zTph~mdNns;#kbO4D|_tyLvs*v3Ee)9<1U-3wishjQ}FgMer7*7mHddB$tl zLcQ`Qh8yFb-{P6mhnJDs*^C%*HJ~9J0ox|?BBU42IP(!;9J8V9+l|wj@8tFY-aKN!(UJLS^WkF zlg;!84?kG{J7^Vp1m>gp<+BUxy_yRTui1%WmA;SAf!G17j^&H5Pcc>d_@=A852p~5 z$DGI2&Bjd|VK;jGXEhcGHO*YzBsC_|Ya#E|$@FH`b$KHwcPN>mPUBRhuRW|m*LBf; ze~xzY&eQ#{mf)ZK{+G$U^ghOYcP_GvIX?U4?r@)5fOeUqkdUOfR+!E;xpE9ouACgU zk?>VY%Sra!WX#t)@jAJrwM^>j56$|Y?^}-*bd>5);IlR;WihO~KZn2ld8@G1^Y5o&ag;IEOdyWxg>ZBe(^P6{#&l~3EI ziRw`=(S#w~--j(cG8xj2j-^Tvgdfgyu<7zngYvl5nhN)Rzg`L}i>&m*iA2YHN>a>S zlO`D8aCoUsL&WVa0mz8O!%`?Rr`d8@n{a@M+MCK!Lnl{qJ#KF*cz;W-h&|PHns(kV z$S9%0MWt6QVs@T5k;LiBpU`alw3mn{&47fD3@gM*WC6TjAfD3qBOdR>}%|p)bsr7gc(S*7L($G`f0@waMER{%q?} zZ!SaOIfIttqn^zAe0Qr+PWI@kJ}cHo5CvQfl>(Pf`*IrtM8P2;&J8=Tv5dY9@-8F0 zIvqwK??XHPK>??R9WG{OjXKMbI|06TbgKO+z-!-`k7(eq>e zKs9y{nY&*-%wT?Vu(ZLX4^ZOlEA?{wE(}qJrO_B-KKn`73^qVh(@TZiJ5`{GJdTkW z86UoUnYxnIF6Sw5tD;lNA1?{=6iJFjx6-<}xWZ9P(>v?k^PONwI^P*l%}{u!U6-TR zmJ^a#P0VX&DQu8w+{6nWjm7cmbWe3UGg3H1quj&swsT;c!;;r|DG~r~`CbZz_ycYZ zNN9w_6(-?A#eRCK=hKiRZZMq8Yp$90_KUdES6EoI{Tz61!q)HEX#;)`{NwE@3dtsa z$6bc~wjohb7Vf8#_csEN?V^Q^-N)%Y{bBa)0RLvB!+b2hKZjlKbb!0fc7OBrT zTNbZ8sRDIu-UT=d1QR8v@7d_jFA46OC7ZLSsz(=nJ|xpbjU%^ENZ!Ev*dH8}53j>C z9AAHYfY}n`M9*Mx%jE41!2kBC+KZ)IcaW<={PnqNp-Kr%m8t6&sKHw% z=P`C?jFdC;X|c_IUfg)QQZa)7@9}rVZq4qp3b{v&AXVUV3>B9V{Sv!xj@d(8yk%ExSm18db6U@)!2hE_#ZnRz7pfvkthf#sa?8UpG#D14bL-)&v69 z)Bbe_N-d#u9+diKJHJ1F2)Q9KS1YB$Hp5oj)#8|`w5G)^pSyU4>T9J{Q>0t?1S?Q9 zk;6h|NOpDjw|ucSCv57g@rzxUf>H8tEE{Q@1)`~MvQ(eX8OdQbQv~*_78k6^ZMTyc z({eQLt-WkHZ9>B*TM^`X|Kwf^>U|J3z!%-V@MKmpZ8#&w*Z`G59Y6oU(yuo0Fr1O@ z&msYv!x4s<56RYTUPO4mPq*V^rTaOppsa4nS)fw=2I~FqX@UR6rO+EFIeFW1XGdV3 z$7Q+b>hAH_(FaUYK_yS^VktbT8Q>#hN)13R5S`I=`N|_nV&E;*HzUzVje+X(f3vOZ zi<3mBa~XfkbE<8O;UB>LGg%CJSak5fgSvBCA*}hj?NjD+xbqJ*MJ>VtH$jTct}%u;sG6DQEjeb z_p9L=ooUwoGw?utF$@(JvnJl94bF3={V5!>y1TQw3?d@mIp|~E<{d#i*T2-N|3Zod zYOglqNlwId&BMn1Q~_6q%C>_!ovDY)&u*y(k~kRjODsd-3oV%So|C^IGmpZ(!A`&@ zJj*xmJ&K`Ib2&cK-3TSYMeXR2R<|cxnhI3@{^Z2C8dUQshPTntxbVTJA9H+z<27HF z*$NeZiwh6`W4Vdk`Y0-DZf=gPC}UYb!Y_@6<6i7gp(*AEP0451fY@IOh_sN_^k`fZNOJ346xYgFv zjXtVPAUzfr6~!J2$oNju6^0eGQaW$L#mcVJ?BKIq2AB-%>Dn`8m-e$1_kjV~>+@aJ z@*kI*hfyj>iR{ro{$a)Mkc^t#&i8KE@l(ammskV@2a6fN+5pGU>~c!S$;o(MM@z2T z)9TibHw(-nn$SQ9Qex9i0_hlaZkDBk9aZK4(ZocNrswXJESZp>Tq5&CYvwZ8_;RU& zg&7%T?$>JhDsG#bw$K8_;(`RT68u#fW|R~LcG$%Dw3#O(eOMAV%JWAzyCr(g_OAuA z4E>$B=3ue(>Jvh5nA>LZr<3^Hd9ixd>=@(toX<`oeo-6g3xSHhxpnf#V^0AA5)v!8 zFi8pw`IGfr7a(huqEIR_h0%%8E84-y$G}$MNKR622X^P&NH#eaR%$(H4SVL;U;eZq zVhl+!^@uaNzCbyg5|_M0x|ea}r55d2xwfrQ6=4b1hOO=|2U-q(g>g5jK2NB6zOVI~ znldFo;yUi%i=&5`*&=j3g-9G*i9es+sJVN#vfkW9oX?pG8EYnUXQ4t4xFRGdt}%!O z?e|&_-hO|AzS#g9a)L_9%L7_|$7F+~Ob8*-i=LrSYyDk0$V9d)%(m8Jt$th9v(u>S zP?Q}2Y=A1jR;*zpiDS~_c3VC0m5#+3S@iM5L+w!TX90K$WhQOt3ZTPoR;B`1R_!hi z6jy{r+5#j*M1Fyg){7sb*W~r<3JVRNX|~C*(DS^6WOpWw&)asxIxcTWzdX!lsI5Zm z)SPo7M4-ttI!vxtC-Om9-)ZeW2W&|R%Ing8dX-%vTbu+|8CZf|u9K`hEbY6$ZeT#) z8aHeqJU;qj&okWj-%8>2xJ`8_$=7+4D@SGOtx^UPy< zQReu+@;jf-phL?o5V5ZPtso@Yd0R4FTgwu1$Vf*86C&dyBS*6o+ZF`eZ$7)H3VO*!4@yr=PdF?YoelxF~x~)pUiSg>n+rM;phPhiNr`mR^nkk_iYhR6IQ|TR9!L zg-R!V*}8J&9s64@Ai=-}0dLO9!2$SOajnGF`rfp4H6X<{wXhf)DIrnL{b)a1 zU94Tu$zyx;0&99k1CxaR>$T@!N)i&1>pk5Hf`ml!yEW!P^+2O9A3sWBVc9kb6%IV_ z4yU7$GLw9&zS=H*87EQ^Mg`MJCXcnzzZ>7`zm4yHE*q(t^b%`dC!i=Wu+mi1mB7lC za^9gKvfn^F`8S2DWJFO(Myo_frG{GRva46@BzyEE>*{>i)yT!z-*OYGQ9a|eHAmd+ z%GD}#V}^g5LimvPOQ_GEhlKqjB>TrfLE+z0!!zT&X!B(%cTbm3y-@386_Jj-dR#!J zp_j~+l&rM0oE+!yWbXd_%*cq)SwUzdd~%F3^JuNtZNbq{uV235#qCCLR@SPn`&pGa z!fbX>Knprj-ht-DAxMmXwc$%@?g8-@!!ifS+5qbFe`C5D0X1h z{@2gBgUJ=;MGc0?oa5Xu0KKo&2nh(`2A-x~o$m^mu8;TEsa#hVV-oX6ety8DhMX+= zf~e-elFS~R;#CEVgfbA>ga9*X-Sv->8@4Fm%HTrC~B!u3O~Omq9;3-LBI*H zIAV|pEYwBm)|DGi6lw5!56Uq5(_@>}BkF(gO2K#6cgah-Krj;U{JAovNZv*cJvnhe zM}JYPv)9q5Zp4Eg(0kM#mGzB*iIUACYoL^T>Yv}nZ9>)&SwkJM=C9?^zj%RNSeCAX zl5#_WiTyJ_ka!=Df+v4^3(+-+Nb=&hqXkptZj>`-Q|wBG%l!3xC9mEV5U_FnQIA2G zxAmKg&Qk0vf+wInIyZZF7_f@EcTCn$JJiw2AwSj-7hB1`aS)I>=2Fwv@I5dOE#tr9{ED z(QewyMF|!3>SwKyl#a5TudXn**A9}xtE#H>A{P$TXnA04SGN*PaP0)M7g!Y*?dj<$ zoU~FYp*o`Eb^E)#mXzZBf`t9k!5^=|q)9Fd_*(%1|NYvB$`s|k!1MxFR^-!oO0_gf zEEDhttN;Q~XTR@d{f*!K&fv1Zk)N~_6tHE##I0w`%RF=UF30q~>f2w=oIYO>!&2N+ z7ug;w6mNT?##-lbmucLOAThZfFB1V^+zZ0tb9ocAce-U)fTs6!KdsDD0ZJ6)Zo|2H z$vn5sfrR-?jrIjCE$w$dUcd!bf3(+@MGpa(P{aA2_sy!AACFa^dhzig+NKqv$sFFh z)*B-$Dd~E%nvitOkbe?(_s3R0$hR;}idrQiv;>OONhEqb5GX+f^-Yja>r(if(-Rnn zTFt-F95}=hZKDUV;Mq5)+25{#J=FWe&cv*Y$ock|5%!o-$gc_2sp&5Nn@$GjLQ?i& zX?rW)a{n3x@-bP5iX3XSt23``TF~Qt*f~6)?cCVtNyKIT$@M7UsonmI?>{*y|>s92651Grsx* zdvS7ldU6`SD4LaH(wvX3Pbe|R-hB}m(q8l!2~>5RaJnaj8Zca&>c z2^J4s`d@iEIP}k;%Yhr@!!+f^#W&77J=gr95e&~Be*^jvbUE!R(ah0o=_Z#;sSJfm ztFZzwziZ`TJW?_;Za1s6SSW%XS%9>3@5Iee03100BzUYVMPlgiJp_do|F(f7q44Y_ zOL>9)Qvaa<<5R-%8gqE78eNQDvzy^*eMr9oNIAH@f<PMj#dNya%aQq))wMK!&0C=y@{kAN~NZx-H{h z4pvr61w;V|vu0*OYVkChH*6bUCr&3EWo1!rrMC z{P;SgU*_hy9yyohKFw+@3LSWGdqqJ-)oXN=mzUS4Ug`~|Qm=S>do5_+{n7&!lZ#QW z(Rx=4s19rs*1x^V%sg%MC7Df@t0&z-)SSb_7wziSO>!8tr`+7GB`C0=zy#`-yzHmC z%i2gfW9YR_mTN~6!g_NCZ+O^NmX|wa;+O3f5B}VNTy8p1$W#m@R>0E}vw0nXM5|TM z7iVNnbIhuUNIsF*zB_cyy=qHO4m2Z zxe0;+|M6z%<3~?YW+Tx*-~D%ItD7D6#*MmcwV)B^C=3x_%2#ToH{_~dv%J6nAAA?5 ziNot{N>5)TFQI0|y}EiDOe#dn&R(P)Hwa1+@GfUW>^p5;FHQD=%Bg05rftop!Fd-L zHOH6uwzy3)l2&JdJy7+m!;v+*yw1eC%#HbuIQl~WPSP4`+XOx9ZMJ?UDts}Vug!N{ z$s0<0#iYH~=&2rx#lPpi%V(oO6?eWnU*V9iR76?VzGep$FdZAme3cesea-HGR*#|z zZLnE)0W~!FuS_5;thSgn|G9DjL{W3~p3Wy1k}U!Y+(I@5ZRQkXkjCf{vp< zl(MBd+uN<376e^Bc|&<^HoMzx4PT(6gDnm)%GR54pSh%EZm)~s56UL}b)FlkqRUwb27CmZ<6=0)qdT&#x`Mvjej=`)((@RzhCEZObV339TJy z>r~X#eEU;PUB)fjZ^*i{#em`}FjQ^ffxL{Af!AJ+7-i3APy^fwhc2EC&ABTXV% zLN1C-DLlHDidyCvnC7Zda&n)&rdvEtd8w$*vXFl}LkJtaZcVOZWr6T6nbY1nS%V+= zJ?)qtmKXMHW0u<#lof*4fsSVC7+3?z4F@q&)xYYYukTr=k+{@Uug&f<;MPLO_zFzJ zN<$D#B6Ein6P-)(zk>hHhh(~Rs~fx=R? z;R7(#oZQ?vX0E<4$u#gZ5ENRucY5^(r%!SjvW_%ZE(DOAKyWB)1tj6u)m8yC{Ltn8 zIy843jDQYTwsc;7yg_=xBjVE^MYD7e2td7-gzq02m$Dn@=!YnDdULgLq^AzZ z>-kXmwH0Ur0A^*~7Z&jC0v;DGGyQb{9tJ?reBG?^T`~dM`oIyW zPfhQZL26^d&CKP(0I$_*C~LI z2kfaj{U%35BA3PXPV{V*S(jXVa-Vp`-+%ZMqW9en-Mwl5l;}1seF73^XT}ROzxNI7 zDPr2y-HfbIOW?f`{~vC?{7Bsd4dVLjzXpl-YIiqrN+WKuZ1>|sa7q4e&EI!1?Et>? z1_Ky6@D?Ujle1l=Z-_uM2tl%gK<(_>AM~0e-}#pp%HS;)_mvUl3k$k~{G?2UsHzs4 zct-9(2GRR78!c&;vkGZIM)WH8-AS#Z=W6_OP`qBkzs%P=0FS`G>;{#ejogel{46HlkVg5_hx#Gh@+@fVHhNu zv87r`(a}X(pK~js02(DC9$KtZafd1Y787`juPtGqtmG$!UF6w!Y6wCL=kKR=~=+(`o{kkDn zv9_^hshp7!u0N2Z)cp#rKD&?{i*^U9$f`2yn~jhm`*IXm;NMdeSqi z7=Mr(ZYdM*&o|<4BFnt@F+dR!vlc}a+_dYomXWqmr(6PPZW&h=^0t&gIZThe`3Bd4 z&+Q5mV_U|EUBE>8G?Q|b>Jke%;bIwEI~6B#ob68UxlRsTFAQ+d#}yRj(~%R;6=W(3l?6`*Dv`jje>3^+r9hlTO+i)t#k!DK38cxq_A{%1rJg8MsT~GWDs_VyK^6sZW!`yA4?lgMT|X!Zy^yHE7(P2`G|rVT(z8=(mDHD~+jreedmR9l0?ezPcxi9Am8opz91 zFwm(5hlYM$enG!~<;btjNK2tM-j`{9?6lte(di8Hcn zT*%7KC--il+VFgZdT1ODKi`=eEi^p`*ja*Z(E1RM_tFvm2Fdb8DFA{Co|Zou`pRLq z{)9%7A`+zR28Mb8o z3_K=frnr16^RF;cAusd!EXDbmDiHK}hzE}-k2?+zUoq;Et}sJ4yrC#)fN=3L1N?W+ zY2nis1OyicBMT{m@~MLMGxfT4lV)v+@=Hq=6IQ*(KW<QPJ z>&l|-AW@K!Y~U>BEYh7W(=G+oQ;yME`Ny6fT50v7LE_^-3n}6{VF;U+)7j!)FNx7$ z+9*PfqA(z0x?>dfS?s=)CYh-r;WSztoM$X(7>ADok?>5j@;P@|d_eW?l;_OOM|@69AdCQB?h@HOxFbJV6IM?Qb&+C#{(gd|Y$ZoEGNub;?fxdk$K9fb$^) z3F?7=n5e_z@Ccw+ohvPzxBD+i=j%_%_-_VgokiPM@q&+EF$q2MkPv-01TVGASV;J|SV}%uIN=LJpB8kk&Riw*Z==R~Ml33(g13NX5G6sj{m@ z$=r^90e~LM*P_8Tlgd`|c|Y8!sSGVIvz{BTw?+803AYKNldyDF|5Gdy5q1vy@jVE71?^q#L&td`kg`6cFYBPd~Vo5OH@zQ=8Ik*7f{ zFN%+kN2CF_n6=g1Bgf^{Z}|#hDlNjP+(IZF24jNh3kWpnoy|wD$yz1yyWLdQ6^F2D zfB9rwXK>0p5f5an9+$|dGZk8Yin6^B`HS8qn>z|Y$osd8FR>VO8kc_{edyIo{rx@pHat9TD}Zcm`K2Nd-WgvWzbc)n zH2)w(Yv3nkpE|8~UbD;P_MmQ9#`mYpFzR4}DpSN?0O&@WO&P0U z1xQF!XjK^G1JOvB_9OtSnmi7_$vN#N=mG}~?5+YfboRdGTAeZ38dQp&vlEj{Dd;Ds zQw?KvE@cKS<6aXHIlz1I^7^?R*3K!C`wePWva`_k%y!k3f4s*yq02VhAXlB+E!UqG z(gq!41|30{S3;iz#sIUzqKrnTTC|9CO>#xKa+_Xw&9AE;!1?%#HKh;v~YDbtq6I@@d^e=R4_+A2sg)nEwdyMDZ%1O#7CvVdH%+7!VNrlOSI$AT;DH)YDUU zonZ|A5{HP(^GMXu(eW7D1eBV{;SzK#j*6-T_OfRTD?XMF-t05zvhSZvqhXQ>C^nS= z{p*>YKt0hWQoUHI4iEId8JrlH$aQWlU6`tJUZ#IZLTOr_;`H z@UtG)9HTBp)J|7_YyfI~;F>-onvl%?WehV;Koe^y{%__@H&|wX#<%!52GO#agpqJ$ z0_KB&?wuz0{t=w0pX3&>*-Kav008@sKm<{u*?Hd!Aef+YBQWo?XWzzjkY>8}~(1 zWkyo=C&1v)hagOl5)GGz=}-wRCm!N|oXR~wiz|6S#A&()|DAFd# z(o&#F4y5S-W->UP2?3->K*u4HliN7J8wg~8^P!@kBIUJ{(U%``y)FTBY+!o>6w7ql zDp%RcpTrVDRYNbHSg^M{QY@XonmuWC!{Yz9YqHcQh7!;M?1v|35j|^O4=@w8xZY-7 zsm1CoG;5}qnE-KBLQ;bDY~4m2;S10q8`-x8JUb|GQ@gG?9r!6J*Txg_9u$Z(vTXNAogbCE8aG3)J$7Nl3_}dRW=7k2jdEuG~6;g9Gva84S8U`>4{leKb*k zW!R}rGSCBcOSA1-<18G_RF1gLWiP9~zTW%&Vo+Qui28^c_gO&2f^z%YHzAj~bAUuQ zIGnY2R8Ut|RgnpJT>;r70AW;$jXN3vK|_@Z(r5J7IzQHVfwZ(++R=Ox^mVm;qwLc> z-qu>Iv~!2 zK8_X#{OMqJ4i=VF7P#%+w~m687LX2|7H?xI0%l2X!AsNuF_r|7BSb-J&3>39%Jd6n zN+{rBx0Fdh+1-2M{0Q=1LE!)t#KA%TUMtkQ&36)6bAh~gZN+xX`2393SPDmRh;$xw zJ9f^QyMoo4K8^z$UtE;&DFy)n;f3o0xHXH2(FFI4xZ9`Hp+vj;j;3e*W}tP!`{;x~ zx!G}64o23pB93{n&@x}Idh8=Y62Imd`%eG%rlasXBj0|lZVHPdwto1mS(y%_Gbm+i z=1VTz=eIx&aAa#zFyM8ya|xCkb8pshFB-Izc*ecckCA;3e^h~55-kfdW1>Qk{K&(| zWMxB8*ZKli+CknE=A0}g3h-l~|LO`?STA%1gT$5SjE!QOSy;^1dve)F{iKouiLCGZ zldp)l_?Kzaww0BR#}uK*>-ViQ;Sw#kQ+Q_PpfULB;)*^ld$!g|Yoa#$e`^8Ot`{)@ zq^Ck^H+s4Vlt=gaLC+rpgM2{Qu2iT;UUxIQ3C9t~1WK|U&>welT%HS->%oF^(p0sH zTK$)aag*MxLx5%tuF)E4d~h7Qg8N{S(M}f3Rv021&Oa1RSStvK>dS*38zf1?RGOey zt>Ty(P_O~z7BDc--tKhMedou?kRW*S!nF5Fa|sJ;x)i75_5Z+Rph~3W(kR?TG<*;6 zdqI}cXa5xPHLB#P?im?aV$e_j&~;_VoA$FT#n(o5?-5q?7{0@&I!+~;`|U+!UE%b& zKi`>rHzs?_z!4&%(yTKLdVO%P;`$gqTc={0hNl2lI6fhP^;}w|Bd`O8XeQz`8U{`4 zG6pa4PI*8RhBrbFWOXKb?~+x4ypmqMfZJm5+nfpp+I2LXR|1s({=XB6^I9dq-EoPC zu4(y&FC(-2GJhF@7N>vw!UjsCjx|Z3bfYsd%F4)Kv7P(E!Ld}z2|Mnr0uAz}Eh#a8 z1sJ1}pZjeV6k8n|5%KnTba?pFncMY++edE@u}ZeH++Oks7aR22=9`%bJ_@&So{u zCabF?vg%w{X56_-Y4sm9GhJ@4?E(_b&F!`uA9R^m1d0|!;g^qON2U1q+^wTqH0{;1 zF)ft76T8=vUp_v@U))z4UuVFjm=s|I;!>k^h8tK235UVWl;SjhpXLpR*Jc0<${Za~ zXhR1mirJipQ6@Ef-C<;whrNskN+T=EXEIHC+U=V-wJ`W(D6f|{Lswb+Tg(s zV9R)Hob+?}-A3i0tKX>_Ib2nt3m8cddVOb8U+`P&ZX90#lf-ip`I;gcSxD(%N1@UO zZzBwfRyG}M0TJZCaUdTj@1Yv(!ccyXllIR$kLz^G#-@A9^->T43V_tv)#kc#pGOBJ zG}yaey*!T^Bf#^4C*%U}k}f3u`oiOGfkYeBm0(wMxGz8fELP`1i2L6~M!ya=7-(3Y zx0P6FJMr>`5N^CaSo}QL^%5%rtWTNkVK)6FfQsV+=ByXg_OMZXm8oTaBZt*QbmzO6 zK5hMkM?@$Bb?E;o{bE;_901tPS4{C1hV$H~JlS8tZ$ zndZ}90y^eNg!!6)242h74qMgJJtase19Wk>2&_8IOMl4olgUk$&dd6o! z7@$>SYY&!>@fx$q>li4T%tZl?4;t(Xt@xbuoA%asSGY{?pcx7ZU#Rbu`VfUY)C}0? z?_+9q1_;#}r{a`ZPS8ahLM*U!7^SZvd)l1qTnO?yhjQd>!$w|&NbW$QGh(bkCPB$V z&>Pg5pqn&(Kdc<=P+VMG8PS5m-(4MnAE8)SAQ`Z$n*%n*wgys=_KuF*b=P}L4|22i zbGQ)&=<~yu)s<1ifSeOFk%gskH(8$^#Yn%JuKXemYDy4^n%z#dG#PlQ*0pa}SNlK2 z2Y*ZW^Cvu~k5&D@Z%erlqM;*?xMNmTi0oPO2_r=!k%umg;Hwk1wzeqy2Lye^Y4pv^ zA}uj6!vYj&a8(O6m|=h=3=U?7-S0@qUG7g&gfK`d{!ysrY3Ek!URQ&9JuozoR*J&*RD-fw;Y&NG-Pr)q%+2^I&>qPJn;+cKYBn#{cn9Es*5TndoN^w=6Dh>M z2J{;(|ITUl#l4e|l3FPD%>Al=5%=u=q0<%q<3V;f6@V5rfF=%^{V-2TCSWLvTWhA? z6&rk;1tfj*;+`^C?&LWFUsZDNP4e>k?fzTrubH4r;*a;vyrTz>DH-H|de5wKZ%-KQ z<4<$~)+a>v3p_YEmi_bbe>>yp?GdjH|2%0!V;G;GA7!_S>^$AOU3v3^6PKfWx zL}fjlDXwX7^HJ6*s;RE(QJ1Z}`Y!a%!7@`aS7DPf=VF+ohh?moe;>(6aoM{l7NxmRc?D%<%&gTxqoYyxYGBRWG9FN44T zsLh+o3y7@_z3Rj-g!iu4{vsECbmJLDI>eSRu%a3DS3RX(UnE! zp9dOV{94|d*tyQRY#2ADjUq$_!&F5LrZ1Ct`HDpI{~+$GqoRDjyCjtqkWL-x z?(UZEE(PfjkS^&4Vdzwekr+;QFe+IwHu zqo_MRJT8@Y!l<^Fp@+6HAT1Xzz}|KcfoR~$VoScF^-(E28W}(0DN^*RWeq>I*ih-$ z9(=S7I4UNF(@jnkrU7NKd?Do^O*BgL9BVvNK_2bCK(cN9jF2Arq7g2&wxpaC1UccW zK4&HGBLqYAC0^kD4;)d6U#97Jj|I^v*A2(&9e^jDpKzo^Hf&1T7bnBbQ)Jf*66>a~ z3VvR5Y@UFF;kUO}L?0tIL|9qM*oQL<3{pRa9Ls4G3*p(6Giit6;=te7~zJTM3GfWOE%ASDtT1LM%`Upabd=Pwiv=3{o0gC zqSlL&o$)xI|9T&oraeub{W#ko*Y@D6SkPyzp(lb&$<3xqYEFG+UYT?u z5ATsgaEN&DGpHtWxx9vw+Qgi5Nk$S&W|D>`J&uQ%Sdj9E(G2gi6<$`nza}}7kAJwYg0dLx%rx}z5Pq1VtCQSw zbi|IhHI2yQK6M0yWaOr%HTKvgU+L3;d+RB)dN~bG~vY@v8<^5UsJ%cw}VBba4DLIzb9|Z7 z>5cbALzl5!cnk{0b6y8p34v(L2|gQwG&sp$(s!GQOrM_I61y}_8NWl$FQj*M>5X1n zANf#|7nIxx$bFB!c#5C$}Ln)`QQ7f2=Mp@x_KJeuU7AoOMER`BX2Cj$`(lR=5==9wV^v;U}px zj@^QG2+hOn&$`TjgBCHjsFIhJDry*Io9Y-2Fx(PdR zrvyoY7j?U6v2y9??K8SH?Tkzh%z%MY7MaeR^!U#pkc7_*r@^Cx+rH@PyFRh*`?itJ zyuG!zGhx}sXeDbNQ7;;VMIGPcCp-)SV&G3p=v0Z-HUl1N;7_7K9ew)vk60YZvVlC< zEd7FW$si!Nl++pIs+}-6ABU&NihpZ={jl^p#UxZ!Cu-e$PrTmiy?3ru#>dCreiZ^+ zqk!{GF~pyroMAH@WzP4edL=X&*s~?7j{?AxO-M;)3Y~HwgH@?Sg<<6=6@z7OhR+4A znWVfzhJKKV5d-7-sw1CW?>Rb7aaE(-TqRsmj^yU(4I}la9du(I|CB-P_ICPVkw}vh zk}t)kenNxC4}GFh)pTEY@A>R=F2i;?k3l&jypj?Ax<{nV8dm8(?}wHmm{^#Y=3U*i zzD%0)6rtgjrfuD9OU@ICXj1gkVS>lH=|NGZYitSmg*neXZkJl<)3a7`8 z2-do5po*2`jV4S`M}Df0ZS?a=opfi;KE>x_!dbd_Z<)>&t@mtpg5$U!(BD6Uh?$-4 zq|L88Opd%MtngpdFC9Bu<5`Gm)BIIv;KQXZ_6Q+BhOw5uy{N3ZN!VIk+{9-+pcHoZ^&;8O7ws!ZYYP6_J=PG83cEF(q zG0&|`Vu+9&yFj&=g}~ZJHgCw1WTS0YROA_bd^YhYU9hFm%2vU5?LjM9=#=d2@|#W0 zpg%d6TG4W*IG1IcmWn1`GF@QrJbsiVoxO})s1(xDNDblWo5HZ<)**^~lRdq7BqW)? zZ*OBI6&bO$yWL&&X6R9d_?r}#0X`Oj(qpZFp3XNzzmh{xk*PA|HB@y!Vf6|p;u7M% z@t-H7*8VoKMLNFF?lYCoOn)q@|Ff^MZD@|=g zTH3`O;Bxm<;8+y;qDh-9WMU=fYw}L2vBy*#xm>%=qi7p@fiOedSv8Qi#`%Wr#G8Jw zsHg(%@41|uykhadQ$O`m+aGZ@*|Yq43g$eveMgw(J$)OEfWRh;jyT62jn=`Adf6{{ zuJ0op`14g|i7Ptknj_ypC9>+aUwluxhGvt8pSq2UsjJ?TU?b^->4-sls}GcePz0Lc z$yt7Pb#sR={yN@CG_m6O^qu0ay{pkX?>5)O@|8nBA|X>5P=3f#j>00@s^7hXkT{G* zg=STP^Wf6rUJ3nJ(;yX~uH>S>>*H=4Lu2|r`j!sgj!%S@i;2d#vF^<|hyVg%kNB!w z+|3r*x8{Z`Y#Tee>pr7`MNN)e4NVTM#I}N<^id?iRb&m2Y)WPRU z4t?g(q^}x-$*JpP20h+*KR@2(sR*rML4HqYFlwZusDsWyNR^M+>I4OQJ<($&K|KHK z(PAb;U;$6BGTC4a0wA~1c+E_wuFXJEQ&(T<@@)KkdBwP+qXRTh>&5R|xLsUXxW70y z)xWE&Y~vzAe{bd{+zUqCp>Z~46_=imNDnF!Qm= zlYI3SSxt4~^XI4Z-4YQ&k3SMF48jBiT?}8FE^3AAo9Vqj9<5oeS@obS7G=f>`utRn z!vZl(mX)Q_%@k^Kwnh8_@{uqGmJt{Cd&P+)RXbFU<|=W{K=MgN6Lto23=D+OdF3iq zW}Jwp6kDmvGk@~-C;HQbgyxsb50e}tO@@WUU&GGys`=Qj@~ zI12_^52>e>Hx*FPv2*kO$?99Xxb#rSh?^ZAXU`lF&j=Z)*tcthY;!VaS5&1y@=fLC zXyjQ~`1Gl9)W_3NQ*x*w8Y~8#8;(M%25jAef9+#&8*& zr$>)+aFMAZx{j_2>gx|C!>~{>Fo5=0*?RPg>9tUZe2i_JG_4ImJZPHi zd6#{bvXHM8N&L@0mbEY^G_uxXi4zu5IeH$#;l)zB3yYzN!f;efcxvW5pH9aX0K7UG z7KxGE1l%}ZzUC0*bHJ#$$82T*V@Q)?T7XlbJ-6 zT5U&(tC)I7N*j{}n`oC0I8q8~dKbzrb`7S+0#GzS+kW%+blduVLvUnBF$HcOVKVy|vbjB0 zN-_6c*m{0BX2D-#oW=jDk#KI8xA*w*M&3v?{DO~laA!6pI7F1XOHAS^(iL=yIby72 zXp%R09{ ze3(2Q`sC2UASk9_ASt(7__@no+~Z)HM>kY7I(xHR#puxg84-!LQ!MNEYxR;VN3ud? z2C8?DKJ=h?W0DEQSZom1u!BrbLuY|=V2A(7B6e=O%8{l*UT?|v%t6zjRjuF2UE&8~ z8X{dTyw-U|C9T`@d4K;)+iRIYk&YE7Y)HB79(NiG+mCjr%=!g;8Vh+wh8wz!Jij&B zl41%H*=Ck6(kAEh^5-O3cyhJ%;6ld&_KidyeV2qpFb`)vIlPC7v0SzIy0~xyw&*aK z)St36wXMf&dX(vpNq&9)r)uRT+G_axYLI4f4=K7heSD$3)Ie(HMbi zg)CfBhGboE+8nyJ@E&$XDV(XHX%3yTFe~tu8FZ-!YeaZH?k5x;{PY?61&zp6DtZV^ zeVZf`!GFQ7F-M`8{;_u&Ts}yDG4Oi+2)wKo0l@;VIHtWYx?J&OJ+r^vW-6{v zgPDneVQ1H=DsqiYugUUJM9U%x&JV6v6k=H` zjjoc40fbo-v;1WrP;YO2ew!Y#g-pqDynE!wUD{fl54Wi!Lhq&!qs)Q<)&yQ|A}F6E zp9@lZ93+yNxStolC1r3>Tb^5FS?+8qpIeXJYNxamw&pT{n7-I`u)Xit-mFQB6&AFn zR!a}nDx3hU1JcE%6Yp+xg`F`|lsHgg)l1d3I7!hnSq&XUsqE8eG5kRm<}Ztk4vz(&nXOU<0zXOz~zo;V)8-6SsYGT$s>V~sHJ z@=7u=@aBVRs`Q+Jwo>{21eQ*Zsgtsgtiw0T&yYP8kYADrIdB+8O48-L^?iCNwo=}g}!28$b_3O@p2HMb2R2ORXB=Pihg5%lx!);ZTC~lN*V`J zlve|I#z%x%D7&f*@M zI{mk#9O~b51`tQAYm8%GaC4;ht^H{D9HA<%jT5{imXF^lVf>f?R%Bod5C0X1%0nkA z79=had@%3*Czj(ilS&~E0bUZePT-R#M-kG51{~f8KW+B5;$Ek`$t}-~kj_U$y24!{ z5I4YGM2JH9F-dZp5f`$+g7rckAzsSygIV|2)o0EYj8|Xf;I`x$9LtTVDi17uM1iVJ_p z$g#P5a-Ib#0>rHNMFhP1OP4XARAre3#B!BQs5>J1NAQQxgjhXksnQ%}q0D+`ZNDC8}06=C-evoM+^QWat<{Vka_tDj- z6iMETlF!*|DZ@nLBt@TzS}X{F(U>pwTMp8zKA~upFM#qO- z2hs;LWxKZ%WWw440d+SJ_UmV>uJ(*TF3qn{ODft|-*hS*+RgXkvp2i`;{~`Jd)n|c z3MBvrbdDd-vX!lmB6=B1cc4JLE~F>t^BXqfW=+B+B&WiTAadc4c3}LxthtCwwaS96 z@zm|xs>S8%I$l=BD%Jwnau^L7 zZ_-!>BHpIRK9P8f{Y1)Al@apWhJQqn01e%)E%ZYTf2`u-4bzG&GY)!TV9JyYUut~F zN<)tg1tX&6!Iym6ZmBM6u`CYW-|a4cwjY@&{zs(tnf(+>#UaSQ|*js zQR*r!W;ATBA)Y;@bHtEbhUk8^3C=v)ZeyYX)$?QC^qQ|1K&M$M!?Wf{ z3uC2oYu3vhL;0+4M8&3iw5+bG%|}OPbohzSb}z$MnhPSyjPn%fSxvn=eg?&sFg;*R zkzwGJcuoO7wUN6+dpHIZf&OR6ti_^^7??>!2{&b9Lm#m`?OIb~DNHii+59R+w;WkR zi<%Obra`N5vQC)L4HKp76iHjjtsr9n{u*3t3d+3}$`@)BOU0F9juOml0H!-q~70W9<8c zA0sn^f_$Wmf*hbW9@ z15E6^a+q@d+5h}X^qpR42a+lZyk`NR)ZQc{0}Izv_BewK#vm%ItKuvTu zL5R_8^AP_0n5TjQK?ryv7MI_QCKws1y@>hrAdw2n z(&Eu}OQ1c|rxhLEb^&5hr?#prJq;`Wunwa+REs{ou{giHt__9iYbqWkF~ujWCaX2& zAVoL3ww6L3Yw=*PgqVvR@S0tgW`ozq0WTe-_Z(LQwuP3Ia=N|J(l)7BA`Wqm_8&5~Khhzxh z(3!pK=jOPSD>*cQk8jV&`w37l*p;x2vjgM{w_Jjcwx{<;aJQNS$x>wHfiUv?1SFNd z(g`M$_*A@qMEE%(VJp)%+)?5>>RP4BF>;mDRn!D1K&-nE%&sZlsv+!0e!{xXXY61g z*N8h}OoLO9t!8D$g<>@=SIb~SYER_=*6pN3&yuo=Qfo_d4r0K;2rIKENWV#yCD)tlEQ$Cb^dO!8hzCG?mzX#TZu^-IACE(e z_Jku-U+Bqy7piRQb(QqB0+*5>gj+#OD%1L@VCNV1_Kud4hOV}O^M<3AwYdxnFr7Fq zq1?K*;=HnO>3qPY!Qs7v4y(M5?h$~gc+N=_)n%q;9zjRT`{E5v?foD2a&;c{DDF!S znNFyMoVAS=OJzyE@+-3EGuVviW25i&_%_@?tGGx!do;uU9%YHqN8P zoCpXYVwI7z8}H64`t`*QIVpMM|H5ZKJjcjm6~DwLel<`XFI0U)1Vy^?aN{B)jwzaa8V<@|b2 z(*db=MjO3N1#tPp7K7*Ke1NBrsmNd{=;CsExzclkOW)tTQ3eD#i1rc|^>FhnKHJ`b z_Hz>=`F`Vm!#^8?ne!8c{BAja%e{62*tW0Fp-E{w?x{bh`&O{+%Q7;lcX&+222g zVVaXH8k6kkFrenx(lla!4Hdf{q%ifIUwhdpUhDpwOyvpCopjx{Zh$~vkf?%bnSy?G z*8vObdJ1HyPmga}>8L{qW+g)-><%^=87lctFHf$h>{7jom^N-$W2Ay%Yx@Eoqu6cA zuf77e0T9*CjR69SCXob5%j6`K(;QMA>UmtN;*#Xu#ijHxNkkCLU25dGBz zBFLAP!imH1f|?w9W?IS3u7iE020^m_0LT~53&*yJT)Y6{u1Xl(&@La3mM4z>^3yWi z(jFfRf^9@)4&`hA{?#MfA?MpgwajZrn<#Y5E;i=MqR0lb*TqGt0!;B8=GdiM7%z1K z+7-Mlc-UJtbtE)wT%N|js%mRp17?A&XdRYi;D%m?92 z)Z1FB+C3|dAbcF5%7melJ#YS=_v>&OnF{cE;1CHD32ZXzZ^l4o(*$IjAebyhuLP|( z1;HVRD#ds_d>G<1f$|~;Pm0HG_cu(4%`Z*CM#THQDin=Z3Ll>-{VLrVjhE1rg^Vws zV8hm=EAtdfk~U70b~COADX-and&z+B{vCk!Fd;gKBrP70=QhL|)Jptff1pu~=5CsC z5Ot1@-L=g-iP_idE^RQ0A%_L&n{qVPYw&$@04ZUu_J^7`gE~lL@~;--v7GLfz3?m& zW4`uzJCf;fxEEwqo@4C{cqePpeqr(=_l?*^w1Z}+l$;9uVj=Y=!+togZ?|7 zDR5cN6ZB-HPOeIUL!!ADmlp=q1g__=(%tvtdRjB?u&9DU^)sHc>}5zDR)rn1+i+VK z8UHl=Yxl5BcP-`hH6;yAJuH^p z8FI{&)gZ+IP->WY#OE$ke%~5?`z~yG2<~y4B?KrtmWuH?Mx3XL8qbLwYH`Ws3E(2o z2GkT$`p4QC;CxBV!-?&(0?e*FK_Un7$>wh$&F`{cm#SKo!h^An62k!&fBINB0RrAL zS9y*sdeX{m+rYFc{SqgNex&XFl+sc*@dqjHwAO-(q06On(YN`^a_dljiIjfx?JX7a zKU+C3-n|W|bPoP`eqvYMvl{;U@0Y~7Gry>Tg^9Gm4gLc20%Zorq1ZNI4}S$fg| zKeMGs`P`I&yFDN>16kv8C14@6B-JZlnVg)M0P0{@D66lpl}p*=vxxBEfbt-UUXf$j z13zuT!wd{4;FPZX4(_SYKiIpgQWCSazH@>|2D30PXl5||yT_a0Hg0h7Xa!F~yDboI z-hrKyF}IK;U#OF=Pmj(^;1JVb>)O^wl@5J4PghhnP@-MV?4LNo{CTf~oDZ0HPt+JS zPNX`i(K~ARTlMYPh$6Y0^{5HJGs_tP|BH6S$KGu?cGP9}+yXj<4RPV0W2f-_0k7#Y zE-x#ttguGM!~kBIm$6@R2^U0x^JJ?AS|QFKwQk88F*ZIiq^38snEum-m}sY|M_py& z6#?q{AYjuP)C&jsv{pBxxrAtt= z&1e6Se{i!O$M-laoXX_ygM1vOvn)x)zknHFr1@T@v`mK;r)hHg9b@gZj#q&QW*BG& z^fWpqyiFMBC5xkRUV*X5U(y?Ux>uY;AwL(cV|FK74}Qe14CvMQ_~f}_z+0v9hoBKV zHxeiv*bvh~G@bd}zp>#NG+YJ-#k%Ze#6PaMrk97MH)A+uA8c{~FCF=IFOnr=6@M6W z1U>e1Fbt5+7e^*W7bl}y4i1#gr+wMU?|wV+KV=?YM#fF06L4~erQr}n@X>Mo^ey}v zn|M%GmP1ebTZEN9?!v&|d>t=$=qVaUq`0e|iKT;~u}`GpA{_yWNzSjZ6uP;8_9y?u z#5tvD?UdDa+OP@^AR<~@JRl!69KN;FpzkQ1I8HJHA!yupyH0I<4i|4xf;#?M)hg(V zd|~0p2hl8V?zDu=)Xlr3K2z>`_utAmE01q{Japv&KcV?M1!>h!c;1NWoxZ*xLHyDR zORRJk7*M$nE0x*B2_5EUd2|5(LOxE^oZxNLe+Fomnclg(h5;zzS+Im~u|xJ)+TkDW zX?E3)lQtYFwAd*5#<8Nz%MO##tu@*is?-4fgX$ju0)H&Ps-y}>R(L7MiF?hVlgtF* zg!{M9gX?h!vO{gB7E#)=vSN2n}&QDE2-rBZP^4hK9m zFtM?Q2!TB!oxge7UDAYjKKD^vyY0A*(e=Ix_ZiS_9EY$$E=I4hik}EM$Q=Sli1ShS zat;m9pV-k=N=CuL`E9F$>ALpK+1K}8PL1nSJePq(kd}`9c?SS%8x7^0DRAv_U=v_a zE4=D(IX@XDKB4F;e%Az&tB-^t*5Zhe--c{J-^+9+lQ)v`bTZcUvcgG`-QQDLU&Uuh zb+;M~8ncRuik6fQ%~mc(46Mp7uB~d^gJhg}0y%A}@mUWFJK))kI8#VdwN*>}U^~yC zQ%-*^<&Ax0*GE-J60Ep&z*qg*erH#`g@ITK&rFHk{ykf$YPLZzX;XKTQw?Ha(rZ1- zTJ7d0Z~0Y11t~tR%d#O;08PTfP-DW-D~qkmN^HMR?E+^O9Sqjrmnu)4Eu3)m`!K!} z&*(kqv_o7hB}?BCZ6uw4rD^w5lY4emEL>*buM0T>uRs6ZWWNw>4I_OJ99jqnCJ}=x ztjPkFdycNO^55;rqNgH(!OU0xzqz;e-;$sv1`Zf|)oBp%&sZi{XE(|Z~ zhfgJpU3`0A6sky(B$NFmN<2PCK}%;L?_=%!YodRa5qJqJi7mO_lSEBKT(bUe56$Gk zs5^v5KoOahs_nPUQwy8r$kcB+356$sTY zFeT7%!jd7vYDkO;+bjuQb;AFC1Ela#*n$cyQJPl7@7m1RxFx2Z=QBh~L8kS(qV|6G zcEXS?QsJ4K-8~;0+l!aytDglrld}9*cUDw1RehG8A5vOWCByQ__yhUq*Zz4+S+WOR zX|>^5zOt68Zng2H^0JAgYy)kb{em|oE27m>-I{)9qX#f z_W$;0@mQ>dukG*%%KP-gg|t*OMDE5AGI2<;SGPdD1<1N{G)5-(w;0oxdn|uO8z~4K zKZb-N6P@kP+;8FzPVSu@GnMC5TdnM*ec-fmQ2cW`pa?D% z>n!-)t7uytwYo156A}VDEgEV>mj4G6d;0~I82g5$rc!K(TK?tU9(j_!Ti3vzhy~@~ z)XBK|GS&`d!$x(!OoO1Enk&4Zz77X}zVUWru--R=&vyHl%sRm4bN&b3CdUNm9Rtmv z=jp?P2sbkeHw>AG!t-WpCiL(I_1MGnaKUpY>b*qu8}t6w(`fHqqO;DCLI3-e*AHf&cKiE}Gy4r2X95Qpm}tI> z^Uc^0O8=(4&YqjxDHy2FXsFVt)PGse1CG}}S{n5gOq56;XQ=EFnTuR2d8vH>ZWaSd zxDrWPDmLWNqdVQ_8{r8_wo_Mr?~EKh#nylqCL$&R0~6DEKu|UF&lfEEz^aXdR)UB% zQ+>af7TCf#id;ih#F%4C9azPhbZhAy2z|Zy*KznQwfj;MbosJ2*U2Ew1 zOJ{?AzdNKVEo zh3X;8(&Ij4@SsMoSasXFvgL2D%pg?|=sE?FWQ^tH;u7G%Oeid<8#m>Ct=00qF`tW% zVQ5B2TTSo%E{BlwHhKLV4mNhTIdqFtTgN-(`YI^ssY>zaa5d_3E_p{uiFHmcbyy5n zt!s2TV?t`jQl!$q8m1sP+zAq}l(p`e^XND$tk=4`MHTogi?@`wX28v9f++m2E>lyr zVdL{{ZBS^u?mEzGNh!RyTUpZV8b8P>#4N+>=lN27$*Axa56*|Y4|XRnCum|s%9Trb z%G&++)0xjttF+6JsTdQMC5<>z08M;R@7F>f^{;vW!wc}?f;Wr4`)@3-xHj~7)8P8< z8+&71wQ_J4L5e|h=rhC%N?s3-q8p$ZzhBd;F}&NDY4h7?#mvA34~sB(%J}pJ~VA!rD<$pEG^xK4Z-68wP`mK3-0OXeE(W14%wbBwEvKzlaps) z3SH%;3F>gSau37yKpmpAHe(hH4HL8VFnDK@>?|-wFdA#vs&>Zx#`T~{00Z&8r&xk0 z^MIrRsl)jJy@H44tF*3O5J}DJi$}MwuEfzhLbu`y6w;dP?|*taTMVYfk2zk+NhZnG zoeYa@sSE>Ot*2Q23Kl)|a^}Fl$$pMpIzgI#r|oXKl-K4=q5XzC$N8T!4g^C%aFM{d z_3`7Q1hp}{qo;_7yyD2{D#nmmcLQH@jx_I8A*AQzmp6TtMQt;k}>ovN~o>@N_rgeAX8! z)@Wv8p{uLQ@9yyW6eDV4)*=3wYTonUJ+$j+RwV`$+Z^?laMhQQv*D2ZvvdPcKI0{& zvk!#$Y#&O*;o!Dioq6+k+BG`LfsIva-;liRBG}q?C{`?bbTKk`dUmGt>TA$NLsr}M z(SR!^SzphoO;i-Pi;f{wi=ihB%*?v5j)}_1#V8CT zOfwS87&;|bq4fgK6#ih^B~42kY^3?;Y7pWBkmf-bKm8GoVX2DX#{!E)i5iMU>M?MW zm16=NvXf2|%))$H;8+LuLRD67+S@rum!Lt233UHg@yUB)pwa=m96~qNzsphn_00Ic zYH1J+Wa@WI}Ep3pAKQZ-E2E%$!Z%^Kfc5 z7s)Al*B=C3aPu5Us4Trfl~vc7K^rjO`tAO0tLJH@Cx{5WdH3#Pme2Rv7w3{oVY%RM zT(4gJ@x6qa%ieEC#GpCFtWpU59X?ioYL@-P*Z02{0>?#kmiNTm)}1Enpkvoj{=jD} z5oZgF3PH~m(xF}@KA=z(SQ!v>Zm0ci?b zH{?^G-;Eoo(}mXu5g_OoB5nIA=Iv&%9hOn zAK;fht4s3(H<<)QemwJD62_35tLp<|U5hZ$o1YR{16tpME-sID4ra-$i=1cc?*np! z_|Ot{&)apP#7#bUUV7FnrB6ZIZjYdu6rY``!$hzC&edD*>=3dx`gE8mEVvKqL80bN zQk3wvT)u}oCbpEfK-?qhlgv7rA~f)A+q2#Px6*o=E!nWOSI$`5R9t*{SeVs(>CdBB ztV$A9XSEidWZ({mgC1rF-Hdxz`7-QVNI?YZ{5Sf)y?H?Hd~r1q^HgM;qJq@;6X*1UENXQ?eWp8&$d zu8S7{6_xqIBcQ8iZ(oE$wqmgK|F&X0z+mj``&$AO3VXhHdvWUy0<9rY#n3hu1Q~{8 zS~s)XDY9>Hzj>slu~4=DaVtf|V8O$x|Lu5L3fMCTzJGh7Gny;fobZS2=k+d;se+Pa z*PC_bZ?=*a7RosFKb#nG=J%3oHyg%(_oL%qwpx!uVez2DYtVm9}4HMN>lJWt2ERZ|ftV zyXx!T=;y~>ND6lE?MP^&899}z8jv)t0t25Qt-61W!7}DZq4fPLujktV zO!*Y@bD`OIde!?z6xIkFDLy-L)@^rZAxmV|qPnf_-zxG#^r@xoK5`Lo|Tz|}PV zSaOm?xdCXa$*e9XH>bD;o!V6p@u@GAI0TghmlqzmBAg@`R44n1aud3)bdcYD5&i&} zfHwob;G?^{LuznyIkERogqYQAFa+l73-5z=llcI;{54dugTV93l|zp(6i4@2zvIF7 zn|*pcYV7-qy}G>4pyNsRye%;L0Z_tB-`)w>%mnPZ%OdN@MNb?JB)M$zcI|83Gxn`g zl@nkN%NsAx&PL{f>TGToi(x!BmoqV@sAl&c-`ls<;!~M&rdDV+E~mVtTydE6_3gby z!fZcZdWlR`+jL*yP2u5j>lwLEZ%I@Y`n?1H`L9X zNdEfd$0dNMuLZAH)62BpY_JN(|27mn9yLK*AM9}SIbZNzm~81Xhc-$Xc+bGW=$nD` z>wSzM>w(gXAmzAt3jft?QoAO&%7$xpRMA{_wlBf!meq{<`4^9!c)0j>@Wd}zVmRlf zN?(hJ0A#$zRHsOuL#*n4ewcVOqXiTJ~EqI=;Z6v*J zJ()-1Arr{l(OqoT;bv(uoj>K7SJ!qE-eGGx2N{!|H+(yvbf~VD%BV{(v|NfNlLvc^^uwCQre#@s{$CB=eT-M$1F=t7!DL}_opOY;@0k?f8 zgZk_Nx>v9MezwSzm6K8~9(w?b06e1$-`?Ku?rv|lU7j_Q9lr_+`FwXVmy`lsKW><9 zZFaZuxGA2+_3Wevhcs|?CFy(a{fXTLzp{GhftI9OF=j=cuT(q=I{zMf%#IdAgQxC9 zh5WA%(_Xh<-mNIK6W!?tunAbxAF3}kCUHN%=pwcN1i+_E&^BVxh;^b{{C(N6kZO}A zbCcI8K2t@57nqNUc{jA!V^G(C<2y{Bk6k;%Tce<9`Markye^Ys`;Dqxu*wX(EM zSzjV=Ox)4Ya(lh&as9$QCpZ(V?Hl-nS=aXk?40AHvEn2%ZApy&#nd*e)EXS?Mymp;8n@?wUz5i`ToPyr1W8b2!rXrBj8u0$CDqV)PbvI7t z^|4J68~s0c6w|qp`%H1i2Qz^Jn?PMd!=78`2n}_`I2RiNVE^Xc9up(O>7NgJ4m4Kv zXmi5M1UrB_@%^x}ljl5U%3Dibdh+vh_H3;Fc)dRp)PejhDA`W!kf^qAz4Y&dT~ExN zUM4M^oLh&+Ts{+18CJgVX`{IZRSI`={$FJ2F<7m>=1|k@x2@EH!5xgHCWv5fz8!3L z(*c^_K*jqIDCpTdn@1Crr!3{_T5jU%SvuzBxAi=NkoC7vO1t&Bh<5pzso&4U@`DAx z8M*zP#UtI|cG{ancM+e}Y$US7G$;TWa*PQ8?tcUUoygk0+4DH@O~|DG;@^Z)b>0mF z-?iTMIo4vnN#{Yq!@)NHnHd4`8p7nS|J&e1FA=e8;fBfnVoR6KpZ6csqoXtPzq#s= zIIwqY>q+AjAGITF^40fSN?Ev{)Y4TJ2c738{xd$Kq>&)1tz8yoy)YLbTAVuRFxy z^V7rAb1dy^1ze!5Jq+f3SuIn4f0>l#!kq^HHgbP+TUh;T_Q^fSHs{B>_3 z$nfx0he>C1IEc$lfL2mS@4sJ21&iF?*GQ>nTF3sVQ_3SiVtT`$M>k}u>cC&nI@{%( zskV9Wjrvw#sdVAc-Gy_KENlB-A}Nyn-sbLfqxGO|{jA4=kbB$t&`Xf^_H*N0S)VUN z4@iP=pci6RGw}G0OgQ?4FN?^0UO0x$xe8+e@ zHX;Bwtk$-7IH0Va+l@);231abd-drOP0;k&c#yxyNy+3Zz8X@B-8pENpv7w_?oiy| zxS?D6z&X8>R@Xm}%16iI`geTJ`)t^&vR|CJf zw)G}!xBJ@|<>I(L@8=Z4+tnLAd0cIK@rkJ|PF>X-`qRAqspvd|n)#S-A;{Sq20o|V zToccpA)?RU9TpmtHTif3wBKh5xMNPL>M8>%$s{Ki3sPFC+2{+CCtQme0HawnWa&4v zZ#;ScF07kv_ZCxxFVlPZP=eA))vxFzahy9r+m@3=VSUa2@1aF0Yy0laFnM^m%=~1F z*@_G(A*neClc2nNm;|Fv)}6(5-QY%A!LT4s5_tCnkH~#LZs5YwBJbU}z(H%(VgCD& zn3|e8jL9ajTbUAB!06KAd;lvv@Hj4458qSzUx_7^c@KTM z)X;O!?Xj5$^$A=P2FLRPX&QR2xA_r-O|q1VHs0PyS%aiL4Y39Hb-p1h`}fnO8WTL6 z)Iq@|4=ZYzZt48i+wgK&#=h|b{O(BzqD^m)P%+Qu`Mv;~Mcw1;kw3U4N5{%Wa%NHUB-Na`Mix7V|LN#CiB9BZGTZC zdK`Riyk=g4a8>)gsG;3prH$UsN?^2JaM*=s><%Q8iTX{O!(C`dm>XRVvun9_L4ORI z@9&nn@d+)&SnOI64P9phJeQ&~G zz#pEMTqK#a^< zAGW%s9f|Rej)FV%&sn!WdAM|X?tgJ;0SW?ITi?^-aHRB)uI8b!lizi6-tFb8G72=C zMpHJ!=VxXnCJJ3taADHhG`ER^*Cj;g9p5bseAbqb4ssR6|IU5~Hxp=WHR-szu8&zo zbh$9Cwv&)?>sv$49$-yTFJxk5Z8g1{WhiFow2r^tZ)6f_$ATSZ504b)mA4t^w`WnN z!81kd2Yh$-ueNs%upnR@^l>PF)8v@J)Jf9ePr-yi_K>B>{gnX3vgjdxZ&cCx0PN^Z z>C>{5eJwGX?0vpBn9l9GKU=2feMfPp&)+(K>BUWiuKiHBO*)kQllIAjy+Bq`r_S%s z(9q?Ey~zIT9aMH)uIzY^o}B9I*JP~41Bo&=TiAE^h5OCpn1yDiv2Z~1ucSEt4GJ3( zem~Y(71xv|Xp*omuA*{$MlRsnl25~5oY8!?iE(8Da~7|ectzgVGuN^^tW@! z|L!U3!rmk#%?5(%3$NAoY--Rcy%rEFIOh=A9^}q ztAH06ehuY|Worc72UYM?~5>;=Kt%#u{#Ps?NeVf9a&B)ri8MZu4dCrRVt6)xF;6 zH*}_b#;P`M7!D^qPZO~jsw1*7Q9geE#B6Tv(!bqPx^*US5TnUWv!y?q^<^cPDtw znXT?(*!bAkO8molAx zT_UlNFdubqNm=EgaBY6?f=zGeYQ38}B`&2~+{hAF^8O2!aBVGeaH&0)oH?B1o2;8FGeENs==|9+1p1LuQCG zd-(2u|69AYTebJ!+O4&yvUqvpIj8${KmBw!)ETz>-rqT$p2foCWcYaXP;rna`^&Rr z(?x$9Tb=D2t#<=YI7G6P0$TFcuyt)!+Wysbupb(@Cx+u6_1%rCd+ay+T@BtrR9& z*}5UWwff&U-547vu}GNgscxK)I2DEqpJ`2=?Di3}q?-k>@yX>=KJm z*CXGCdaKzq)5G(#7;5C-xPP;1&WR2k;7Fd(6@*T#)~h=fUW>f(E%_qv>__u8?Dd+o z!qpXJvk1q+Lx+^-@|74bG&?9|6Be(#%;rF&^=(B#D8fM?HwU{kGVr^=JKy8qS4jOQ z30{qqJ1CpTQ-e)$nSpCkFPV|B3+w(4{KHJIRH9w-<|jT;uK3#8bP)m;~q$LTwa&TiGX z&+W|LOUSz%>V8*B0B0L-V#^LjVR?Uiee#2zn75)Ksy*07e}H>{3rgC2N^`Z9Gtiwz z>fTniRYpbXlkL@qWBPM9B56dYH6uvvPD?rG+SsbNpd*TLJY8wpM|(F5cH^}7Qnudu zPUxfj{|w&Fy200Q$m+GwdnFiM3fC@y`?%P0VqOA$_tIOwvg*w^lknv1 zmY^_}+(h2G)Tzt(tTmDHQgd;-Pi^EE)t=~ix7CmHok zUjOjHyPLK>q;gTmNB-_~mLzMC-}%KiR-yb>|`UWdC z9H@Fl5&8K8yv}Z*vm^HV+tAv|R-c8CsB00^zPVIlI+GJ(gjAAqh0I(F>o9DgkG`~f zRL8pnW9RrP3H@l zuG8hUc25|~ZhnaHkpP$yi;7ik>~?>XwU)x&$vj2dYqolz7-_ZpYdCAH!s(Q8%Nbs1 zRD$VE_H*yene{m7@NP5Z^Wjhi8iq3t{cESF=a0GDg6FVmv3s$@7O8~OJ%~2vpQ$98 z({HCz`nT5FtMGqos{LfKK1J`}NerCseh}A;(~}{K&;E^MbvdIcZ+|}|t}?6&fpgRx zn?{UN3BUa_z){H_tUWmP^Zqr!y!*epKJwoRZr?fnWysw7Z#?*a<-*5O2iA*Ke%{1O zZ+5$qr9X@$^lk8KHksQi1LDwPgVrC}K(7xet32N9q?35KY@jeXLR$`MS943%z-Nv?F zBJyZXHuGfNl+~}+t8Uk3WEXbBg`t6F=QNjs>GtnXLVj>QqkjC)lxv$ks(x<~77=k9VQ4!UW8{)HhbgtV3#BM`;Hme-|vL_>(?j~U(yQWVB_%~u07V+B@$_- z;xgc>uAV4`tMiiW*>d{U;SA}KBZtEs- zj`dqub}L^IFhCLQtc_?(m95ZTX;Dz*2^`&?IUxoF1%aNaJ16Du&z8!P=TA%-dP9SQ z@8Kg-^70Uq9-ZYVnG~7bP+?iJ=f;osRmZ!%++So!z&uWPu#!I89=D@`>PjZXr1uSkmycA<#Ogb`;6!p`T~3(VI{p#hsFd}BHO^PrJ0m#p zAKJa{KhaL1P$=VU412_L)vIN}f4Vey=Mv4>ud2sO@a^v2=_#6x$&&h8{7Ckm*@!bQ zmOWwXF;*N&Ne(%iW}k9J-O%N!VSs|PXDC$_9ptgn@1+N{FO=1+(Efgn$E(bPdJ$3{ z^E#Mmal+6hw)?vmhLyY{>`RLKoUdCbb5gKfRKIwb;7|>-T7E_6H9M9KbC`SxPJOK| zv5iN_zRl^Y(_3s-Vn4(QRxPZLj#ZbY<$;0gut1aNMzwlC6==z#JQXst1a_ zrXC4$lt^#mOuX!g*WjWc@C!BlN#RHA$LMY08s7t9e)%`J5qk{zz;Uj(W@8b3vJL9A zUZAG7{dY#FfeHZw!J<>hE~_9gf|+nT$|tG6NllNY*{|iyqrANr=-a z>=dfm2+1#EMvNQCb{(#S!yT=&f&j6UbOC?vk{#kHtUW(NlQtt|Pbh)0w`n|Yz+_mF(T3O6mS)PmD`e@);FF0yc3KJWV2!ENq zLglzQ14D26Q$WUVT2oxe3TWOx^j0_Us#(cT0WsMLJVWFRsCQZlFe*9B=>4B#Ga*BKVR6ABgyP^e!JR!q=GsXLr3#eYTS>&h?h-?~$LLhO6y* zv1XvQ2a*X^V%wWM6g}_S!?x};+S$>0ax@@M3Qx++w1Adc;3`A5LZat0xM<>e2Yk5` z_5t2Vb(Luug(Qqwsh(KY_AW^kT%uv}K(}uZW%frGTt5FH${sJvW;^E`O4nLtAe4Q8 zHk5D1dmwk(tw@);*?Sj#d^ScBFu!GuY4ASs`XU^5j_U<~Ln8Lc}nRMj`6x6*E|w&J90TeIbl zy*-}DjB9;0T@ty{(9#Mv8&;aK#P_fAcu-E_Ymt$36&3DOXhVJNdF0HlcOhUd+~wv5 zU~#YP7;^9Sqepf<^^`!~l&#~6#7-i2lcgL(!@_d&@*YgN$;oee`_WJ|Wn1ee=*TBt zyvUhpF^&?YzI2K8ijSL{wt_-t@WaI1dwBIGu1l9Lp&ALTwq(y?Q?l=z)mOAHKCG(z zqoh}dGIufvPT^=Q@&O{mFkqUGw9@PfQ8Ylkh8 zo=zNGR~YIrwf|PVyTR|Tkg>^RHEG&Ah5TMgjL$EA(1ACPyc<@Qo5^NuyZjmMnsd!w zhrhlycphWj?O5W>473k@d^zo+(`Cj-Z#M@lXw=nNq%8G&{;9664haca;MeVr6X`WJ zWshp4c+WGgSMR!1B%&^h-xh9sv_Jgu>|-FP_Qv>wSfgoAvy|xu(6K-uZ+yOP|22II zVSp14LUVO%A^BH{!hZYYr=GrFM6li6&@;iIb$cfbuy~ZP;J74!{boE)bm~F7;fRr zB+;pMYxlWNr4h&ykA#GZoRj3b6J;oGde2AOh+P1~<31-sKi$4I6xg ztHk>d874hXDoDbm&dwzlH#ZZi?503%CsW5p4ILc?4NWbr<1p$=)ZBhDKzi{sT6BgE zj!%At+{gzp;!AXZ5C>5D;aF33TQek+2}pL^k_C*1$x1=luGtM)!muq_Jg$6b==X0Z zQu~$!6m<1;o`Yp6T*?1R1GAsj)X?x648!xQSI?N(cNQp`TPH>kJe9l@;>mVf@Q7iu&qh zv%_w|GZ4wrJyJ&F@`r5`&&AIh_u8^k{AQ7d$Ak3tFx;HFjB5wu(q#aJM~n1$9k*Mt zTsfldedPQLrFHAQ+B^XH>hV23!ZjNo4rIGQV}Sx?_C_hER~{mGN76*$$>C1&Nz=cU z%webST}~;5d0`}4G#`nsDa-0T+N|poYAkj>LY^E&Msm1tC%U)JdaUi$5juNTWr0D2 zK3?Ofnm7S8)QWmpf6$g?y>WN-i(}S{gSB+PMMF%JU&@>KY~%C&kq1?#>TK?7_hm^B zt(JMM;*!f{LAua7`mY$n`b^Pk@YrCiR@_pSaX z7hrJ;#Mc(ky+Zo?+p}y9i#S}Ze`g^g*?Bxy)|a?@v|cZ>ke@3kkz6>6Js)<}TBJK3 zdx#y0-VG}!IT@`I+p_8(i2qu7DXbC^T$bm!o<|p{1k*7Z;xu>qf`pa|@S%{x+}5on zymIFDRSP-~$NG%D77Y3;Q4z|?i8wc0s}{Vw_X2Bu3j;wIa$7sP;8zX4vZT?^8@adX z12#526W5dE21;OZ#{-j&lJXP4!C#x1Y0Sk~Tj#FVZ>rd+Y*EG<^*ddRwtOVI*$_z7Nd*|TVWDpT(K)%rQYk=#@y z$7GjAu2_-Km?4hZ9n>ic;@)sn!5`N3gkel=%f=k)a3)PVXErrs zun+Qid-i94?#aN1ZcApV#eGxVBEu3F_ol%Gbb(QwLxc5E1#T;bT)NdvuUIv2ej5q| zw9aNX-c+~X*k-jHY?gP^;d-F#FLc{K(Rpqc1ABOC`PQvh>&SEY?GONcslWL6FLf`x z!B=DMNds8#RVF?wb40B7Ng)E`IC^Wf5m5P?ZP2z*kd*T&C16PsdZ&lEd^UlQN}O%!hECnEEk@h7}4mK_T`g z4{DlR&tJKB_43E_S8m0K>|{%9`!+Hde@ZXvsW6+%+GU3IBvsAGM#z3k8YWk3a9nj* zM579^R$sHhKFGt7KCv&Np0J1i!T>rUKp^5OdgPYg5Tr2HS~h7z?2};8x@hfkHvA_CLWF$S!qL)11x0E9F3tyBXjhJrqYgtP68>z8M z$+Y!$R{lI;oRS>lJ$tYuLt2(o?rBC!?<}XK+^ovjTFFjMoOg4Ws+5+Nw)gDmn;;(` z6m-}U_RHIFtY8elXWkSzG|(AFl1>(XeLP=eQ0GAUpg_8DL!n`|R@Pyx6yKs}V%uq~ z1mE;r{ixYSc`B6VSvW&}_t2s9IYd>|(!9h?8;gQIplG zW@i0>rUI*Lp})vP=;4BjR#foo7I?3Y-2}S{6bht1LT2Y@-yJy{TU(iwEq9lwvfA23 zS^AVMF;K)OMfT{ZE!p$1;i}A)akey8X9NsPuiVAs1kDQrA3(qQy*nSz3mR0`&#sKp zw_fgLWMXI6)72$)hJ15f>_(v+35VIg9)MkxwEpWzUDEyxMh&0wFH#XSU@-Vp>7TeU zk#t)Ol8j4U!FKzuea@DfVlBUR3UJK4JVVYJ@<&+V-V)G9d#Q4PzDH9;by*_zCGiyT zSPl-KcBK}7;B=a-`X473>AgJtOW)^z{yQMXWBF!Ujv-@k;RQa93#N|GsNV$#r>>+D zPLZ2?D~hy@)8Kw|bgJ8*YELniUl|AGKSTdLgu*aKm$`#utRKug{gQun{(ICIQ!nF~ z;{#rcmc<06hY_!WoKtU@Xz{!Y8dv_%)^4U*{+1W^W zN{AA*Ldn?LES*LwfeQSg9J03L6aX(sv_I?QR-!KuUcB*=c;<1Nf+0ly*!9L*q7KCA zVX1+Ap3OyfVTjwy3`#JWmaYL{o@k*V=4BI4r!GQRuUk{l(v|4%8`J#k?=&}K;X1|4 z9?Q$$5rl|`@bY1)Am8T(!6~zqmmuMmmr_1JuDt)hfwcX9G0guH+k0%~zO9N`Dl`P$ z*OgKUK>dlEM>4#-jf+Z-sj{)2&dYqx(4(fNVzafnFQV27uh=Pzz)~w?ujrv0{Tv!s zKuSn}hsjL{Fo`oLe-z3qC{Ba8V=6)6u|Hy^MGU*2-5jw0Uni(~Hki;!0OV#)?NMrB zrP(ce9Rqp!0$rSPA>Q{k?hNEzcoWm=6^K$+nqP}8W+BOWoR^68Yx~RD30iQ)JIVr) z2gtw&nFgORd?lba`$_Lk5M>YL2g?J`-@kt!S$EK?rOtSh^+ts>vTb7B|K*YktI^W& zc2DC@8nw5Dzn%?eP1Hd&i!_G%*7|LN%>+U8tp zj)=Gkv?&%~R}FiC2`5XSIL48bWBnxB)RfM-#;DQ@$?BT5}1YXmuOMuA;`+ zVzcz%`OB;i1gw_JdeQsi3G3e-h^fdaAJB%w4K#ssPsqzNuJe#U9R<|yGRKlj(~#&! z9UUE#r7flqQDX!J9va@%1t*AP6u(`Um&fOL5?Ee7L@{PS_Me&l6a432V>aHPDo(I+ zk>@Pr9Zb;>GA{3_udl4UBGTKi4=DF0n|05#)DnPUm2BOqrOt-f{ItXvxcTR`x-S5D z;i0p1UCz+k- z_V?3L3h2ZFkgvdD+mVnzpSizyZ60Kkytl(l)e}tg^@rYomsPuk3=E$1!+zAtR=r7= z&<#7>*LWT)bTbmr#>heJR!Fg3YPuTYd!*xZX~kykDP*OsJyc*IYhCM5VQA{P_qpV? zBAFEO{KR_pB@TvG0Q!>Rz9Yq3dXPISFLPqI)uCgnt>d?BN;Cw;b7F3qdNu|N9D8jl zrT8wgdzX8*4^&}K9?J_g&iIDrh5X6QPP_{ZjTJb0Gq4n2WE&4#=**>NAWc^He7c#) z4>d(@7s~Pl|5>Zq9$Bz2))(N8VD{PYa5qL^Frj}o%p_n5s=y{*S1(%{85$uiP74~9 z?;pjLJv6>a2aKMBP?Y>#X~Ayngxtk9p};Y?3*g~|mpVG~mzz8{W|c%$aah#)H#9j3 zsr)s@;N`Hl{LCgFmjX<513GD1Z^e%ymXpUSIr zb}&`(^7A_$KZL;)<$Gmn?*AaS zJzr;dc+=VW{{8;2o2|{w`|R47q2S=og^-XhJ4^4+_iJd7wX*Xh@86%@qJNnxqYL81p73Rq#k&-x_T4~#CFvx%5T0|NNZCO8eD>CEMB*p5 z72W{Dt<}D?^ib5l)@+cAPQqh8Sa-(>ATy^xD(U-u%N=LZr&d(<6Yb9TS3|ykzeQ%f zo9w-K@L63(%6)vtK(=SP(%$Fmhb$av@pL4VaK@9ie18;4azdPoYcY+wMf;97);!2QTOB7{%(M`~f_r?tgdU}xuwV(e?5vBKshpb63M>J{R z&uhitXTX=0m*3}zkd^?$e(I}qaJk8*qP4X-BQp!2xq9*j;4pykwo=Y+!gsc#ij3<3imITf2*$ZCt+3I`p z%sq4bH4Blnf@}8l`B120wT`xeo1F83yu5Fqw$C@Flp)V=DiMZ;!kiA7&4mm#8RN`< z{U{(T7Ql6)eWPgpy&AX%fk5m}ZQ#Pp;C`uAF{m2@(sp9dI} z&uPhP429iDC0rW6ar*S4&*x@%=p8mqu%^s}%kHw0_Y{1W5Ms6Rqtl~2UsTS#OJ}yG z_^Htx?CMFDczn8g!rjh{-&giF3^-X4RpFJD0H`1?CuNjsCdbSzbmy2qD5uZmC_*2R zdC3BTm6o46G7{u*2D=S#yT?v90;Gn$u-Rm_q!vw3&phYp$x!GG1F7t6p> z2{5?(+eTbKF`(sH*gegruyQA9=?B`xFCu?gl6`{4Q%uqwaxN=_o_wjmIdz&J47%${ zf<3mw`@nQWfP~nXd;%l3mZxLWfbO3zc?h*vRLn?b&9-P`jJt2H z(WO!zb}y7z`DA1XW1t&bg#}GTSwBXJii-{TK@3q@lHzEDqC(sG%qx<&r74`5sbn^)W?f*u5;`s1`X?L{t_({IuN)1yh;s0;Zm{rDma@Ns7y1ve z_>b9msLsr?esc74PdtBSM41vk;o6Jp0%p!)`PSAL{ z9f{Zuxle}7UXVXsF?D6Bu5$!C@$2d81;ont=8jF)-7&)pm0~ahbeA3i6EYpnef>%n z^D4oxvmPpn8VIgp=AE^#xD%Qd@k|q=$75lnyZfwZYV1E(^v~!>7F{HU_Ve2WYKdEE zMSK+vrN1QYV>MSDzN(hTXs%WYOlAuS(*?CGGjQ79zd4?_+LRcxz^PJ*qRHcU1El^% zBtu_KB!kmEj*i6|pPaju?K`IKR$=%9_Yv#5Xcu+((A7(s1GWYxX+;7ewMvYe_D(D; z41Ui(>@NBJy*M?)e-4bsf67gKDbMr-6{Eeg-2K?zy>{3{0<%=-ZI;a6uj90Ci^?=O zzS!V)lpotMxgYaM0WXosY#;JpL#paY?o(oqyuMQ!^XX`g8m-g~8|2 zY&G`l?2}(Fd#~TB=qp?+qA^$f-(7yom_wR;>(*of3;%>IO9V<&Ooy_>GAGApRp9ya z9hv*rhCs2w8Bcew&zdK2M4n<00C@gW{7>xih?Tz-pWZ`L!TtG)$ztoz4%rHZ4hyJ5 z?oOszwVuPfDC_F1vs)$U@i2+pI@K=!sSx%L{9|)z5a&^e4oLTp?yEhtScO!<<-pon zqp_udAg&OmjAq)`Vs?2J_FhhTcjQlJs{${mM)8And2F+Bu=admBD_qtLD#rk_${Rr z8_a_B4Db#d%*t4arvWlk%8^*8dov(SGs7vEH z_khoE`u+yi9A9@xB~N(fhwsY+`7Fq(=E9`69EXI zPhw0PYZ3P(C!M+S<~C5UPk4soa^ZOe!jAvEGP*b7V<23z9YGCYxwHS6s*l&2JDd6Q zlj`Sz_^la&H|=F)3%fyS~2XHe-t)VfAl zJ(D;B-4BI2g*o42`g=O=t;PJrK_E{7XYI#!zdGK$dHjq(TJ*?-$<*j-iCBMszi`8b z8^Eo+@?-W^raH@2pFaIPg_x9_OW@6q5j3iN+e5Y9=)7lOb$-BQ{yql?#Y_VZ(PGab zVGl`~0`7~o)8C7)w{!)Q$b(;U{^%Mf{Gr9+5~4K`wj=gNTs=iezgH+B;@9+}Gza9S zsDA7|N}e%lZY5m!{hguwYLmm}u_*k^@rt3yntGuIHQX!zz5mH!{mEgc+}s8$z=Z`gona-;?JMp(w<3v z?|YU{e;EHyD;y%p==UN-p3>X=m!t%G7-1&0o_LWRP4=iNGQON%gZfR+?jtTOs45R! z-~$YD(x2#e)nTkwRUU*fEmV7$%ZnF=^-N}+M#bzV-PXQ4BgspwP?uj`OBtD&u!q(2 zY1<+qJ|G}*vfZv_SY_+qv8aN(dGKDFmX4#fC`|2Q@de23c_3kuH*_|W`bB>-br1}3 zJI00j{g!1XxW+m*zkO4)D{b)=I4<>r)t7=GWD>>_$qy}C>Zt>Te=JYLkMeT|iyHS@ zLqb&OYCsT!4^48xZpE~H`Ew&O7yAN*B6cP2YFS&y<>j@7v&WN;tkzvr^{5SxAmdV%2#xE%4!t9~Ev2ExwSrgKQfY~48!|9Z>Ng{b zhhOA_U_3JaTv7?^Wf&Et1i>f}gU%$EJihAf)+=5R zBa_lQw;B9QzL})O4|q3FO}(XSk;vl{%b=#y_%pMZ{gpTC@x}h7I=Q>&HJ`9Y(?(C2 z18MyPq;;^bHB0$b(~O|jR;J_+6k6~)D=Q~d6qMzO!Hf`0fnV=hEr?i~;hV6LtOxg zIQuTQF{$o(T6!7?>7}K_PI^rUhNRVRj`-AOp@HynD9syci#~dET&|_=59oAP8o8Uy zG>`%>^WyjL#c$KK&USf1-qAwAV_Cr7OA&5e;#&o;e{Uj?^T|jkHhA>PVHRQE{d4v- z_(;%-Tv6-w zghHVoVQz-9Q%p0vpiTCjUjSA}de_fkc#Wmd6q^sXJpV9p(W%g+Y=nH<>d#DTXVBHo zOZ@zLo* zJy-sVV#@MY>FvI{FRVrQ1F8Q&l~_d%$GF+0@Izk#H#U(t?JatWm`?jH`Ke;AR$07e zWSjzNnC7a303EFf{PZvKtDuv-f_rej5-szE=SP zI&~FOgSlZtrRItbCYq{5N^s|=T!f|N-r8Ut2JmZr$8)*G1jw7&66=j*4V)pY8mtIV z)9orCu^`*tAKiJ8yfr+mXB;_J)@H?yUUCvo>}dvCPwi;0?#EiVg-%@6IDdcTNMRB2 z^S67U)f0}ZK=bnmDD#5gCqV!H2M9LlP&qeH`g(Ux3*Jy`;ql(W-GKIJaHhHBH2{S_ z|Ka{BBg}8h^bAz&!eD)IE_HLS=Rkp*sohpO?0%!_z{TaXW8+ z`#+o(m64grQ@^_*ezoJr(C3hF8Wq-ZO6(_T-=#{@hevx<{uzIpe;8yZ|sPbMCL#~;Wpp7pm@_l?MhNBwKw|%35c5v z*lbu6@|LC3;0={gdOa4TkFB)suNCq!BR&eD*P5pxknyv?2nw>i`$@Svb2uM9Qbn2& zyplCf>GY_nF!1Zer;xsO5Dj*FtN04BtGWHuNi>7wJ7@7+yYx6vu^{xg+_&SPQD~ya z>&|~veL?*m?C4AQ4jp=xy!S=5r1DTwyeWoEt3n<> zJAI>9u;Jj!DsTIWe_W}5-F$Xl#GKW93CjwQkNu(Zea?&*!$(Y0O~nGr`_4x{)x$ZO zr3TYY9PU#=-Xc$L97|_myIZT`)$q6f!zklE@Q|Z1Ro=Ugr?vivl>AI9C^W6=qObR# zL7sD?8(xN-{;BR%fU3VNLwW?O{hq8{WYWq%D_(hu%cz}hI%lcN%Dr`3TJB}QwC3!0 z^Av9_F+hHZoQ~7a0t#Qn8f9vG&L7*b_x0I;_TQEbdl%k$VI~1Ixi)+&(x_{CUAcLD zHQ$H=^7Qfl8BWBN*pu-RL80^`D(de`#O#IZ1rb^hrCY5LXWq*B=AVW9&;k*XQzY^K zkWKO5kMS0y5&#mGBGce~neqDqa&6a}pD$m`ajLP($rNs9yZpm0zopb}A-KA@mpN^2=@1SN=M=A~tEye9w|n!ZS(*EbO?^gMX}y z%abAsZ>G~S6^>L~T+bU6j-^Jj+HI22(ylhE5Q$8ocoD%e9N4wz>q4zJR z*QeS1`fCxNC5D!&k@#q1Yix1j#>dj57I~%aWOQ{w{Zp{PB%UohYHgOUo7#m}E;vrd zvk%SDK$cHk_i&s3{z!MBShBN9sq0Qj4YQadyhalAqr0+@R!oH*STlHM#{o`9#lJD>ueCsq(GtgW+LO=z~XAFFn7%URAP>>9C`8kx5* zHxvMt7mY^h!IZL^8ki#7VHn&7X9OZ$(7!LfuuJoaInKqjbm}T9DY1(92Akc2SK=?s zUpNbq6kP<&1QiLE@PfvMIrG;rhv`Mr2S1Q0#&y&qMObYO107AnO0ZToHi`*Twa1pc zsu-H7dzYJ*whw$|nhI3EH#ItK3LmMc@aO00p{8bvhhsT%cC>8;oG=XBbgLo z$0p0rmcLHom*FN_m_u%E;px)y>fCE=@Ma^WQbJfHPA5lb z)-0U5S9<$5sM2xtdOiJza0Ie;387@WPQ zq2AIZYF9vR1@zYVMl#WV>RvaKCVYuch?iN~M&2^wa{ll@v5`h}b3kE%bEUsh5yu z?X7HWFh`v_UNCak>UZN`O|n)523O3!ugONoHoc$gu*|Ug6O!5#6%%h{x_)3Y-i690 z-y<(sfJM_?1(eZrwHMs{X?^f=t%-?7T>QXJ3>~^gifYikp`acLr2#+8(^fKs2CJn%4(| zWG0a2^%$^703Sm1en~$lBN9nR{t7jON=j;K0Fq-fsAUBf#7Fh(!Oyhc$a|ot!4d=O zZ@>Ecmm=eiPX1%zLTq`3k|O5)*a_&oOHcpzXt!Pa`hpmuZD@Qq!P{e9@?M?eQ(q$^ zyBCHkE?|)~jo*ZT@Yw0vF$pv)Ff1cA-*3yx&Ri4Z;X~yllDA7KPLAH};xnxcpJ;4G zE#x!#8}wppr1&2E9i3#!AJ&B9Iws09HzNpj#Orq@=v{#()$UB=L|?xV%VM6u1ya_I z-t67-vvZI`RXSixT)Gci?dxSseSPgv$&4O#Q*)aP*YWTQkB{U`u+XVvkE4dG^ioWu zkC&)m_G3cO?)C6ozn08x2BGer)xkQes|u0hl3+O`31aEMs#p529nt;)WBHA=wAUXs zsl<(>AJks{vwE~WaKmEZTcDYKVGPhF^OxD~y&jq5sxpF%YZuF?QLvbSU?3!Qh*SEs zrbM|Nxfq(F&M&~nK$rim*9!7@c@sqk3DERX(6Nkg;BD{O3QeP>4)?GDb%?8+ZqBOeqywaANiglJbC0^zQVGH@8}AcM~v$SeDiB z)9iBooHJlk{(n0S@&8!}(LipBV&+dYEjy+P(VNkHE}?tX0~4#KYd=|E2v)tA)jz{F zCtfQdFA*CXJAanrhfxU6@9CA5grk&2(2&%0dUc9Xl4ruEL;zmHr7wM1_gDD#dCC*o z)Dtg9#@h~4dmV?Z)D?444VXRZI*(*>?$YdROq`E^D|Ldf4!P?YuBuK@gjlX)N-;wV zm?j&$Yt!~X>wG#2K^im^O&hbbvBgBK+3g2Ttpt3M2wb|{0Y`f+B{%4Xa`H%NY8Y5l z7@k4VzxoBhsK}1ZZIK|zrZ3a?z>@NS?&=auJKf-Rn6kB^6PP&4Y|M3}BrHTM%) z`Jl`>mN<^&-6p|Pdga5Q)BCM=5eVMu(}CSVc4oK0n%uz+@;`vqR{~1#;0SBoUW5?P zXF$A?5~FX?(Gff_j-A*V=WQ7I`Kh6RGjew?BKbYwfO6Q_;i(?`sE%i-0{&& z_JOhMC|>ToWo9N9r~(*R+6k-*?18M@14EPIUwu!;8bs$ZLP+&RY`0B0_cK z0l#9bUKF`RXy{(+xf5}D{PEl!MQ_FEbz(zU7f+M_OnKB?iY!0pF4R2dZ(TvGhfx%r zwH9JXAB#ejL%xKA2l*EgdL7f=3=`C9vTmpXW_Uc1Q~$-Mqdx{L@Jm-+yAm6I&TU@lAYeGsI}0SJ^KESskT>IbM_+Z< z($uDMb8>YV6wAv$Oj#Gh{$L+S?ngZf{1^Z+-H{nta5*xS99r!rQrWj;c{{Qf6!DbEmbG4}@+LQ^rP=tCL&+MOYNSA zRJ_b+Rw!-0Bhfi=mgAFd;QX^6s$(SonU4zF3&$pn4DD8ney09O>Pkd{tew3z6XP0r zby|!RVUlJWpXH9I#5VfKCc!?@Ml7aWrrG)QG&Lz!mUx8x`TIz`DBmbET5^6ux5eGI;bKTP_Woj73 z&{CWQ)N<#A!{bLd`sKPaqwVblBTtGWW!>!URe7%S)hA?G3wEnUzFnJP=&S^l`tkOK znYAUt6%AURcQ~0QY(Q2aa>vD(UbEw48^TYxSZMDZY25p2U!=G8@++3mFl}o~uDToQ zJ9}|tidn`*P{C#KI82zeLp zWR#IpCN-Ys{{Hl2d=CE+-C6jB_UVw0J?o=)2r3N2yGxyKO5H0eI0^swcoi03Qz5j4 z9v;?i_NN}Vzvhf)yKr5|y?fEW@8z>Q0cN+J2HU|9%Z~%ip4|~L{a)|s4@EvF-BWxC zs<~4kI+539h8`t!4hJ+X`0xi@-D;ekCS`Dojnex?Dos30kY1r3T@(xzqC@uLHOA*IC}dM+p8S=rW*jxKiA zUJJ9w^li@J41*9y&Fu~%d;n3dQ1Js*pJ)NE>R|??4BpIP@|syar?DC zj)?o~S$7#&d4QmJX+q_0oz%p-Y@)1vp-dko5zexA+!zVGOz{u@!ij|-*vCM&c%!iM>O+FhNSP{DX zxB7=u$U7Wu9@pB3mZ}_%;@GzN_VRbf(yz_w+hueZ8e^;7%<{_8RWC7KcxxM(L-r1! zRmp8x_y&H8diFYRay8BNbtR2)(9oJt4PGQ2m=UhtoWvzT@o!DrwztC}KWc$y|0F1c z^7TDw>Q8`C1P4%SmJLB%;-@&^bsWa0mG8_>;dQx7Ln_YO=Bdhj+t3JF;_Oi&=r0OZk@V zWtA)CxdF>-Z+%MYvT^S}m(Y>5@JkHI#z!+QTnTVEw(ntKVTIT1N|fg6ugTxL{DU#} zg-Yf{xRHP+QPJ6!g{7c)Eb<0S2}{tlmww|C+u*_a+3ky>~;Zj}p2MXCt;!xzsL$F)y3 zN=e;^J@9h4o6s$7>@7pwU&%d`lvOf{kaz5vWCh~GxZ0Z{(z9O7IPnGPzrEMI|7YVd zI@(;=-rHYaJ*yIw0ncxD<(NyC3!jruOAtX=`l&5?iIe3qXZ5m#83RkU|F+z$YLpyx zZ(Hhr9@_SmIX##mU$QKKwV>$hoOic6@{S3w{L{*JyX@2bs0FWM#E;%SHZza^Pkt;= zFUP``8(X)ydkQ|wm2#N+v5)(~6R%}uY`@Q!lx9aj6ICmuXep}DRY|f|O!QODaXQoHRW<%kZZ`GF00iJyJtfV|Qw>L;> zuAlp&*~Q%Fb?SHTX37`1nsH;lT*2~Lq0Nd#>$YtZ-yz9evrX^LseJY~oijZcY$l3* zNCS?bI=BKya4V9WfIEg581jL`u|RMSIK%=3dmv+#6+l%$@BujY4+IZ@TWEmb0B~Un zkZJ&}xBvo5G&*o7P6@cG$H=hC{i75I$Y9{1=1+HkMUI8;VPgW>+#+zO@J-fq28Phb zH~hAGGZZ)h9jDl`L8qJhBg4HwugJ1>w_F)476P|g2{`RomwYp9>uQDutqnS-OLuGo zyY`2lS+bEl=YieF3g6^hTT>>=;HP(q>4UBD-tKds}i_)sr-zsIWxcDVY zoMDd6&7RE6oeKncSV~s1rfhx9*l;SyN#lB9&hbsfJ`PL)VwsE`o7lMXhQN0G&?9;OXk;vd$@?2>=GQpIQI_ literal 0 HcmV?d00001 diff --git a/documentation/operators/src/images/wallet-screenshots/node_settings.png b/documentation/operators/src/images/wallet-screenshots/node_settings.png new file mode 100644 index 0000000000000000000000000000000000000000..69568ce6a540cbc1d6c4e0fea6d286451ebc189b GIT binary patch literal 55864 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%0Arw=^F)sf3iGu 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 z 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

l-5 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

ly}MeV>=*ZTQ0urb=*FZRXrf1u%f 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 literal 0 HcmV?d00001 diff --git a/documentation/operators/src/introduction.md b/documentation/operators/src/introduction.md new file mode 100644 index 0000000000..f0344db71a --- /dev/null +++ b/documentation/operators/src/introduction.md @@ -0,0 +1,34 @@ +# Introduction + +This is Nym's operators guide, containing information and setup tutorials about the various pieces of Nym mixnet infrastructure nodes, such as mix node, gateway and network requester. + +If you are new to Nym and want to learn about the mixnet, how to integrate with the network, developer tutorials and quickstart guides, check out the [Developer Portal](https://nymtech.net/developers/). + +If you want to go deeper into Nym architecture and various software such as different, application clients, existing applications like the desktop wallet, mixnet explorer or SDKs, visit [Nym's technical documentation](https://nymtech.net/docs/). + +If you are looking for information on grants and beta-release Nym apps, check out the [Shipyard](https://shipyard.nymtech.net) site. + + +## Popular pages + + + +**Network Architecture:** +* [Network Overview](https://nymtech.net/docs/architecture/network-overview.html) +* [Mixnet Traffic Flow](https://nymtech.net/docs/architecture/traffic-flow.html) + +**Node setup and usage guides:** +* [Mix nodes](./nodes/mix-node-setup.md) +* [Gateways](./nodes/gateway-setup.md) +* [Network requesters](./nodes/network-requester-setup.md) + + +**Client setup and usage guides:** +* [Websocket client](https://nymtech.net/docs/clients/websocket-client.html) +* [Socks5 client](https://nymtech.net/docs/clients/socks5-client.html) +* [Webassembly client](https://nymtech.net/docs/clients/webassembly-client.html) + +**SDK guides:** +* [Typescript SDK](https://nymtech.net/docs/sdk/typescript.html) +* [Rust SDK](https://nymtech.net/docs/sdk/rust.html) + diff --git a/documentation/operators/src/nodes/mix-node-setup.md b/documentation/operators/src/nodes/mix-node-setup.md new file mode 100644 index 0000000000..a24ec587ad --- /dev/null +++ b/documentation/operators/src/nodes/mix-node-setup.md @@ -0,0 +1,700 @@ + + +# Mix Nodes + +> The Nym mix node binary was built in the [building nym](https://nymtech.net/docs/binaries/building-nym.html) section. If you haven't yet built Nym and want to run the code, go there first. + +> Any syntax in `<>` brackets is a user's unique variable. Exchange with a corresponding name without the `<>` brackets. + +## Current version +``` + +``` + +The `nym-mixnode` binary is currently one point version ahead of the rest of the platform binaries due to a patch applied between releases. + +## Preliminary steps + +There are a couple of steps that need completing before starting to set up your mix node: + +- preparing your [wallet](https://nymtech.net/docs/wallet/desktop-wallet.html) +- requisitioning a VPS (Virtual Private Server) + +### Wallet preparation + +#### Mainnet + +Before you initialise and run your mixnode, head to our [website](https://nymtech.net/download/) and download the Nym wallet for your operating system. If pre-compiled binaries for your operating system aren't available, you can build the wallet yourself with instructions [here](https://nymtech.net/docs/wallet/desktop-wallet.html). + +If you don't already have one, please create a Nym address using the wallet, and fund it with tokens. The minimum amount required to bond a mixnode is 100 `NYM`, but make sure you have a bit more to account for gas costs. + +`NYM` can be purchased via Bity from the wallet itself with BTC or fiat, and is currently present on several [exchanges](https://www.coingecko.com/en/coins/nym#markets). + +> Remember that you can **only** use Cosmos `NYM` tokens to bond your mixnode. You **cannot** use ERC20 representations of `NYM` to run a node. + +#### Sandbox testnet + +Make sure to download a wallet and create an account as outlined above. Then head to our [token faucet](https://faucet.nymtech.net/) and get some tokens to use to bond it. + +### VPS Hardware Specs + +You will need to rent a VPS to run your mix node on. One key reason for this is that your node **must be able to send TCP data using both IPv4 and IPv6** (as other nodes you talk to may use either protocol). + +For the moment, we haven't put a great amount of effort into optimizing concurrency to increase throughput, so don't bother provisioning a beastly server with multiple cores. This will change when we get a chance to start doing performance optimizations in a more serious way. Sphinx packet decryption is CPU-bound, so once we optimize, more fast cores will be better. + +For now, see the below rough specs: + +- Processors: 2 cores are fine. Get the fastest CPUs you can afford. +- RAM: Memory requirements are very low - typically a mix node may use only a few hundred MB of RAM. +- Disks: The mixnodes require no disk space beyond a few bytes for the configuration files. + +## Mix node setup + +Now that you have built the codebase, set up your wallet, and have a VPS with the `nym-mixnode` binary, you can set up your mix node with the instructions below. + +To begin, move to `/taget/release` directory from which you run the node commands: + +``` +cd target/release +``` + +### Viewing command help + +You can check that your binaries are properly compiled with: + +``` +./nym-mixnode --help +``` + +Which should return a list of all available commands. + +~~~admonish example collapsible=true title="Console output" +``` + +``` +~~~ + +You can also check the various arguments required for individual commands with: + +``` +./nym-mixnode --help +``` + +### Initialising your mix node + +To check available configuration options for initializing your node use: + +``` +./nym-mixnode init --help +``` + +~~~admonish example collapsible=true title="Console output" +``` + +``` +~~~ + +Initalise your mixnode with the following command, replacing the value of `--id` with the moniker you wish to give your mixnode, and the `--wallet-address` with the Nym address you created earlier. Your `--host` must be publicly routable on the internet in order to mix packets, and can be either an Ipv4 or IPv6 address. The `$(curl ifconfig.me)` command returns your IP automatically using an external service. If you enter your IP address manually, enter it **without** any port information. + +``` +./nym-mixnode init --id --host $(curl ifconfig.me) --wallet-address +``` + + +~~~admonish example collapsible=true title="Console output" +``` +.nym-mixnode init --id --host $(curl ifconfig.me) --wallet-address + + +Initialising mixnode ... +Saved mixnet identity and sphinx keypairs + 2023-06-04T08:20:32.862Z INFO nym_config > Configuration file will be saved to "/home//.nym/mixnodes//config/config.toml" +Saved configuration file to "/home//.nym/mixnodes//config/config.toml" +Mixnode configuration completed. + + _ __ _ _ _ __ ___ + | '_ \| | | | '_ \ _ \ + | | | | |_| | | | | | | + |_| |_|\__, |_| |_| |_| + |___/ + + (nym-mixnode - version {{mix_node_release_version}}) + + +Identity Key: DhmUYedPZvhP9MMwXdNpPaqCxxTQgjAg78s2nqtTTiNF","version":"{{mix_node_release_version}}"},"cost_params +Sphinx Key: CfZSy1jRfrfiVi9JYexjFWPqWkKoY72t7NdpWaq37K8Z +Host: 62.240.134.189 (bind address: 62.240.134.189) +Version: {{mix_node_release_version}} +Mix Port: 1789, Verloc port: 1790, Http Port: 8000 +``` +~~~ + +> The `init` command will refuse to destroy existing mix node keys. + +During the `init` process you will have the option to change the `http_api`, `verloc` and `mixnode` ports from their default settings. If you wish to change these in the future you can edit their values in the `config.toml` file created by the initialization process, which is located at `~/.nym/mixnodes//`. + +### Bonding your mix node + +```admonish caution +From `v1.1.3`, if you unbond your mixnode that means you are leaving the mixnet and you will lose all your delegations (permanently). You can join again with the same identity key, however, you will start with **no delegations**. +``` + +#### Bond via the Desktop wallet (recommended) + +You can bond your mix node via the Desktop wallet. + +* Open your wallet, and head to the `Bond` page, then select the node type `Mixnode` and input your node details. Press `Next`. + +* Enter the `Amount`, `Operating cost` and `Profit margin` and press `Next`. + +* You will be asked to run a the `sign` command with your `gateway` - copy and paste the long signature as the value of `--contract-msg` and run it. + +``` +./nym-mixnode sign --id --contract-msg +``` + +It will look something like this: + +~~~admonish example collapsible=true title="Console output" +``` +./nym-mixnode sign --id upgrade_test --contract-msg 22Z9wt4PyiBCbMiErxj5bBa4VCCFsjNawZ1KnLyMeV9pMUQGyksRVANbXHjWndMUaXNRnAuEVJW6UCxpRJwZe788hDt4sicsrv7iAXRajEq19cWPVybbUqgeo76wbXbCbRdg1FvVKgYZGZZp8D72p5zWhKSBRD44qgCrqzfV1SkiFEhsvcLUvZATdLRocAUL75KmWivyRiQjCE1XYEWyRH9yvRYn4TymWwrKVDtEB63zhHjATN4QEi2E5qSrSbBcmmqatXsKakbgSbQoLsYygcHx7tkwbQ2HDYzeiKP1t16Rhcjn6Ftc2FuXUNnTcibk2LQ1hiqu3FAq31bHUbzn2wiaPfm4RgqTwGM4eqnjBofwR3251wQSxbYwKUYwGsrkweRcoPuEaovApR9R19oJ7GVG5BrKmFwZWX3XFVuECe8vt1x9MY7DbQ3xhAapsHhThUmzN6JPPU4qbQ3PdMt3YVWy6oRhap97ma2dPMBaidebfgLJizpRU3Yu7mtb6E8vgi5Xnehrgtd35gitoJqJUY5sB1p6TDPd6vk3MVU1zqusrke7Lvrud4xKfCLqp672Bj9eGb2wPwow643CpHuMkhigfSWsv9jDq13d75EGTEiprC2UmWTzCJWHrDH7ka68DZJ5XXAW67DBewu7KUm1jrJkNs55vS83SWwm5RjzQLVhscdtCH1Bamec6uZoFBNVzjs21o7ax2WHDghJpGMxFi6dmdMCZpqn618t4 + + _ __ _ _ _ __ ___ + | '_ \| | | | '_ \ _ \ + | | | | |_| | | | | | | + |_| |_|\__, |_| |_| |_| + |___/ + + (nym-mixnode - version {{mix_node_release_version}}) + + +>>> attempting to sign 22Z9wt4PyiBCbMiErxj5bBa4VCCFsjNawZ1KnLyMeV9pMUQGyksRVANbXHjWndMUaXNRnAuEVJW6UCxpRJwZe788hDt4sicsrv7iAXRajEq19cWPVybbUqgeo76wbXbCbRdg1FvVKgYZGZZp8D72p5zWhKSBRD44qgCrqzfV1SkiFEhsvcLUvZATdLRocAUL75KmWivyRiQjCE1XYEWyRH9yvRYn4TymWwrKVDtEB63zhHjATN4QEi2E5qSrSbBcmmqatXsKakbgSbQoLsYygcHx7tkwbQ2HDYzeiKP1t16Rhcjn6Ftc2FuXUNnTcibk2LQ1hiqu3FAq31bHUbzn2wiaPfm4RgqTwGM4eqnjBofwR3251wQSxbYwKUYwGsrkweRcoPuEaovApR9R19oJ7GVG5BrKmFwZWX3XFVuECe8vt1x9MY7DbQ3xhAapsHhThUmzN6JPPU4qbQ3PdMt3YVWy6oRhap97ma2dPMBaidebfgLJizpRU3Yu7mtb6E8vgi5Xnehrgtd35gitoJqJUY5sB1p6TDPd6vk3MVU1zqusrke7Lvrud4xKfCLqp672Bj9eGb2wPwow643CpHuMkhigfSWsv9jDq13d75EGTEiprC2UmWTzCJWHrDH7ka68DZJ5XXAW67DBewu7KUm1jrJkNs55vS83SWwm5RjzQLVhscdtCH1Bamec6uZoFBNVzjs21o7ax2WHDghJpGMxFi6dmdMCZpqn618t4 +>>> decoding the message... +>>> message to sign: {"nonce":0,"algorithm":"ed25519","message_type":"mixnode-bonding","content":{"sender":"n1eufxdlgt0puwrwptgjfqne8pj4nhy2u5ft62uq","proxy":null,"funds":[{"denom":"unym","amount":"100000000"}],"data":{"mix_node":{"host":"62.240.134.189","mix_port":1789,"verloc_port":1790,"http_api_port":8000,"sphinx_key":"CfZSy1jRfrfiVi9JYexjFWPqWkKoY72t7NdpWaq37K8Z","identity_key":"DhmUYedPZvhP9MMwXdNpPaqCxxTQgjAg78s2nqtTTiNF","version":"1.1.14"},"cost_params":{"profit_margin_percent":"0.1","interval_operating_cost":{"denom":"unym","amount":"40000000"}}}}} +``` +~~~ + +* Copy the resulting signature: + +``` +>>> The base58-encoded signature is: +2GbKcZVKFdpi3sR9xoJWzwPuGdj3bvd7yDtDYVoKfbTWdpjqAeU8KS5bSftD5giVLJC3gZiCg2kmEjNG5jkdjKUt +``` + +* And paste it into the wallet nodal, press `Next` and confirm the transaction. + +![Paste Signature](../images/wallet-screenshots/wallet-sign.png) + +* Your node will now be bonded and ready to mix at the beginning of the next epoch (at most 1 hour). + +> You are asked to `sign` a transaction on bonding so that the mixnet smart contract is able to map your nym address to your node. This allows us to create a nonce for each account and defend against replay attacks. + +#### Bond via the CLI (power users) +If you want to bond your mix node via the CLI, then check out the [relevant section in the Nym CLI](https://nymtech.net/docs/tools/nym-cli.html#bond-a-mix-node) docs. + +### Running your mix node + +Now you've bonded your mix node, run it with: + +``` +./nym-mixnode run --id +``` + +If everything worked, you'll see your node running on the either the [Sandbox testnet network explorer](https://sandbox-explorer.nymtech.net) or the [mainnet network explorer](https://explorer.nymtech.net), depending on which network you're running. + +Note that your node's public identity key is displayed during startup, you can use it to identify your node in the list. + + + +Have a look at the saved configuration files in `$HOME/.nym/mixnodes/` to see more configuration options. + +### Describe your mix node (optional) + +In order to easily identify your node via human-readable information later on in the development of the testnet when delegated staking is implemented, you can `describe` your mixnode with the following command: + +``` +./nym-mixnode describe --id +``` + +> Remember to restart your mix node process in order for the new description to be propagated. + +### Upgrading your mix node + +Upgrading your node is a two-step process: +* Updating the binary and `~/.nym/mixnodes//config.toml` on your VPS +* Updating the node information in the [mixnet smart contract](../nyx/mixnet-contract.md). **This is the information that is present on the [mixnet explorer](https://explorer.nymtech.net)**. + +#### Step 1: Upgrading your binary +Follow these steps to upgrade your mix node binary and update its config file: +* pause your mix node process. +* replace the existing binary with the newest binary (which you can either [compile yourself](https://nymtech.net/docs/binaries/building-nym.html) or grab from our [releases page](https://github.com/nymtech/nym/releases)). +* re-run `init` with the same values as you used initially. **This will just update the config file, it will not overwrite existing keys**. +* restart your mix node process with the new binary. + +#### Step 2: Updating your node information in the smart contract +Follow these steps to update the information about your mix node which is publically avaliable from the [Nym API](https://validator.nymtech.net/api/swagger/index.html) and information displayed on the [mixnet explorer](https://explorer.nymtech.net). + +You can either do this graphically via the Desktop Wallet, or the CLI. + +#### Updating node information via the Desktop Wallet +* Navigate to the `Bonding` page and click the `Node Settings` link in the top right corner: +\ +![Bonding page](../images/wallet-screenshots/bonding.png) + +* Update the fields in the `Node Settings` page and click `Submit changes to the blockchain`. +\ +![Node Settings Page](../images/wallet-screenshots/node_settings.png) + +#### Updating node information via the CLI +If you want to bond your mix node via the CLI, then check out the [relevant section in the Nym CLI](../../documentation/docs/src/tools/nym-cli.md#upgrade-a-mix-node) docs. + +### Displaying mix node information + +You can always check the details of your mix node with the `node-details` command: + +``` +./nym-mixnode node-details --id +``` + +## VPS Setup and Automation +### Configure your firewall +The following commands will allow you to set up a firewall using `ufw`. + +``` +# check if you have ufw installed +ufw version + +# if it is not installed, install with +sudo apt install ufw -y + +# enable ufw +sudo ufw enable + +# check the status of the firewall +sudo ufw status +``` + +Finally open your mix node's p2p port, as well as ports for ssh and ports `8000` and `1790` for verloc and measurement pings: + +``` +sudo ufw allow 1789,1790,8000,22/tcp + +# check the status of the firewall +sudo ufw status +``` + +For more information about your mix node's port configuration, check the [mix node port reference table](./mix-node-setup.md#mixnode-port-reference) below. + +### Automating your mix node with tmux and systemd + +It's useful to have the mix node automatically start at system boot time. + +#### tmux + +One way is to use `tmux` shell on top of your current VPS terminal. Tmux is a terminal multiplexer, it allows you to create several terminal windows and panes from a single terminal. Processes started in `tmux` keep running after closing the terminal as long as the given `tmux` window was not terminated. + +Use the following command to get `tmux`. + +Platform|Install Command +---|--- +Arch Linux|`pacman -S tmux` +Debian or Ubuntu|`apt install tmux` +Fedora|`dnf install tmux` +RHEL or CentOS|`yum install tmux` +macOS (using Homebrew|`brew install tmux` +macOS (using MacPorts)|`port install tmux` +openSUSE|`zypper install tmux` + +In case it didn't work for your distribution, see how to build `tmux` from [version control](https://github.com/tmux/tmux#from-version-control). + +**Running tmux** + +No when you installed tmux on your VPS, let's run a mixnode on tmux, which allows you to detach your terminal and let the mixnode run on its own on the VPS. + +* Pause your mixnode +* Start tmux with the command +``` +tmux +``` +* The terminal should stay in the same directory, just the layout changed into tmux default layout. +* Start the mixnode again with a command: +``` +./nym-mixnode run --id +``` +* Now, without closing the tmux window, you can close the whole terminal and the mixnode (and any other process running in tmux) will stay active. +* Next time just start your teminal, ssh into the VPS and run the following command to attach back to your previous session: +``` +tmux attach session +``` +* To see keybinding options of tmux press `ctrl`+`b` and after 1 second `?` + +#### systemd + +Here's a systemd service file to do that: + +```ini +[Unit] +Description=Nym Mixnode ({{mix_node_release_version}}) +StartLimitInterval=350 +StartLimitBurst=10 + +[Service] +User= +LimitNOFILE=65536 +ExecStart=/home///nym-mixnode run --id +KillSignal=SIGINT +Restart=on-failure +RestartSec=30 + +[Install] +WantedBy=multi-user.target +``` + +Put the above file onto your system at `/etc/systemd/system/nym-mixnode.service`. + +Change the `` in `ExecStart` to point at your mix node binary (`nym-mixnode`), and the `` so it is the user you are running as. + +If you have built nym in the `$HOME` directory on your server, and your username is `jetpanther`, then the start command might look like this: + +`ExecStart=/home/jetpanther/nym/target/release/nym-mixnode run --id your-id`. Basically, you want the full `/path/to/nym-mixnode run --id whatever-your-node-id-is` + +Then run: + +``` +systemctl enable nym-mixnode.service +``` + +Start your node: + +``` +service nym-mixnode start +``` + +This will cause your node to start at system boot time. If you restart your machine, the node will come back up automatically. + +You can also do `service nym-mixnode stop` or `service nym-mixnode restart`. + +Note: if you make any changes to your systemd script after you've enabled it, you will need to run: + +``` +systemctl daemon-reload +``` + +This lets your operating system know it's ok to reload the service configuration. + +#### Setting the ulimit + +Linux machines limit how many open files a user is allowed to have. This is called a `ulimit`. + +`ulimit` is 1024 by default on most systems. It needs to be set higher, because mix nodes make and receive a lot of connections to other nodes. + +If you see errors such as: + +``` +Failed to accept incoming connection - Os { code: 24, kind: Other, message: "Too many open files" } +``` + +This means that the operating system is preventing network connections from being made. + +##### Set the ulimit via `systemd` service file + +Query the `ulimit` of your mix node with: + +``` +grep -i "open files" /proc/$(ps -A -o pid,cmd|grep nym-mixnode | grep -v grep |head -n 1 | awk '{print $1}')/limits +``` + +You'll get back the hard and soft limits, which looks something like this: + +``` +Max open files 65536 65536 files +``` + +If your output is **the same as above**, your node will not encounter any `ulimit` related issues. + +However if either value is `1024`, you must raise the limit via the systemd service file. Add the line: + +``` +LimitNOFILE=65536 +``` + +Reload the daemon: + +``` +systemctl daemon-reload +``` + +or execute this as root for system-wide setting of `ulimit`: + +``` +echo "DefaultLimitNOFILE=65535" >> /etc/systemd/system.conf +``` + +Reboot your machine and restart your node. When it comes back, use `cat /proc/$(pidof nym-mixnode)/limits | grep "Max open files"` to make sure the limit has changed to 65535. + +##### Set the ulimit on `non-systemd` based distributions + +In case you chose tmux option for mixnode automatization, see your `ulimit` list by running: + +``` +ulimit -a + +# watch for the output line -n +-n: file descriptors 1024 +``` + +You can change it either by running a command: + +``` +ulimit -u -n 4096 +``` + +or editing `etc/security/conf` and add the following lines: + +``` +# Example hard limit for max opened files +username hard nofile 4096 + +# Example soft limit for max opened files +username soft nofile 4096 +``` + +Then reboot your server and restart your mixnode. + +## Node Description +Node description is a short text that describes your node. It is displayed in the `./nym-mixnode list` command and in the `./nym-mixnode node-details` command. It also shows up in the node explorer to let people know what your node is about and link to your website. + +To set your node description, create a file called `description.toml` and put it in the same directory as your `config.toml` file (`~/.nym/mixnodes//description.toml`). The file should look like this example: + +```toml +name = "Winston Smith" +description = "I am the Sphinx" +link = "https://nymtech.net" +location = "Giza, Egypt" +``` + +You will need to restart your node for the changes to take effect. + +## Node Families + +Node family involves setting up a group of mix nodes that work together to provide greater privacy and security for network communications. This is achieved by having the nodes in the family share information and routes, creating a decentralized network that makes it difficult for third parties to monitor or track communication traffic. + +### Create a Node Family + +To create a Node family, you will need to install and configure multiple mix nodes, and then use the CLI to link them together into a family. Once your Node family is up and running, you can use it to route your network traffic through a series of nodes, obscuring the original source and destination of the communication. + +You can use either `nym-cli` which can be downloaded from the [release page](https://github.com/nymtech/nym/releases) or compiling `nyxd`. + + +Change directory by `cd ///` and run the following on the family head to obtain the signature for the member: + +``` +./nym-mixnode sign --id --text +``` + +~~~admonish example collapsible=true title="Console output" +``` + +``` +~~~ + +Using `nym-cli`: + +> `--mnemonic` is the mnemonic of the member wanting to be the head of family. + +``` +/nym-cli cosmwasm execute '{"create_family": {"signature": "","family_head": "","owner_signature":"","label": ""}}' --mnemonic +``` + +Using `nyxd`: + +> `--from` is mnemonic of the member wanting to join the family. + +``` +./nyxd tx wasm execute ${MIXNET-CONTRACT} '{"join_family": {"signature": "","family_head": ""}}' --node ${VALIDATOR-ENDPOINT} --from mix1 --chain-id nyx --gas-prices 0.025unym --gas auto --gas-adjustment 1.3 -y -b block +``` + +To get the node owner signature, use: + +`./nym-mixnode node-details --id ` + +### Joining a Node Family + +Change directory by `cd ///` and run the following on the family head to obtain the signature for the member: + +``` +./nym-mixnode sign --id --text +``` + +~~~admonish example collapsible=true title="Console output" +``` + +``` +~~~ + +Using `nym-cli`: + +``` +./nym-cli cosmwasm execute '{"join_family": {"signature": "","family_head": "","owner_signautre": "", "label":""}}' --mnemonic +``` + +Using `nyxd`: + +``` +./nyxd tx wasm execute ${MIXNET-CONTRACT} '{"join_family": {"signature": "","family_head": ""}}' --node ${VALIDATOR-ENDPOINT} --from mix1 --chain-id nyx --gas-prices 0.025unym --gas auto --gas-adjustment 1.3 -y -b block +``` + + +To get the node owner signature, use: + +`./nym-mixnode node-details --id ` + + +### Leaving a family +If wanting to leave, run the same initial command as above, followed by: + +Using `nym-cli`: + +``` +./nym-cli cosmwasm execute '{"leave_family": {"signature": "","family_head": "","owner_signautre": ""}}' --mnemonic +``` + +Using `nyxd`: + +``` +./nyxd tx wasm execute ${MIXNET-CONTRACT} '{"join_family": {"signature": "","family_head": ""}}' --node ${VALIDATOR-ENDPOINT} --from mix1 --chain-id nyx --gas-prices 0.025unym --gas auto --gas-adjustment 1.3 -y -b block +``` + +## Checking that your node is mixing correctly +### Network explorers +Once you've started your mix node and it connects to the validator, your node will automatically show up in the 'Mix nodes' section of either the Nym Network Explorers: + +- [Mainnet](https://explorer.nymtech.net/overview) +- [Sandbox testnet](https://sandbox-explorer.nymtech.net/) + +Enter your **identity key** to find your node. There are numerous statistics about your node on that page that are useful for checking your uptime history, packets mixed, and any delegations your node may have. + +There are also 2 community explorers which have been created by [Nodes Guru](https://nodes.guru): + +- [Mainnet](https://mixnet.explorers.guru/) +- [Sandbox testnet](https://sandbox.mixnet.explorers.guru/) + +For more details see [Troubleshooting FAQ](../nodes/troubleshooting.md) + + + +### Virtual IPs and hosting via Google & AWS +For tru internet decentralization we encourage operators to use diverse VPS providers instead of the largest companies offering such services. If for some reasons you have already running AWS or Google and want to setup a mixnode there, please read the following. + +On some services (AWS, Google, etc) the machine's available bind address is not the same as the public IP address. In this case, bind `--host` to the local machine address returned by `$(curl ifconfig.me)`, but also specify `--announce-host` with the public IP. Please make sure that you pass the correct, routable `--announce-host`. + +For example, on a Google machine, you may see the following output from the `ifconfig` command: + +``` +ens4: flags=4163 mtu 1460 + inet 10.126.5.7 netmask 255.255.255.255 broadcast 0.0.0.0 + ... +``` + +The `ens4` interface has the IP `10.126.5.7`. But this isn't the public IP of the machine, it's the IP of the machine on Google's internal network. Google uses virtual routing, so the public IP of this machine is something else, maybe `36.68.243.18`. + +`./nym-mixnode init --host 10.126.5.7`, initalises the mix node, but no packets will be routed because `10.126.5.7` is not on the public internet. + +Trying `nym-mixnode init --host 36.68.243.18`, you'll get back a startup error saying `AddrNotAvailable`. This is because the mix node doesn't know how to bind to a host that's not in the output of `ifconfig`. + +The right thing to do in this situation is to init with a command: +``` +./nym-mixnode init --host 10.126.5.7 --announce-host 36.68.243.18 +``` + +This will bind the mix node to the available host `10.126.5.7`, but announce the mix node's public IP to the directory server as `36.68.243.18`. It's up to you as a node operator to ensure that your public and private IPs match up properly. + +To find the right IP configuration, contact your VPS provider for support. + +## Metrics / API endpoints +The mix node binary exposes several API endpoints that can be pinged in order to gather information about the node, and the Nym API (previously 'Validator API') exposes numerous mix node related endpoints which provide network-wide information about mix nodes, the network topology (the list of avaliable mix nodes for packet routing), and information regarding uptime monitoring and rewarding history. + +### Mix node API endpoints +Since the mix node binary exposes several API endpoints itself, you can ping these easily via curl: + +| Endpoint | Description | Command | +| -------------------- | ------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------- | +| `/description` | Returns the description of the node set with the `describe` command | `curl :8000/description` | +| `/hardware` | Returns the hardware information of the node | `curl :8000/hardware` | +| `/verloc` | Returns the verloc information of the node, updated every 12 hours | `curl :8000/verloc` | + +The code for exposed API endpoints can be found [here](https://github.com/nymtech/nym/tree/release/{{platform_release_version}}/mixnode/src/node/http). + +> You can get more detailed info by appending `?debug` to the URL, like so: +> ``` +> curl http://:8000/stats?debug +> ``` + +### Mix node related Nym API (previously 'Validator API') endpoints +Numerous endpoints are documented on the Nym API (previously 'Validator API')'s [Swagger Documentation](https://validator.nymtech.net/api/swagger/index.html). There you can also try out various requests from your browser, and download the response from the API. Swagger will also show you what commands it is running, so that you can run these from an app or from your CLI if you prefer. + +#### Mix node Reward Estimation API endpoint + +The Reward Estimation API endpoint allows mixnode operators to estimate the rewards they could earn for running a Nym mixnode with a specific `MIX_ID`. + +> The `` can be found in the "Mix ID" column of the [Network Explorer](https://explorer.nymtech.net/network-components/mixnodes/active). + +The endpoint is a particularly common for mixnode operators as it can provide an estimate of potential earnings based on factors such as the amount of traffic routed through the mixnode, the quality of the mixnode's performance, and the overall demand for mixnodes in the network. This information can be useful for mixnode operators in deciding whether or not to run a mix node and in optimizing its operations for maximum profitability. + +Using this API endpoint returns information about the Reward Estimation: + +``` +/status/mixnode//reward-estimation +``` + +Query Response: + +``` + "estimation": { + "total_node_reward": "942035.916721770541325331", + "operator": "161666.263307386408152071", + "delegates": "780369.65341438413317326", + "operating_cost": "54444.444444444444444443" + }, +``` + +> The unit of value is measured in `uNYM`. + +- `estimated_total_node_reward` - An estimate of the total amount of rewards that a particular mix node can expect to receive during the current epoch. This value is calculated by the Nym Validator based on a number of factors, including the current state of the network, the number of mix nodes currently active in the network, and the amount of network traffic being processed by the mix node. + +- `estimated_operator_reward` - An estimate of the amount of rewards that a particular mix node operator can expect to receive. This value is calculated by the Nym Validator based on a number of factors, including the amount of traffic being processed by the mix node, the quality of service provided by the mix node, and the operator's stake in the network. + +- `estimated_delegators_reward` - An estimate of the amount of rewards that mix node delegators can expect to receive individually. This value is calculated by the Nym Validator based on a number of factors, including the amount of traffic being processed by the mix node, the quality of service provided by the mix node, and the delegator's stake in the network. + +- `estimated_node_profit` - An estimate of the profit that a particular mix node operator can expect to earn. This value is calculated by subtracting the mix node operator's `operating_costs` from their `estimated_operator_reward` for the current epoch. + +- `estimated_operator_cost` - An estimate of the total cost that a particular mix node operator can expect to incur for their participation. This value is calculated by the Nym Validator based on a number of factors, including the cost of running a mix node, such as server hosting fees, and other expenses associated with operating the mix node. + +## Ports +All mix node-specific port configuration can be found in `$HOME/.nym/mixnodes//config/config.toml`. If you do edit any port configs, remember to restart your mix node. + +### Mix node port reference +| Default port | Use | +| ------------ | ------------------------- | +| `1789` | Listen for mixnet traffic | +| `1790` | Listen for VerLoc traffic | +| `8000` | Metrics http API endpoint | +/ + diff --git a/documentation/operators/src/nodes/setup-guides.md b/documentation/operators/src/nodes/setup-guides.md new file mode 100644 index 0000000000..b19cf07deb --- /dev/null +++ b/documentation/operators/src/nodes/setup-guides.md @@ -0,0 +1,18 @@ + +# Node Setup Guides + +To setup any type of Nym's node, start with building [Nym's platform](https://nymtech.net/docs/binaries/building-nym.html) on the machine (VPS) where you want to run the node. Nodes will need to be bond to Nym's wallet, setup one [here](https://nymtech.net/docs/wallet/desktop-wallet.html). + +This section contains setup guides for the following node types: +* [Mix node](./mix-node-setup.md) +* [Gateway](./gateway-setup.md) +* [Network Requester](./network-requester-setup.md) +* [Validator](./validator-setup.md) + + From 7e5c79848ff4d64185b0b32a482fb57b04a91096 Mon Sep 17 00:00:00 2001 From: serinko Date: Thu, 13 Jul 2023 16:59:25 +0200 Subject: [PATCH 002/361] feature/operators-guide: coppied all configs from ../docs/ --- documentation/operators/.gitignore | 27 ++ documentation/operators/mdbook-admonish.css | 352 ++++++++++++++++++ .../operators/src/nodes/gateway-setup.md | 1 + .../src/nodes/network-requester-setup.md | 1 + .../operators/src/nodes/troubleshooting.md | 1 + 5 files changed, 382 insertions(+) create mode 100644 documentation/operators/.gitignore create mode 100644 documentation/operators/mdbook-admonish.css create mode 100644 documentation/operators/src/nodes/gateway-setup.md create mode 100644 documentation/operators/src/nodes/network-requester-setup.md create mode 100644 documentation/operators/src/nodes/troubleshooting.md diff --git a/documentation/operators/.gitignore b/documentation/operators/.gitignore new file mode 100644 index 0000000000..e84b38bdfb --- /dev/null +++ b/documentation/operators/.gitignore @@ -0,0 +1,27 @@ +# mdbook files +book +book/* +theme/index.hbs +theme/pagetoc.css +theme/pagetoc.js +theme/* +theme + +# --------------- +# Compiled assets +# --------------- +.sass-cache +_site + +# ---------- +# Developing +# ---------- +todo.md +.vscode + +# OSX +.DS_Store + +theme/ +theme +theme/* diff --git a/documentation/operators/mdbook-admonish.css b/documentation/operators/mdbook-admonish.css new file mode 100644 index 0000000000..5e360387df --- /dev/null +++ b/documentation/operators/mdbook-admonish.css @@ -0,0 +1,352 @@ +@charset "UTF-8"; +:root { + --md-admonition-icon--note: + url("data:image/svg+xml;charset=utf-8,"); + --md-admonition-icon--abstract: + url("data:image/svg+xml;charset=utf-8,"); + --md-admonition-icon--info: + url("data:image/svg+xml;charset=utf-8,"); + --md-admonition-icon--tip: + url("data:image/svg+xml;charset=utf-8,"); + --md-admonition-icon--success: + url("data:image/svg+xml;charset=utf-8,"); + --md-admonition-icon--question: + url("data:image/svg+xml;charset=utf-8,"); + --md-admonition-icon--warning: + url("data:image/svg+xml;charset=utf-8,"); + --md-admonition-icon--failure: + url("data:image/svg+xml;charset=utf-8,"); + --md-admonition-icon--danger: + url("data:image/svg+xml;charset=utf-8,"); + --md-admonition-icon--bug: + url("data:image/svg+xml;charset=utf-8,"); + --md-admonition-icon--example: + url("data:image/svg+xml;charset=utf-8,"); + --md-admonition-icon--quote: + url("data:image/svg+xml;charset=utf-8,"); + --md-details-icon: + url("data:image/svg+xml;charset=utf-8,"); +} + +:is(.admonition) { + display: flow-root; + margin: 1.5625em 0; + padding: 0 1.2rem; + color: var(--fg); + page-break-inside: avoid; + background-color: var(--bg); + border: 0 solid black; + border-inline-start-width: 0.4rem; + border-radius: 0.2rem; + box-shadow: 0 0.2rem 1rem rgba(0, 0, 0, 0.05), 0 0 0.1rem rgba(0, 0, 0, 0.1); +} +@media print { + :is(.admonition) { + box-shadow: none; + } +} +:is(.admonition) > * { + box-sizing: border-box; +} +:is(.admonition) :is(.admonition) { + margin-top: 1em; + margin-bottom: 1em; +} +:is(.admonition) > .tabbed-set:only-child { + margin-top: 0; +} +html :is(.admonition) > :last-child { + margin-bottom: 1.2rem; +} + +a.admonition-anchor-link { + display: none; + position: absolute; + left: -1.2rem; + padding-right: 1rem; +} +a.admonition-anchor-link:link, a.admonition-anchor-link:visited { + color: var(--fg); +} +a.admonition-anchor-link:link:hover, a.admonition-anchor-link:visited:hover { + text-decoration: none; +} +a.admonition-anchor-link::before { + content: "§"; +} + +:is(.admonition-title, summary) { + position: relative; + margin-block: 0; + margin-inline: -1.6rem -1.2rem; + padding-block: 0.8rem; + padding-inline: 4.4rem 1.2rem; + font-weight: 700; + background-color: rgba(68, 138, 255, 0.1); + display: flex; +} +:is(.admonition-title, summary) p { + margin: 0; +} +html :is(.admonition-title, summary):last-child { + margin-bottom: 0; +} +:is(.admonition-title, summary)::before { + position: absolute; + top: 0.625em; + inset-inline-start: 1.6rem; + width: 2rem; + height: 2rem; + background-color: #448aff; + mask-image: url('data:image/svg+xml;charset=utf-8,'); + -webkit-mask-image: url('data:image/svg+xml;charset=utf-8,'); + mask-repeat: no-repeat; + -webkit-mask-repeat: no-repeat; + mask-size: contain; + -webkit-mask-size: contain; + content: ""; +} +:is(.admonition-title, summary):hover a.admonition-anchor-link { + display: initial; +} + +details.admonition > summary.admonition-title::after { + position: absolute; + top: 0.625em; + inset-inline-end: 1.6rem; + height: 2rem; + width: 2rem; + background-color: currentcolor; + mask-image: var(--md-details-icon); + -webkit-mask-image: var(--md-details-icon); + mask-repeat: no-repeat; + -webkit-mask-repeat: no-repeat; + mask-size: contain; + -webkit-mask-size: contain; + content: ""; + transform: rotate(0deg); + transition: transform 0.25s; +} +details[open].admonition > summary.admonition-title::after { + transform: rotate(90deg); +} + +:is(.admonition):is(.note) { + border-color: #448aff; +} + +:is(.note) > :is(.admonition-title, summary) { + background-color: rgba(68, 138, 255, 0.1); +} +:is(.note) > :is(.admonition-title, summary)::before { + background-color: #448aff; + mask-image: var(--md-admonition-icon--note); + -webkit-mask-image: var(--md-admonition-icon--note); + mask-repeat: no-repeat; + -webkit-mask-repeat: no-repeat; + mask-size: contain; + -webkit-mask-repeat: no-repeat; +} + +:is(.admonition):is(.abstract, .summary, .tldr) { + border-color: #00b0ff; +} + +:is(.abstract, .summary, .tldr) > :is(.admonition-title, summary) { + background-color: rgba(0, 176, 255, 0.1); +} +:is(.abstract, .summary, .tldr) > :is(.admonition-title, summary)::before { + background-color: #00b0ff; + mask-image: var(--md-admonition-icon--abstract); + -webkit-mask-image: var(--md-admonition-icon--abstract); + mask-repeat: no-repeat; + -webkit-mask-repeat: no-repeat; + mask-size: contain; + -webkit-mask-repeat: no-repeat; +} + +:is(.admonition):is(.info, .todo) { + border-color: #00b8d4; +} + +:is(.info, .todo) > :is(.admonition-title, summary) { + background-color: rgba(0, 184, 212, 0.1); +} +:is(.info, .todo) > :is(.admonition-title, summary)::before { + background-color: #00b8d4; + mask-image: var(--md-admonition-icon--info); + -webkit-mask-image: var(--md-admonition-icon--info); + mask-repeat: no-repeat; + -webkit-mask-repeat: no-repeat; + mask-size: contain; + -webkit-mask-repeat: no-repeat; +} + +:is(.admonition):is(.tip, .hint, .important) { + border-color: #00bfa5; +} + +:is(.tip, .hint, .important) > :is(.admonition-title, summary) { + background-color: rgba(0, 191, 165, 0.1); +} +:is(.tip, .hint, .important) > :is(.admonition-title, summary)::before { + background-color: #00bfa5; + mask-image: var(--md-admonition-icon--tip); + -webkit-mask-image: var(--md-admonition-icon--tip); + mask-repeat: no-repeat; + -webkit-mask-repeat: no-repeat; + mask-size: contain; + -webkit-mask-repeat: no-repeat; +} + +:is(.admonition):is(.success, .check, .done) { + border-color: #00c853; +} + +:is(.success, .check, .done) > :is(.admonition-title, summary) { + background-color: rgba(0, 200, 83, 0.1); +} +:is(.success, .check, .done) > :is(.admonition-title, summary)::before { + background-color: #00c853; + mask-image: var(--md-admonition-icon--success); + -webkit-mask-image: var(--md-admonition-icon--success); + mask-repeat: no-repeat; + -webkit-mask-repeat: no-repeat; + mask-size: contain; + -webkit-mask-repeat: no-repeat; +} + +:is(.admonition):is(.question, .help, .faq) { + border-color: #64dd17; +} + +:is(.question, .help, .faq) > :is(.admonition-title, summary) { + background-color: rgba(100, 221, 23, 0.1); +} +:is(.question, .help, .faq) > :is(.admonition-title, summary)::before { + background-color: #64dd17; + mask-image: var(--md-admonition-icon--question); + -webkit-mask-image: var(--md-admonition-icon--question); + mask-repeat: no-repeat; + -webkit-mask-repeat: no-repeat; + mask-size: contain; + -webkit-mask-repeat: no-repeat; +} + +:is(.admonition):is(.warning, .caution, .attention) { + border-color: #ff9100; +} + +:is(.warning, .caution, .attention) > :is(.admonition-title, summary) { + background-color: rgba(255, 145, 0, 0.1); +} +:is(.warning, .caution, .attention) > :is(.admonition-title, summary)::before { + background-color: #ff9100; + mask-image: var(--md-admonition-icon--warning); + -webkit-mask-image: var(--md-admonition-icon--warning); + mask-repeat: no-repeat; + -webkit-mask-repeat: no-repeat; + mask-size: contain; + -webkit-mask-repeat: no-repeat; +} + +:is(.admonition):is(.failure, .fail, .missing) { + border-color: #ff5252; +} + +:is(.failure, .fail, .missing) > :is(.admonition-title, summary) { + background-color: rgba(255, 82, 82, 0.1); +} +:is(.failure, .fail, .missing) > :is(.admonition-title, summary)::before { + background-color: #ff5252; + mask-image: var(--md-admonition-icon--failure); + -webkit-mask-image: var(--md-admonition-icon--failure); + mask-repeat: no-repeat; + -webkit-mask-repeat: no-repeat; + mask-size: contain; + -webkit-mask-repeat: no-repeat; +} + +:is(.admonition):is(.danger, .error) { + border-color: #ff1744; +} + +:is(.danger, .error) > :is(.admonition-title, summary) { + background-color: rgba(255, 23, 68, 0.1); +} +:is(.danger, .error) > :is(.admonition-title, summary)::before { + background-color: #ff1744; + mask-image: var(--md-admonition-icon--danger); + -webkit-mask-image: var(--md-admonition-icon--danger); + mask-repeat: no-repeat; + -webkit-mask-repeat: no-repeat; + mask-size: contain; + -webkit-mask-repeat: no-repeat; +} + +:is(.admonition):is(.bug) { + border-color: #f50057; +} + +:is(.bug) > :is(.admonition-title, summary) { + background-color: rgba(245, 0, 87, 0.1); +} +:is(.bug) > :is(.admonition-title, summary)::before { + background-color: #f50057; + mask-image: var(--md-admonition-icon--bug); + -webkit-mask-image: var(--md-admonition-icon--bug); + mask-repeat: no-repeat; + -webkit-mask-repeat: no-repeat; + mask-size: contain; + -webkit-mask-repeat: no-repeat; +} + +:is(.admonition):is(.example) { + border-color: #7c4dff; +} + +:is(.example) > :is(.admonition-title, summary) { + background-color: rgba(124, 77, 255, 0.1); +} +:is(.example) > :is(.admonition-title, summary)::before { + background-color: #7c4dff; + mask-image: var(--md-admonition-icon--example); + -webkit-mask-image: var(--md-admonition-icon--example); + mask-repeat: no-repeat; + -webkit-mask-repeat: no-repeat; + mask-size: contain; + -webkit-mask-repeat: no-repeat; +} + +:is(.admonition):is(.quote, .cite) { + border-color: #9e9e9e; +} + +:is(.quote, .cite) > :is(.admonition-title, summary) { + background-color: rgba(158, 158, 158, 0.1); +} +:is(.quote, .cite) > :is(.admonition-title, summary)::before { + background-color: #9e9e9e; + mask-image: var(--md-admonition-icon--quote); + -webkit-mask-image: var(--md-admonition-icon--quote); + mask-repeat: no-repeat; + -webkit-mask-repeat: no-repeat; + mask-size: contain; + -webkit-mask-repeat: no-repeat; +} + +.navy :is(.admonition) { + background-color: var(--sidebar-bg); +} + +.ayu :is(.admonition), .coal :is(.admonition) { + background-color: var(--theme-hover); +} + +.rust :is(.admonition) { + background-color: var(--sidebar-bg); + color: var(--sidebar-fg); +} +.rust .admonition-anchor-link:link, .rust .admonition-anchor-link:visited { + color: var(--sidebar-fg); +} diff --git a/documentation/operators/src/nodes/gateway-setup.md b/documentation/operators/src/nodes/gateway-setup.md new file mode 100644 index 0000000000..f4ee68e242 --- /dev/null +++ b/documentation/operators/src/nodes/gateway-setup.md @@ -0,0 +1 @@ +# Gateway diff --git a/documentation/operators/src/nodes/network-requester-setup.md b/documentation/operators/src/nodes/network-requester-setup.md new file mode 100644 index 0000000000..797dfd7795 --- /dev/null +++ b/documentation/operators/src/nodes/network-requester-setup.md @@ -0,0 +1 @@ +# Network Requester diff --git a/documentation/operators/src/nodes/troubleshooting.md b/documentation/operators/src/nodes/troubleshooting.md new file mode 100644 index 0000000000..4f341277d3 --- /dev/null +++ b/documentation/operators/src/nodes/troubleshooting.md @@ -0,0 +1 @@ +# Troubleshooting From 153e899e0e924c5542e00d4fc5254efb9f6aca97 Mon Sep 17 00:00:00 2001 From: serinko Date: Fri, 14 Jul 2023 11:20:51 +0200 Subject: [PATCH 003/361] mixnode-seup: minor edits --- documentation/operators/src/introduction.md | 10 ++++++++-- documentation/operators/src/nodes/mix-node-setup.md | 2 +- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/documentation/operators/src/introduction.md b/documentation/operators/src/introduction.md index f0344db71a..d2c148bf64 100644 --- a/documentation/operators/src/introduction.md +++ b/documentation/operators/src/introduction.md @@ -21,7 +21,13 @@ If you are looking for information on grants and beta-release Nym apps, check ou * [Mix nodes](./nodes/mix-node-setup.md) * [Gateways](./nodes/gateway-setup.md) * [Network requesters](./nodes/network-requester-setup.md) - +* [Validators](./nodes/validator-setup.md) + +**Troubleshooting and FAQ** +* [Troubleshooting](./nodes/troubleshooting.md) +* [FAQ](./faq.md) + + diff --git a/documentation/operators/src/nodes/mix-node-setup.md b/documentation/operators/src/nodes/mix-node-setup.md index a24ec587ad..723984ff39 100644 --- a/documentation/operators/src/nodes/mix-node-setup.md +++ b/documentation/operators/src/nodes/mix-node-setup.md @@ -63,7 +63,7 @@ For now, see the below rough specs: ## Mix node setup -Now that you have built the codebase, set up your wallet, and have a VPS with the `nym-mixnode` binary, you can set up your mix node with the instructions below. +Now that you have built the [codebase](https://nymtech.net/docs/binaries/building-nym.html), set up your [wallet](https://nymtech.net/docs/wallet/desktop-wallet.html), and have a VPS with the `nym-mixnode` binary, you can set up your mix node with the instructions below. To begin, move to `/taget/release` directory from which you run the node commands: From ff65d052b86160886b1a3d98a421e859714b1706 Mon Sep 17 00:00:00 2001 From: serinko Date: Fri, 14 Jul 2023 13:25:12 +0200 Subject: [PATCH 004/361] operators-guide: created generic docs maintenance.md and preliminary-steps.md to avoid redundancy --- documentation/operators/src/SUMMARY.md | 3 + documentation/operators/src/faq.md | 7 + .../wallet-gateway-sign.png | Bin 0 -> 118052 bytes documentation/operators/src/introduction.md | 3 +- .../operators/src/nodes/gateway-setup.md | 254 ++++++++++++- .../operators/src/nodes/maintenance.md | 357 ++++++++++++++++++ .../operators/src/nodes/mix-node-setup.md | 35 +- .../operators/src/preliminary-steps.md | 43 +++ 8 files changed, 667 insertions(+), 35 deletions(-) create mode 100644 documentation/operators/src/images/wallet-screenshots/wallet-gateway-sign.png create mode 100644 documentation/operators/src/nodes/maintenance.md create mode 100644 documentation/operators/src/preliminary-steps.md diff --git a/documentation/operators/src/SUMMARY.md b/documentation/operators/src/SUMMARY.md index 7bb81c8800..796c7d41ed 100644 --- a/documentation/operators/src/SUMMARY.md +++ b/documentation/operators/src/SUMMARY.md @@ -3,8 +3,11 @@ # Operators Guides - [Node Setup Guides](./nodes/setup-guides.md) +- [Preliminary Steps](./preliminary-steps.md) - [Mix Node](./nodes/mix-node-setup.md) - [Gateway](./nodes/gateway-setup.md) - [Network Requester](./nodes/network-requester-setup.md) + - [Validator](./nodes/validator-setup.md) +- [Maintenance](./nodes/maintenance.md) - [Troubleshooting](./nodes/troubleshooting.md) - [FAQ](./faq.md) diff --git a/documentation/operators/src/faq.md b/documentation/operators/src/faq.md index d6b32f3e77..5e1f5b72ad 100644 --- a/documentation/operators/src/faq.md +++ b/documentation/operators/src/faq.md @@ -4,4 +4,11 @@ TODO - [ ] List friendly VPS providers - [ ] Explain difficulty running a node on a self-hosted machine - [ ] Explain uNYM vs NYM. Why, ratio, etc + +Some community questions: +Hi, do you know if it's on the roadmap for network nodes (requesters, socks5 client, etc.) to reconnect to another gateway if the current gateway's connection drops or has issues? I have to reset my requester and socks5 clients to connect them to a new gateway almost every day...😕 having to delete the entire folders associated with them + +you should be able to switch gateway (and overwrite keys) using —force-register + + ---> diff --git a/documentation/operators/src/images/wallet-screenshots/wallet-gateway-sign.png b/documentation/operators/src/images/wallet-screenshots/wallet-gateway-sign.png new file mode 100644 index 0000000000000000000000000000000000000000..63a76aaf4daa49a970ceed5e65c6bcf03891d507 GIT binary patch literal 118052 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

gh{@Ld5VYc~WzO%W;Gv3S$jd$h&x{|!h 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

@E}Q7HmryqarIxWXBsD9W9l?>YP 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

TaZoR+yex1nQT6Ur! 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 Fe*r}Fq&@%u literal 0 HcmV?d00001 diff --git a/documentation/operators/src/introduction.md b/documentation/operators/src/introduction.md index d2c148bf64..a97860f292 100644 --- a/documentation/operators/src/introduction.md +++ b/documentation/operators/src/introduction.md @@ -23,7 +23,8 @@ If you are looking for information on grants and beta-release Nym apps, check ou * [Network requesters](./nodes/network-requester-setup.md) * [Validators](./nodes/validator-setup.md) -**Troubleshooting and FAQ** +**Maintenance, troubleshooting and FAQ** +* [Maintenance](./nodes/maintenance.md) * [Troubleshooting](./nodes/troubleshooting.md) * [FAQ](./faq.md) diff --git a/documentation/operators/src/nodes/gateway-setup.md b/documentation/operators/src/nodes/gateway-setup.md index f4ee68e242..591cd3d1a9 100644 --- a/documentation/operators/src/nodes/gateway-setup.md +++ b/documentation/operators/src/nodes/gateway-setup.md @@ -1 +1,253 @@ -# Gateway +# Gateways + +> The Nym gateway was built in the [building nym](../binaries/building-nym.md) section. If you haven't yet built Nym and want to run the code, go there first. + +> Any syntax in `<>` brackets is a user's unique variable. Exchange with a corresponding name without the `<>` brackets. + +## Current version +``` + +``` + +## Preliminary steps + +Make sure you do the preparation listed in the [preliminary steps page](../preliminary-steps.md) before setting up your gateway. + + +## Gateway setup +Now that you have built the codebase, set up your wallet, and have a VPS with the `nym-gateway` binary, you can set up your gateway with the instructions below. + +To begin, move to `/taget/release` directory from which you run the node commands: + +``` +cd target/release +``` + +### Viewing command help +You can check that your binaries are properly compiled with: + +``` +./nym-gateway --help +``` + +~~~admonish example collapsible=true title="Console output" +``` + +``` +~~~ + +You can also check the various arguments required for individual commands with: + +``` +./nym-gateway --help +``` + +### Initialising your gateway +To check available configuration options use: + +``` + ./nym-gateway init --help +``` + +~~~admonish example collapsible=true title="Console output" +``` + +``` +~~~ + +The following command returns a gateway on your current IP with the `id` of `supergateway`: + +``` +./nym-gateway init --id supergateway --host $(curl ifconfig.me) --wallet-address n1eufxdlgt0puwrwptgjfqne8pj4nhy2u5ft62uq +``` + +~~~admonish example collapsible=true title="Console output" +``` + +``` +~~~ + +The `$(curl ifconfig.me)` command above returns your IP automatically using an external service. Alternatively, you can enter your IP manually if you wish. If you do this, remember to enter your IP **without** any port information. + +### Bonding your gateway +#### Via the Desktop wallet +You can bond your gateway via the Desktop wallet. + +1. Open your wallet, and head to the `Bonding` page, then select the node type `Gateway` and input your node details (Location format is , ). Press `Next` + +2. Enter the `Amount`, `Operating cost` and press `Next`. + +3. You will be asked to run a the `sign` command with your `gateway` - copy and paste the long signature as the value of `--contract-msg` and run it. + +``` +./nym-mixnode sign --id --contract-msg +``` + +It will look something like this: + +~~~admonish example collapsible=true title="Console output" +``` +./nym-gateway sign --id upgrade_test --contract-msg 2Mf8xYytgEeyJke9LA7TjhHoGQWNBEfgHZtTyy2krFJfGHSiqy7FLgTnauSkQepCZTqKN5Yfi34JQCuog9k6FGA2EjsdpNGAWHZiuUGDipyJ6UksNKRxnFKhYW7ri4MRduyZwbR98y5fQMLAwHne1Tjm9cXYCn8McfigNt77WAYwBk5bRRKmC34BJMmWcAxphcLES2v9RdSR68tkHSpy2C8STfdmAQs3tZg8bJS5Qa8pQdqx14TnfQAPLk3QYCynfUJvgcQTrg29aqCasceGRpKdQ3Tbn81MLXAGAs7JLBbiMEAhCezAr2kEN8kET1q54zXtKz6znTPgeTZoSbP8rzf4k2JKHZYWrHYF9JriXepuZTnyxAKAxvGFPBk8Z6KAQi33NRQkwd7MPyttatHna6kG9x7knffV6ebGzgRBf7NV27LurH8x4L1uUXwm1v1UYCA1WSBQ9Pp2JW69k5v5v7G9gBy8RUcZnMbeL26Qqb8WkuGcmuHhaFfoqSfV7PRHPpPT4M8uRqUyR4bjUtSJJM1yh6QSeZk9BEazzoJqPeYeGoiFDZ3LMj2jesbJweQR4caaYuRczK92UGSSqu9zBKmE45a + + + _ __ _ _ _ __ ___ + | '_ \| | | | '_ \ _ \ + | | | | |_| | | | | | | + |_| |_|\__, |_| |_| |_| + |___/ + + (nym-gateway - version {{platform_release_version}}) + + +>>> attempting to sign 2Mf8xYytgEeyJke9LA7TjhHoGQWNBEfgHZtTyy2krFJfGHSiqy7FLgTnauSkQepCZTqKN5Yfi34JQCuog9k6FGA2EjsdpNGAWHZiuUGDipyJ6UksNKRxnFKhYW7ri4MRduyZwbR98y5fQMLAwHne1Tjm9cXYCn8McfigNt77WAYwBk5bRRKmC34BJMmWcAxphcLES2v9RdSR68tkHSpy2C8STfdmAQs3tZg8bJS5Qa8pQdqx14TnfQAPLk3QYCynfUJvgcQTrg29aqCasceGRpKdQ3Tbn81MLXAGAs7JLBbiMEAhCezAr2kEN8kET1q54zXtKz6znTPgeTZoSbP8rzf4k2JKHZYWrHYF9JriXepuZTnyxAKAxvGFPBk8Z6KAQi33NRQkwd7MPyttatHna6kG9x7knffV6ebGzgRBf7NV27LurH8x4L1uUXwm1v1UYCA1WSBQ9Pp2JW69k5v5v7G9gBy8RUcZnMbeL26Qqb8WkuGcmuHhaFfoqSfV7PRHPpPT4M8uRqUyR4bjUtSJJM1yh6QSeZk9BEazzoJqPeYeGoiFDZ3LMj2jesbJweQR4caaYuRczK92UGSSqu9zBKmE45a +>>> decoding the message... +>>> message to sign: {"nonce":0,"algorithm":"ed25519","message_type":"gateway-bonding","content":{"sender":"n1ewmme88q22l8syvgshqma02jv0vqrug9zq9dy8","proxy":null,"funds":[{"denom":"unym","amount":"100000000"}],"data":{"gateway":{"host":"62.240.134.189","mix_port":1789,"clients_port":9000,"location":"62.240.134.189","sphinx_key":"FKbuN7mPdoCG9jA3CkAfXxC5X4rHhqeMVtmfRtJ3cFZd","identity_key":"3RoAhR8gEdfBETMjm2vbMFzKddxXDdE9ygBAnJHWqSzD","version":"1.1.13"}}}} +``` +~~~ + +* Copy the resulting signature: + +``` +>>> The base58-encoded signature is: +2SPDjLjX4b6XEtkgG7yD8Znsb1xycL1edFvRK4JcVnPsM9k6HXEUUeVS6rswRiYxoj1bMgiRKyPDwiksiuyxu8Xi +``` + +* And paste it into the wallet nodal, press `Next` and confirm the transaction. + +![Paste Signature](../images/wallet-screenshots/wallet-gateway-sign.png) + +* Your gateway is now bonded. + +> You are asked to `sign` a transaction on bonding so that the mixnet smart contract is able to map your nym address to your node. This allows us to create a nonce for each account and defend against replay attacks. + +#### Via the CLI (power users) +If you want to bond your mix node via the CLI, then check out the [relevant section in the Nym CLI](https://nymtech.net/docs/tools/nym-cli.html#bond-a-mix-node) docs. + +### Running your gateway +The `run` command starts the gateway: + +``` +./nym-gateway run --id +``` + +### Upgrading your gateway +Upgrading your node is a two-step process: +* Updating the binary and `config.toml` on your VPS +* Updating the node information in the [mixnet smart contract](../nyx/mixnet-contract.md). **This is the information that is present on the [mixnet explorer](https://explorer.nymtech.net)**. + +>These instructions are specifically regarding upgrading your gateway binary from one version to another. If you want to change node information such as the listening port, you can do this by clicking the `node settings` tab in the `Bonding` page of the wallet. + +#### Step 1: Upgrading your binary +Follow these steps to upgrade your binary and update its config file: +* pause your gateway process. +* replace the existing binary with the newest binary (which you can either compile yourself or grab from our [releases page](https://github.com/nymtech/nym/releases)). +* re-run `init` with the same values as you used initially. **This will just update the config file, it will not overwrite existing keys**. +* restart your gateway process with the new binary. + +> Do **not** use the `upgrade` command: there is a known error with the command that will be fixed in a subsequent release. + +#### Step 2: Updating your node information in the smart contract +Follow these steps to update the information about your node which is publically avaliable from the [Nym API](https://validator.nymtech.net/api/swagger/index.html) and information displayed on the [mixnet explorer](https://explorer.nymtech.net). + +You can either do this graphically via the Desktop Wallet, or the CLI. + +#### Updating node information via the Desktop Wallet +* Navigate to the `Bonding` page and click the `Node Settings` link in the top right corner: +![Bonding page](../images/wallet-screenshots/bonding.png) + +* Update the fields in the `Node Settings` page and click `Submit changes to the blockchain`. +![Node Settings Page](../images/wallet-screenshots/node_settings.png) + +#### Updating node information via the CLI +If you want to bond your mix node via the CLI, then check out the [relevant section in the Nym CLI](../tools/nym-cli.md#upgrade-a-mix-node) docs. + +## VPS Setup and Automation +### Configure your firewall +Although your gateway is now ready to receive traffic, your server may not be - the following commands will allow you to set up a properly configured firewall using `ufw`: + +``` +# check if you have ufw installed +ufw version +# if it is not installed, install with +sudo apt install ufw -y +# enable ufw +sudo ufw enable +# check the status of the firewall +sudo ufw status +``` + +Finally open your gateway's p2p port, as well as ports for ssh and incoming traffic connections: + +``` +sudo ufw allow 1789,22,9000/tcp +# check the status of the firewall +sudo ufw status +``` + +For more information about your gateway's port configuration, check the [gateway port reference table](#gateway-port-reference) below. + +### Automating your gateway with systemd +Although it's not totally necessary, it's useful to have the gateway automatically start at system boot time. Here's a systemd service file to do that: + +```ini +[Unit] +Description=Nym Gateway ({{platform_release_version}}) +StartLimitInterval=350 +StartLimitBurst=10 + +[Service] +User=nym +LimitNOFILE=65536 +ExecStart=/home/nym/nym-gateway run --id supergateway +KillSignal=SIGINT +Restart=on-failure +RestartSec=30 + +[Install] +WantedBy=multi-user.target +``` + +Put the above file onto your system at `/etc/systemd/system/nym-gateway.service`. + +Change the path in `ExecStart` to point at your gateway binary (`nym-gateway`), and the `User` so it is the user you are running as. + +If you have built nym on your server, and your username is `jetpanther`, then the start command might look like this: + +`ExecStart=/home/jetpanther/nym/target/release/nym-gateway run --id your-id`. Basically, you want the full `/path/to/nym-gateway run --id whatever-your-node-id-is` + + +Then run: + +``` +systemctl enable nym-gateway.service +``` + +Start your node: + +``` +service nym-gateway start +``` + +This will cause your node to start at system boot time. If you restart your machine, the node will come back up automatically. + +You can also do `service nym-gateway stop` or `service nym-gateway restart`. + +Note: if you make any changes to your systemd script after you've enabled it, you will need to run: + +``` +systemctl daemon-reload +``` + +This lets your operating system know it's ok to reload the service configuration. + +## Gateway related Validator API endpoints +Numerous gateway related API endpoints are documented on the Validator API's [Swagger Documentation](https://validator.nymtech.net/api/swagger/index.html). There you can also try out various requests from your broswer, and download the response from the API. Swagger will also show you what commands it is running, so that you can run these from an app or from your CLI if you prefer. + +## Ports +All gateway specific port configuration can be found in `$HOME/.nym/gateways//config/config.toml`. If you do edit any port configs, remember to restart your gateway. + +### Gateway port reference +| Default port | Use | +|--------------|---------------------------| +| 1789 | Listen for Mixnet traffic | +| 9000 | Listen for Client traffic | diff --git a/documentation/operators/src/nodes/maintenance.md b/documentation/operators/src/nodes/maintenance.md new file mode 100644 index 0000000000..b2fe722d0a --- /dev/null +++ b/documentation/operators/src/nodes/maintenance.md @@ -0,0 +1,357 @@ +# Maintenance + + + + + +### Upgrading your mix node + +Upgrading your node is a two-step process: +* Updating the binary and `~/.nym/mixnodes//config.toml` on your VPS +* Updating the node information in the [mixnet smart contract](../nyx/mixnet-contract.md). **This is the information that is present on the [mixnet explorer](https://explorer.nymtech.net)**. + +#### Step 1: Upgrading your binary +Follow these steps to upgrade your mix node binary and update its config file: +* pause your mix node process. +* replace the existing binary with the newest binary (which you can either [compile yourself](https://nymtech.net/docs/binaries/building-nym.html) or grab from our [releases page](https://github.com/nymtech/nym/releases)). +* re-run `init` with the same values as you used initially. **This will just update the config file, it will not overwrite existing keys**. +* restart your mix node process with the new binary. + +#### Step 2: Updating your node information in the smart contract +Follow these steps to update the information about your mix node which is publically avaliable from the [Nym API](https://validator.nymtech.net/api/swagger/index.html) and information displayed on the [mixnet explorer](https://explorer.nymtech.net). + +You can either do this graphically via the Desktop Wallet, or the CLI. + +#### Updating node information via the Desktop Wallet +* Navigate to the `Bonding` page and click the `Node Settings` link in the top right corner: +\ +![Bonding page](../images/wallet-screenshots/bonding.png) + +* Update the fields in the `Node Settings` page and click `Submit changes to the blockchain`. +\ +![Node Settings Page](../images/wallet-screenshots/node_settings.png) + +#### Updating node information via the CLI +If you want to bond your mix node via the CLI, then check out the [relevant section in the Nym CLI](../../documentation/docs/src/tools/nym-cli.md#upgrade-a-mix-node) docs. + +--- + +## VPS Setup and Automation +### Configure your firewall +The following commands will allow you to set up a firewall using `ufw`. + +``` +# check if you have ufw installed +ufw version + +# if it is not installed, install with +sudo apt install ufw -y + +# enable ufw +sudo ufw enable + +# check the status of the firewall +sudo ufw status +``` + +Finally open your mix node's p2p port, as well as ports for ssh and ports `8000` and `1790` for verloc and measurement pings: + +``` +sudo ufw allow 1789,1790,8000,22/tcp + +# check the status of the firewall +sudo ufw status +``` + +For more information about your mix node's port configuration, check the [mix node port reference table](./mix-node-setup.md#mixnode-port-reference) below. + +### Automating your mix node with tmux and systemd + +It's useful to have the mix node automatically start at system boot time. + +#### tmux + +One way is to use `tmux` shell on top of your current VPS terminal. Tmux is a terminal multiplexer, it allows you to create several terminal windows and panes from a single terminal. Processes started in `tmux` keep running after closing the terminal as long as the given `tmux` window was not terminated. + +Use the following command to get `tmux`. + +Platform|Install Command +---|--- +Arch Linux|`pacman -S tmux` +Debian or Ubuntu|`apt install tmux` +Fedora|`dnf install tmux` +RHEL or CentOS|`yum install tmux` +macOS (using Homebrew|`brew install tmux` +macOS (using MacPorts)|`port install tmux` +openSUSE|`zypper install tmux` + +In case it didn't work for your distribution, see how to build `tmux` from [version control](https://github.com/tmux/tmux#from-version-control). + +**Running tmux** + +No when you installed tmux on your VPS, let's run a mixnode on tmux, which allows you to detach your terminal and let the mixnode run on its own on the VPS. + +* Pause your mixnode +* Start tmux with the command +``` +tmux +``` +* The terminal should stay in the same directory, just the layout changed into tmux default layout. +* Start the mixnode again with a command: +``` +./nym-mixnode run --id +``` +* Now, without closing the tmux window, you can close the whole terminal and the mixnode (and any other process running in tmux) will stay active. +* Next time just start your teminal, ssh into the VPS and run the following command to attach back to your previous session: +``` +tmux attach session +``` +* To see keybinding options of tmux press `ctrl`+`b` and after 1 second `?` + +#### systemd + +Here's a systemd service file to do that: + +```ini +[Unit] +Description=Nym Mixnode ({{mix_node_release_version}}) +StartLimitInterval=350 +StartLimitBurst=10 + +[Service] +User= +LimitNOFILE=65536 +ExecStart=/home///nym-mixnode run --id +KillSignal=SIGINT +Restart=on-failure +RestartSec=30 + +[Install] +WantedBy=multi-user.target +``` + +Put the above file onto your system at `/etc/systemd/system/nym-mixnode.service`. + +Change the `` in `ExecStart` to point at your mix node binary (`nym-mixnode`), and the `` so it is the user you are running as. + +If you have built nym in the `$HOME` directory on your server, and your username is `jetpanther`, then the start command might look like this: + +`ExecStart=/home/jetpanther/nym/target/release/nym-mixnode run --id your-id`. Basically, you want the full `/path/to/nym-mixnode run --id whatever-your-node-id-is` + +Then run: + +``` +systemctl enable nym-mixnode.service +``` + +Start your node: + +``` +service nym-mixnode start +``` + +This will cause your node to start at system boot time. If you restart your machine, the node will come back up automatically. + +You can also do `service nym-mixnode stop` or `service nym-mixnode restart`. + +Note: if you make any changes to your systemd script after you've enabled it, you will need to run: + +``` +systemctl daemon-reload +``` + +This lets your operating system know it's ok to reload the service configuration. + +#### Setting the ulimit + +Linux machines limit how many open files a user is allowed to have. This is called a `ulimit`. + +`ulimit` is 1024 by default on most systems. It needs to be set higher, because mix nodes make and receive a lot of connections to other nodes. + +If you see errors such as: + +``` +Failed to accept incoming connection - Os { code: 24, kind: Other, message: "Too many open files" } +``` + +This means that the operating system is preventing network connections from being made. + +##### Set the ulimit via `systemd` service file + +Query the `ulimit` of your mix node with: + +``` +grep -i "open files" /proc/$(ps -A -o pid,cmd|grep nym-mixnode | grep -v grep |head -n 1 | awk '{print $1}')/limits +``` + +You'll get back the hard and soft limits, which looks something like this: + +``` +Max open files 65536 65536 files +``` + +If your output is **the same as above**, your node will not encounter any `ulimit` related issues. + +However if either value is `1024`, you must raise the limit via the systemd service file. Add the line: + +``` +LimitNOFILE=65536 +``` + +Reload the daemon: + +``` +systemctl daemon-reload +``` + +or execute this as root for system-wide setting of `ulimit`: + +``` +echo "DefaultLimitNOFILE=65535" >> /etc/systemd/system.conf +``` + +Reboot your machine and restart your node. When it comes back, use `cat /proc/$(pidof nym-mixnode)/limits | grep "Max open files"` to make sure the limit has changed to 65535. + +##### Set the ulimit on `non-systemd` based distributions + +In case you chose tmux option for mixnode automatization, see your `ulimit` list by running: + +``` +ulimit -a + +# watch for the output line -n +-n: file descriptors 1024 +``` + +You can change it either by running a command: + +``` +ulimit -u -n 4096 +``` + +or editing `etc/security/conf` and add the following lines: + +``` +# Example hard limit for max opened files +username hard nofile 4096 + +# Example soft limit for max opened files +username soft nofile 4096 +``` + +Then reboot your server and restart your mixnode. + + + + + +#### Step 1: Upgrading your binary +Follow these steps to upgrade your binary and update its config file: +* pause your gateway process. +* replace the existing binary with the newest binary (which you can either compile yourself or grab from our [releases page](https://github.com/nymtech/nym/releases)). +* re-run `init` with the same values as you used initially. **This will just update the config file, it will not overwrite existing keys**. +* restart your gateway process with the new binary. + +> Do **not** use the `upgrade` command: there is a known error with the command that will be fixed in a subsequent release. + +#### Step 2: Updating your node information in the smart contract +Follow these steps to update the information about your node which is publically avaliable from the [Nym API](https://validator.nymtech.net/api/swagger/index.html) and information displayed on the [mixnet explorer](https://explorer.nymtech.net). + +You can either do this graphically via the Desktop Wallet, or the CLI. + +#### Updating node information via the Desktop Wallet +* Navigate to the `Bonding` page and click the `Node Settings` link in the top right corner: +![Bonding page](../images/wallet-screenshots/bonding.png) + +* Update the fields in the `Node Settings` page and click `Submit changes to the blockchain`. +![Node Settings Page](../images/wallet-screenshots/node_settings.png) + +#### Updating node information via the CLI +If you want to bond your mix node via the CLI, then check out the [relevant section in the Nym CLI](../tools/nym-cli.md#upgrade-a-mix-node) docs. + +--- + +## VPS Setup and Automation +### Configure your firewall +Although your gateway is now ready to receive traffic, your server may not be - the following commands will allow you to set up a properly configured firewall using `ufw`: + +``` +# check if you have ufw installed +ufw version +# if it is not installed, install with +sudo apt install ufw -y +# enable ufw +sudo ufw enable +# check the status of the firewall +sudo ufw status +``` + +Finally open your gateway's p2p port, as well as ports for ssh and incoming traffic connections: + +``` +sudo ufw allow 1789,22,9000/tcp +# check the status of the firewall +sudo ufw status +``` + +For more information about your gateway's port configuration, check the [gateway port reference table](#gateway-port-reference) below. + +### Automating your gateway with systemd +Although it's not totally necessary, it's useful to have the gateway automatically start at system boot time. Here's a systemd service file to do that: + +```ini +[Unit] +Description=Nym Gateway ({{platform_release_version}}) +StartLimitInterval=350 +StartLimitBurst=10 + +[Service] +User=nym +LimitNOFILE=65536 +ExecStart=/home/nym/nym-gateway run --id supergateway +KillSignal=SIGINT +Restart=on-failure +RestartSec=30 + +[Install] +WantedBy=multi-user.target +``` + +Put the above file onto your system at `/etc/systemd/system/nym-gateway.service`. + +Change the path in `ExecStart` to point at your gateway binary (`nym-gateway`), and the `User` so it is the user you are running as. + +If you have built nym on your server, and your username is `jetpanther`, then the start command might look like this: + +`ExecStart=/home/jetpanther/nym/target/release/nym-gateway run --id your-id`. Basically, you want the full `/path/to/nym-gateway run --id whatever-your-node-id-is` + + +Then run: + +``` +systemctl enable nym-gateway.service +``` + +Start your node: + +``` +service nym-gateway start +``` + +This will cause your node to start at system boot time. If you restart your machine, the node will come back up automatically. + +You can also do `service nym-gateway stop` or `service nym-gateway restart`. + +Note: if you make any changes to your systemd script after you've enabled it, you will need to run: + +``` +systemctl daemon-reload +``` + +This lets your operating system know it's ok to reload the service configuration. + +## Gateway related Validator API endpoints +Numerous gateway related API endpoints are documented on the Validator API's [Swagger Documentation](https://validator.nymtech.net/api/swagger/index.html). There you can also try out various requests from your broswer, and download the response from the API. Swagger will also show you what commands it is running, so that you can run these from an app or from your CLI if you prefer. diff --git a/documentation/operators/src/nodes/mix-node-setup.md b/documentation/operators/src/nodes/mix-node-setup.md index 723984ff39..68f6bf9090 100644 --- a/documentation/operators/src/nodes/mix-node-setup.md +++ b/documentation/operators/src/nodes/mix-node-setup.md @@ -28,42 +28,11 @@ The `nym-mixnode` binary is currently one point version ahead of the rest of the ## Preliminary steps -There are a couple of steps that need completing before starting to set up your mix node: - -- preparing your [wallet](https://nymtech.net/docs/wallet/desktop-wallet.html) -- requisitioning a VPS (Virtual Private Server) - -### Wallet preparation - -#### Mainnet - -Before you initialise and run your mixnode, head to our [website](https://nymtech.net/download/) and download the Nym wallet for your operating system. If pre-compiled binaries for your operating system aren't available, you can build the wallet yourself with instructions [here](https://nymtech.net/docs/wallet/desktop-wallet.html). - -If you don't already have one, please create a Nym address using the wallet, and fund it with tokens. The minimum amount required to bond a mixnode is 100 `NYM`, but make sure you have a bit more to account for gas costs. - -`NYM` can be purchased via Bity from the wallet itself with BTC or fiat, and is currently present on several [exchanges](https://www.coingecko.com/en/coins/nym#markets). - -> Remember that you can **only** use Cosmos `NYM` tokens to bond your mixnode. You **cannot** use ERC20 representations of `NYM` to run a node. - -#### Sandbox testnet - -Make sure to download a wallet and create an account as outlined above. Then head to our [token faucet](https://faucet.nymtech.net/) and get some tokens to use to bond it. - -### VPS Hardware Specs - -You will need to rent a VPS to run your mix node on. One key reason for this is that your node **must be able to send TCP data using both IPv4 and IPv6** (as other nodes you talk to may use either protocol). - -For the moment, we haven't put a great amount of effort into optimizing concurrency to increase throughput, so don't bother provisioning a beastly server with multiple cores. This will change when we get a chance to start doing performance optimizations in a more serious way. Sphinx packet decryption is CPU-bound, so once we optimize, more fast cores will be better. - -For now, see the below rough specs: - -- Processors: 2 cores are fine. Get the fastest CPUs you can afford. -- RAM: Memory requirements are very low - typically a mix node may use only a few hundred MB of RAM. -- Disks: The mixnodes require no disk space beyond a few bytes for the configuration files. +Make sure you do the preparation listed in the [preliminary steps page](../preliminary-steps.md) before setting up your mixnode. ## Mix node setup -Now that you have built the [codebase](https://nymtech.net/docs/binaries/building-nym.html), set up your [wallet](https://nymtech.net/docs/wallet/desktop-wallet.html), and have a VPS with the `nym-mixnode` binary, you can set up your mix node with the instructions below. +Now that you have built the [codebase](https://nymtech.net/docs/binaries/building-nym.html), set up your [wallet](https://nymtech.net/docs/wallet/desktop-wallet.html), and have a VPS with the `nym-mixnode` binary, you can set up your mix node with the instructions below. To begin, move to `/taget/release` directory from which you run the node commands: diff --git a/documentation/operators/src/preliminary-steps.md b/documentation/operators/src/preliminary-steps.md new file mode 100644 index 0000000000..1c738b34bb --- /dev/null +++ b/documentation/operators/src/preliminary-steps.md @@ -0,0 +1,43 @@ +# Preliminary Steps + +> The Nym mixnode, gateway and network requester binaries were built in the [building nym](https://nymtech.net/docs/binaries/building-nym.html) section. If you haven't yet built Nym and want to run the code, go there first. + +## Preliminary steps + +There are a couple of steps that need completing before starting to set up your mixnode, gateway or a network requester: + +- preparing your [wallet](https://nymtech.net/docs/wallet/desktop-wallet.html) +- requisitioning a VPS (Virtual Private Server) + +### Wallet preparation +#### Mainnet +Before you initialise and run your mixnode, head to our [website](https://nymtech.net/download/) and download the Nym wallet for your operating system. If pre-compiled binaries for your operating system aren't available, you can build the wallet yourself with instructions [here](https://nymtech.net/docs/wallet/desktop-wallet.html). + +If you don't already have one, please create a Nym address using the wallet, and fund it with tokens. The minimum amount required to bond a mixnode is 100 `NYM`, but make sure you have a bit more to account for gas costs. + +`NYM` can be purchased via Bity from the wallet itself with BTC or fiat, and is currently present on several [exchanges](https://www.coingecko.com/en/coins/nym#markets). + +> Remember that you can **only** use Cosmos `NYM` tokens to bond your mixnode. You **cannot** use ERC20 representations of `NYM` to run a node. + + +#### Sandbox testnet +Make sure to download a wallet and create an account as outlined above. Then head to our [token faucet](https://faucet.nymtech.net/) and get some tokens to use to bond it. + +### VPS Hardware Specs +You will need to rent a VPS to run your node on. One key reason for this is that your node **must be able to send TCP data using both IPv4 and IPv6** (as other nodes you talk to may use either protocol). + +For the moment, we haven't put a great amount of effort into optimizing concurrency to increase throughput, so don't bother provisioning a beastly server with multiple cores. This will change when we get a chance to start doing performance optimizations in a more serious way. Sphinx packet decryption is CPU-bound, so once we optimize, more fast cores will be better. + +For now, see the below rough specs: + +- Processors: 2 cores are fine. Get the fastest CPUs you can afford. + +#### For Nym Mixnode + +- RAM: Memory requirements are very low - typically a mix node may use only a few hundred MB of RAM. +- Disks: The mixnodes require no disk space beyond a few bytes for the configuration files. + +#### For Nym Gateway + +- RAM: Memory requirements depend on the amount of users your Gateway will be serving at any one time. If you're just going to be using it yourself, then minimal RAM is fine. **If you're running your Gateway as part of a Service Grant, get something with at least 4GB RAM.** +- Disks: much like the amount of RAM your Gateway could use, the amount of disk space required will vary with the amount of users your Gateway is serving. **If you're running your Gateway as part of a Service Grant, get something with at least 40GB storage.** From 141b9d396c415d8be6425805af4e7927b1caa1a4 Mon Sep 17 00:00:00 2001 From: serinko Date: Fri, 14 Jul 2023 17:17:07 +0200 Subject: [PATCH 005/361] operator-guides: made todo notes --- documentation/operators/src/faq.md | 2 ++ documentation/operators/src/nodes/gateway-setup.md | 8 +++++++- documentation/operators/src/nodes/mix-node-setup.md | 4 ++++ 3 files changed, 13 insertions(+), 1 deletion(-) diff --git a/documentation/operators/src/faq.md b/documentation/operators/src/faq.md index 5e1f5b72ad..e09a78c21c 100644 --- a/documentation/operators/src/faq.md +++ b/documentation/operators/src/faq.md @@ -10,5 +10,7 @@ Hi, do you know if it's on the roadmap for network nodes (requesters, socks5 cli you should be able to switch gateway (and overwrite keys) using —force-register +make a note that the keys will be overwritten in bold when you're noting it in the operators guides + ---> diff --git a/documentation/operators/src/nodes/gateway-setup.md b/documentation/operators/src/nodes/gateway-setup.md index 591cd3d1a9..ecd04d1005 100644 --- a/documentation/operators/src/nodes/gateway-setup.md +++ b/documentation/operators/src/nodes/gateway-setup.md @@ -1,3 +1,7 @@ + # Gateways > The Nym gateway was built in the [building nym](../binaries/building-nym.md) section. If you haven't yet built Nym and want to run the code, go there first. @@ -153,9 +157,11 @@ You can either do this graphically via the Desktop Wallet, or the CLI. #### Updating node information via the Desktop Wallet * Navigate to the `Bonding` page and click the `Node Settings` link in the top right corner: +/ ![Bonding page](../images/wallet-screenshots/bonding.png) * Update the fields in the `Node Settings` page and click `Submit changes to the blockchain`. +/ ![Node Settings Page](../images/wallet-screenshots/node_settings.png) #### Updating node information via the CLI @@ -244,7 +250,7 @@ This lets your operating system know it's ok to reload the service configuration Numerous gateway related API endpoints are documented on the Validator API's [Swagger Documentation](https://validator.nymtech.net/api/swagger/index.html). There you can also try out various requests from your broswer, and download the response from the API. Swagger will also show you what commands it is running, so that you can run these from an app or from your CLI if you prefer. ## Ports -All gateway specific port configuration can be found in `$HOME/.nym/gateways//config/config.toml`. If you do edit any port configs, remember to restart your gateway. +All gateway specific port configuration can be found in `$HOME/.nym/gateways//config/config.toml`. If you do edit any port configs, remember to restart your gateway. ### Gateway port reference | Default port | Use | diff --git a/documentation/operators/src/nodes/mix-node-setup.md b/documentation/operators/src/nodes/mix-node-setup.md index 68f6bf9090..dc27005feb 100644 --- a/documentation/operators/src/nodes/mix-node-setup.md +++ b/documentation/operators/src/nodes/mix-node-setup.md @@ -10,6 +10,10 @@ TODO - [x] Add second console output to Initialising your mixnode manually as the automatized is not working - [ ] Point users to understand the requirements for becoming a part of the active set. - [ ] Include how to move a mix node from one VPS to another while keeping the delegation etc (ie init a new mix node and move `/home/nym/.nym/mixnodes/data/*` there ?) + - copy that dir over to the new vps + - re-run init - remember that init doesn't overwrite existing keys - to generate a config with the new listening address etc + - change the node info via the wallet + - Since otherwise their keys will point to the old IP address in the smart contract, and their node will (a) not be able to be connected to, and (b) fail uptime checks - [ ] Explain more about self hosted nodes in the 6.1 not only as a troubleshooting ---> From cf71d655e294e97292f745cd75b42aaac391f0c0 Mon Sep 17 00:00:00 2001 From: serinko <97586125+serinko@users.noreply.github.com> Date: Fri, 14 Jul 2023 19:21:25 +0200 Subject: [PATCH 006/361] operators-guide: moved repeating to maintenance.md, shortened and cleaned the existing guides --- .../operators/src/nodes/gateway-setup.md | 126 +------ .../operators/src/nodes/maintenance.md | 249 ++++++------ .../operators/src/nodes/mix-node-setup.md | 356 +----------------- 3 files changed, 142 insertions(+), 589 deletions(-) diff --git a/documentation/operators/src/nodes/gateway-setup.md b/documentation/operators/src/nodes/gateway-setup.md index ecd04d1005..eaacfaf52a 100644 --- a/documentation/operators/src/nodes/gateway-setup.md +++ b/documentation/operators/src/nodes/gateway-setup.md @@ -1,6 +1,6 @@ # Gateways @@ -133,127 +133,7 @@ The `run` command starts the gateway: ``` ./nym-gateway run --id ``` +## Maintenance -### Upgrading your gateway -Upgrading your node is a two-step process: -* Updating the binary and `config.toml` on your VPS -* Updating the node information in the [mixnet smart contract](../nyx/mixnet-contract.md). **This is the information that is present on the [mixnet explorer](https://explorer.nymtech.net)**. +For gateway upgrade, firewall setup, port configuration, API endpoints, VPS suggestions, automation and more, see the [maintenance page](./maintenance.md) ->These instructions are specifically regarding upgrading your gateway binary from one version to another. If you want to change node information such as the listening port, you can do this by clicking the `node settings` tab in the `Bonding` page of the wallet. - -#### Step 1: Upgrading your binary -Follow these steps to upgrade your binary and update its config file: -* pause your gateway process. -* replace the existing binary with the newest binary (which you can either compile yourself or grab from our [releases page](https://github.com/nymtech/nym/releases)). -* re-run `init` with the same values as you used initially. **This will just update the config file, it will not overwrite existing keys**. -* restart your gateway process with the new binary. - -> Do **not** use the `upgrade` command: there is a known error with the command that will be fixed in a subsequent release. - -#### Step 2: Updating your node information in the smart contract -Follow these steps to update the information about your node which is publically avaliable from the [Nym API](https://validator.nymtech.net/api/swagger/index.html) and information displayed on the [mixnet explorer](https://explorer.nymtech.net). - -You can either do this graphically via the Desktop Wallet, or the CLI. - -#### Updating node information via the Desktop Wallet -* Navigate to the `Bonding` page and click the `Node Settings` link in the top right corner: -/ -![Bonding page](../images/wallet-screenshots/bonding.png) - -* Update the fields in the `Node Settings` page and click `Submit changes to the blockchain`. -/ -![Node Settings Page](../images/wallet-screenshots/node_settings.png) - -#### Updating node information via the CLI -If you want to bond your mix node via the CLI, then check out the [relevant section in the Nym CLI](../tools/nym-cli.md#upgrade-a-mix-node) docs. - -## VPS Setup and Automation -### Configure your firewall -Although your gateway is now ready to receive traffic, your server may not be - the following commands will allow you to set up a properly configured firewall using `ufw`: - -``` -# check if you have ufw installed -ufw version -# if it is not installed, install with -sudo apt install ufw -y -# enable ufw -sudo ufw enable -# check the status of the firewall -sudo ufw status -``` - -Finally open your gateway's p2p port, as well as ports for ssh and incoming traffic connections: - -``` -sudo ufw allow 1789,22,9000/tcp -# check the status of the firewall -sudo ufw status -``` - -For more information about your gateway's port configuration, check the [gateway port reference table](#gateway-port-reference) below. - -### Automating your gateway with systemd -Although it's not totally necessary, it's useful to have the gateway automatically start at system boot time. Here's a systemd service file to do that: - -```ini -[Unit] -Description=Nym Gateway ({{platform_release_version}}) -StartLimitInterval=350 -StartLimitBurst=10 - -[Service] -User=nym -LimitNOFILE=65536 -ExecStart=/home/nym/nym-gateway run --id supergateway -KillSignal=SIGINT -Restart=on-failure -RestartSec=30 - -[Install] -WantedBy=multi-user.target -``` - -Put the above file onto your system at `/etc/systemd/system/nym-gateway.service`. - -Change the path in `ExecStart` to point at your gateway binary (`nym-gateway`), and the `User` so it is the user you are running as. - -If you have built nym on your server, and your username is `jetpanther`, then the start command might look like this: - -`ExecStart=/home/jetpanther/nym/target/release/nym-gateway run --id your-id`. Basically, you want the full `/path/to/nym-gateway run --id whatever-your-node-id-is` - - -Then run: - -``` -systemctl enable nym-gateway.service -``` - -Start your node: - -``` -service nym-gateway start -``` - -This will cause your node to start at system boot time. If you restart your machine, the node will come back up automatically. - -You can also do `service nym-gateway stop` or `service nym-gateway restart`. - -Note: if you make any changes to your systemd script after you've enabled it, you will need to run: - -``` -systemctl daemon-reload -``` - -This lets your operating system know it's ok to reload the service configuration. - -## Gateway related Validator API endpoints -Numerous gateway related API endpoints are documented on the Validator API's [Swagger Documentation](https://validator.nymtech.net/api/swagger/index.html). There you can also try out various requests from your broswer, and download the response from the API. Swagger will also show you what commands it is running, so that you can run these from an app or from your CLI if you prefer. - -## Ports -All gateway specific port configuration can be found in `$HOME/.nym/gateways//config/config.toml`. If you do edit any port configs, remember to restart your gateway. - -### Gateway port reference -| Default port | Use | -|--------------|---------------------------| -| 1789 | Listen for Mixnet traffic | -| 9000 | Listen for Client traffic | diff --git a/documentation/operators/src/nodes/maintenance.md b/documentation/operators/src/nodes/maintenance.md index b2fe722d0a..583bb6aa47 100644 --- a/documentation/operators/src/nodes/maintenance.md +++ b/documentation/operators/src/nodes/maintenance.md @@ -1,47 +1,46 @@ # Maintenance - +## Upgrading your node - - -### Upgrading your mix node +> The process is the similar for mixnode, gateway and network requester. In the following steps we use a placeholder `` in the commands, please change it for the type of node you want to upgrade. Any particularities for the given type of node are included. Upgrading your node is a two-step process: -* Updating the binary and `~/.nym/mixnodes//config.toml` on your VPS -* Updating the node information in the [mixnet smart contract](../nyx/mixnet-contract.md). **This is the information that is present on the [mixnet explorer](https://explorer.nymtech.net)**. +* Updating the binary and `~/.nym///config.toml` on your VPS +* Updating the node information in the [mixnet smart contract](https://nymtech.net/docs/nyx/mixnet-contract.html). **This is the information that is present on the [mixnet explorer](https://explorer.nymtech.net)**. -#### Step 1: Upgrading your binary +### Step 1: Upgrading your binary Follow these steps to upgrade your mix node binary and update its config file: * pause your mix node process. * replace the existing binary with the newest binary (which you can either [compile yourself](https://nymtech.net/docs/binaries/building-nym.html) or grab from our [releases page](https://github.com/nymtech/nym/releases)). * re-run `init` with the same values as you used initially. **This will just update the config file, it will not overwrite existing keys**. * restart your mix node process with the new binary. -#### Step 2: Updating your node information in the smart contract -Follow these steps to update the information about your mix node which is publically avaliable from the [Nym API](https://validator.nymtech.net/api/swagger/index.html) and information displayed on the [mixnet explorer](https://explorer.nymtech.net). +### Step 2: Updating your node information in the smart contract +Follow these steps to update the information about your `` which is publicly available from the [Nym API](https://validator.nymtech.net/api/swagger/index.html) and information displayed on the [mixnet explorer](https://explorer.nymtech.net). You can either do this graphically via the Desktop Wallet, or the CLI. -#### Updating node information via the Desktop Wallet -* Navigate to the `Bonding` page and click the `Node Settings` link in the top right corner: -\ +### Updating node information via the Desktop Wallet +* Navigate to the `Bonding` page and click the `Node Settings` link in the top right corner: + ![Bonding page](../images/wallet-screenshots/bonding.png) -* Update the fields in the `Node Settings` page and click `Submit changes to the blockchain`. -\ +* Update the fields in the `Node Settings` page and click `Submit changes to the blockchain`. + ![Node Settings Page](../images/wallet-screenshots/node_settings.png) -#### Updating node information via the CLI -If you want to bond your mix node via the CLI, then check out the [relevant section in the Nym CLI](../../documentation/docs/src/tools/nym-cli.md#upgrade-a-mix-node) docs. +### Updating node information via the CLI +If you want to bond your `` via the CLI, then check out the [relevant section in the Nym CLI](../../documentation/docs/src/tools/nym-cli.md#upgrade-a-mix-node) docs. ---- ## VPS Setup and Automation ### Configure your firewall -The following commands will allow you to set up a firewall using `ufw`. +Although your `` is now ready to recieve traffic, your server may not be. The following commands will allow you to set up a firewall using `ufw`. ``` # check if you have ufw installed @@ -57,20 +56,24 @@ sudo ufw enable sudo ufw status ``` -Finally open your mix node's p2p port, as well as ports for ssh and ports `8000` and `1790` for verloc and measurement pings: +Finally open your `` p2p port, as well as ports for ssh and ports for verloc and measurement pings: ``` +# for mixnode sudo ufw allow 1789,1790,8000,22/tcp +# for gateway +sudo ufw allow 1789,22,9000/tcp + # check the status of the firewall sudo ufw status ``` -For more information about your mix node's port configuration, check the [mix node port reference table](./mix-node-setup.md#mixnode-port-reference) below. +For more information about your mix node's port configuration, check the [mix node port reference table](./mix-node-setup.md#mixnode-port-reference) or [gateway port reference table](https://nymtech.net/docs/nodes/gateway-setup.html#gateway-port-reference) below. -### Automating your mix node with tmux and systemd +### Automating your node with tmux and systemd -It's useful to have the mix node automatically start at system boot time. +Although it’s not totally necessary, it's useful to have the mix node automatically start at system boot time. #### tmux @@ -92,22 +95,22 @@ In case it didn't work for your distribution, see how to build `tmux` from [vers **Running tmux** -No when you installed tmux on your VPS, let's run a mixnode on tmux, which allows you to detach your terminal and let the mixnode run on its own on the VPS. +No when you installed tmux on your VPS, let's run a mixnode on tmux, which allows you to detach your terminal and let your `` run on its own on the VPS. -* Pause your mixnode +* Pause your `` * Start tmux with the command ``` tmux ``` -* The terminal should stay in the same directory, just the layout changed into tmux default layout. -* Start the mixnode again with a command: +* The tmux terminal should open in the same working directory, just the layout changed into tmux default layout. +* Start the `` again with a command: ``` -./nym-mixnode run --id +./ run --id ``` -* Now, without closing the tmux window, you can close the whole terminal and the mixnode (and any other process running in tmux) will stay active. +* Now, without closing the tmux window, you can close the whole terminal and the `` (and any other process running in tmux) will stay active. * Next time just start your teminal, ssh into the VPS and run the following command to attach back to your previous session: ``` -tmux attach session +tmux attach-session ``` * To see keybinding options of tmux press `ctrl`+`b` and after 1 second `?` @@ -115,6 +118,8 @@ tmux attach session Here's a systemd service file to do that: +For mixnode: + ```ini [Unit] Description=Nym Mixnode ({{mix_node_release_version}}) @@ -133,29 +138,60 @@ RestartSec=30 WantedBy=multi-user.target ``` -Put the above file onto your system at `/etc/systemd/system/nym-mixnode.service`. +* Put the above file onto your system at `/etc/systemd/system/nym-mixnode.service`. -Change the `` in `ExecStart` to point at your mix node binary (`nym-mixnode`), and the `` so it is the user you are running as. +For gateway: -If you have built nym in the `$HOME` directory on your server, and your username is `jetpanther`, then the start command might look like this: +```ini +[Unit] +Description=Nym Gateway ({{platform_release_version}}) +StartLimitInterval=350 +StartLimitBurst=10 -`ExecStart=/home/jetpanther/nym/target/release/nym-mixnode run --id your-id`. Basically, you want the full `/path/to/nym-mixnode run --id whatever-your-node-id-is` +[Service] +User=nym +LimitNOFILE=65536 +ExecStart=/home///nym-gateway run --id +KillSignal=SIGINT +Restart=on-failure +RestartSec=30 + +[Install] +WantedBy=multi-user.target +``` + +* Put the above file onto your system at `/etc/systemd/system/nym-gateway.service`. + +Change the `` in `ExecStart` to point at your `` binary (`nym-mixnode` or `nym-gateway), and the `` so it is the user you are running as. + +If you have built nym in the `$HOME` directory on your server, and your username is `jetpanther`, then the start command for nym mixnode might look like this: + +`ExecStart=/home/jetpanther/nym/target/release/nym-mixnode run --id `. Basically, you want the full `/path/to/nym-mixnode run --id whatever-your-node-id-is` Then run: ``` +# for mixnode systemctl enable nym-mixnode.service + +# for gateway +systemctl enable nym-gateway.service ``` Start your node: ``` +# for mixnode service nym-mixnode start + +# for gateway +service nym-gateway start + ``` This will cause your node to start at system boot time. If you restart your machine, the node will come back up automatically. -You can also do `service nym-mixnode stop` or `service nym-mixnode restart`. +You can also do `service stop` or `service restart`. Note: if you make any changes to your systemd script after you've enabled it, you will need to run: @@ -165,7 +201,7 @@ systemctl daemon-reload This lets your operating system know it's ok to reload the service configuration. -#### Setting the ulimit +### Setting the ulimit Linux machines limit how many open files a user is allowed to have. This is called a `ulimit`. @@ -179,7 +215,9 @@ Failed to accept incoming connection - Os { code: 24, kind: Other, message: "Too This means that the operating system is preventing network connections from being made. -##### Set the ulimit via `systemd` service file +#### Set the ulimit via `systemd` service file + +The ulimit setup is relevant for maintenance of nym mixnode only. Query the `ulimit` of your mix node with: @@ -215,7 +253,7 @@ echo "DefaultLimitNOFILE=65535" >> /etc/systemd/system.conf Reboot your machine and restart your node. When it comes back, use `cat /proc/$(pidof nym-mixnode)/limits | grep "Max open files"` to make sure the limit has changed to 65535. -##### Set the ulimit on `non-systemd` based distributions +#### Set the ulimit on `non-systemd` based distributions In case you chose tmux option for mixnode automatization, see your `ulimit` list by running: @@ -244,114 +282,89 @@ username soft nofile 4096 Then reboot your server and restart your mixnode. +## Virtual IPs and hosting via Google & AWS +For true internet decentralization we encourage operators to use diverse VPS providers instead of the largest companies offering such services. If for some reasons you have already running AWS or Google and want to setup a `` there, please read the following. +On some services (AWS, Google, etc) the machine's available bind address is not the same as the public IP address. In this case, bind `--host` to the local machine address returned by `$(curl ifconfig.me)`, but also specify `--announce-host` with the public IP. Please make sure that you pass the correct, routable `--announce-host`. - - -#### Step 1: Upgrading your binary -Follow these steps to upgrade your binary and update its config file: -* pause your gateway process. -* replace the existing binary with the newest binary (which you can either compile yourself or grab from our [releases page](https://github.com/nymtech/nym/releases)). -* re-run `init` with the same values as you used initially. **This will just update the config file, it will not overwrite existing keys**. -* restart your gateway process with the new binary. - -> Do **not** use the `upgrade` command: there is a known error with the command that will be fixed in a subsequent release. - -#### Step 2: Updating your node information in the smart contract -Follow these steps to update the information about your node which is publically avaliable from the [Nym API](https://validator.nymtech.net/api/swagger/index.html) and information displayed on the [mixnet explorer](https://explorer.nymtech.net). - -You can either do this graphically via the Desktop Wallet, or the CLI. - -#### Updating node information via the Desktop Wallet -* Navigate to the `Bonding` page and click the `Node Settings` link in the top right corner: -![Bonding page](../images/wallet-screenshots/bonding.png) - -* Update the fields in the `Node Settings` page and click `Submit changes to the blockchain`. -![Node Settings Page](../images/wallet-screenshots/node_settings.png) - -#### Updating node information via the CLI -If you want to bond your mix node via the CLI, then check out the [relevant section in the Nym CLI](../tools/nym-cli.md#upgrade-a-mix-node) docs. - ---- - -## VPS Setup and Automation -### Configure your firewall -Although your gateway is now ready to receive traffic, your server may not be - the following commands will allow you to set up a properly configured firewall using `ufw`: +For example, on a Google machine, you may see the following output from the `ifconfig` command: ``` -# check if you have ufw installed -ufw version -# if it is not installed, install with -sudo apt install ufw -y -# enable ufw -sudo ufw enable -# check the status of the firewall -sudo ufw status +ens4: flags=4163 mtu 1460 + inet 10.126.5.7 netmask 255.255.255.255 broadcast 0.0.0.0 + ... ``` -Finally open your gateway's p2p port, as well as ports for ssh and incoming traffic connections: +The `ens4` interface has the IP `10.126.5.7`. But this isn't the public IP of the machine, it's the IP of the machine on Google's internal network. Google uses virtual routing, so the public IP of this machine is something else, maybe `36.68.243.18`. +`./nym-mixnode init --host 10.126.5.7`, initalises the mix node, but no packets will be routed because `10.126.5.7` is not on the public internet. + +Trying `nym-mixnode init --host 36.68.243.18`, you'll get back a startup error saying `AddrNotAvailable`. This is because the mix node doesn't know how to bind to a host that's not in the output of `ifconfig`. + +The right thing to do in this situation is to init with a command: ``` -sudo ufw allow 1789,22,9000/tcp -# check the status of the firewall -sudo ufw status +./nym-mixnode init --host 10.126.5.7 --announce-host 36.68.243.18 ``` -For more information about your gateway's port configuration, check the [gateway port reference table](#gateway-port-reference) below. +This will bind the mix node to the available host `10.126.5.7`, but announce the mix node's public IP to the directory server as `36.68.243.18`. It's up to you as a node operator to ensure that your public and private IPs match up properly. -### Automating your gateway with systemd -Although it's not totally necessary, it's useful to have the gateway automatically start at system boot time. Here's a systemd service file to do that: +To find the right IP configuration, contact your VPS provider for support. -```ini -[Unit] -Description=Nym Gateway ({{platform_release_version}}) -StartLimitInterval=350 -StartLimitBurst=10 +## Nym API (previously 'Validator API') endpoints +Numerous API endpoints are documented on the Nym API (previously 'Validator API')'s [Swagger Documentation](https://validator.nymtech.net/api/swagger/index.html). There you can also try out various requests from your browser, and download the response from the API. Swagger will also show you what commands it is running, so that you can run these from an app or from your CLI if you prefer. -[Service] -User=nym -LimitNOFILE=65536 -ExecStart=/home/nym/nym-gateway run --id supergateway -KillSignal=SIGINT -Restart=on-failure -RestartSec=30 +### Mix node Reward Estimation API endpoint -[Install] -WantedBy=multi-user.target -``` +The Reward Estimation API endpoint allows mixnode operators to estimate the rewards they could earn for running a Nym mixnode with a specific `MIX_ID`. -Put the above file onto your system at `/etc/systemd/system/nym-gateway.service`. +> The `` can be found in the "Mix ID" column of the [Network Explorer](https://explorer.nymtech.net/network-components/mixnodes/active). -Change the path in `ExecStart` to point at your gateway binary (`nym-gateway`), and the `User` so it is the user you are running as. +The endpoint is a particularly common for mixnode operators as it can provide an estimate of potential earnings based on factors such as the amount of traffic routed through the mixnode, the quality of the mixnode's performance, and the overall demand for mixnodes in the network. This information can be useful for mixnode operators in deciding whether or not to run a mix node and in optimizing its operations for maximum profitability. -If you have built nym on your server, and your username is `jetpanther`, then the start command might look like this: - -`ExecStart=/home/jetpanther/nym/target/release/nym-gateway run --id your-id`. Basically, you want the full `/path/to/nym-gateway run --id whatever-your-node-id-is` - - -Then run: +Using this API endpoint returns information about the Reward Estimation: ``` -systemctl enable nym-gateway.service +/status/mixnode//reward-estimation ``` -Start your node: +Query Response: ``` -service nym-gateway start + "estimation": { + "total_node_reward": "942035.916721770541325331", + "operator": "161666.263307386408152071", + "delegates": "780369.65341438413317326", + "operating_cost": "54444.444444444444444443" + }, ``` -This will cause your node to start at system boot time. If you restart your machine, the node will come back up automatically. +> The unit of value is measured in `uNYM`. -You can also do `service nym-gateway stop` or `service nym-gateway restart`. +- `estimated_total_node_reward` - An estimate of the total amount of rewards that a particular mix node can expect to receive during the current epoch. This value is calculated by the Nym Validator based on a number of factors, including the current state of the network, the number of mix nodes currently active in the network, and the amount of network traffic being processed by the mix node. -Note: if you make any changes to your systemd script after you've enabled it, you will need to run: +- `estimated_operator_reward` - An estimate of the amount of rewards that a particular mix node operator can expect to receive. This value is calculated by the Nym Validator based on a number of factors, including the amount of traffic being processed by the mix node, the quality of service provided by the mix node, and the operator's stake in the network. -``` -systemctl daemon-reload -``` +- `estimated_delegators_reward` - An estimate of the amount of rewards that mix node delegators can expect to receive individually. This value is calculated by the Nym Validator based on a number of factors, including the amount of traffic being processed by the mix node, the quality of service provided by the mix node, and the delegator's stake in the network. -This lets your operating system know it's ok to reload the service configuration. +- `estimated_node_profit` - An estimate of the profit that a particular mix node operator can expect to earn. This value is calculated by subtracting the mix node operator's `operating_costs` from their `estimated_operator_reward` for the current epoch. -## Gateway related Validator API endpoints -Numerous gateway related API endpoints are documented on the Validator API's [Swagger Documentation](https://validator.nymtech.net/api/swagger/index.html). There you can also try out various requests from your broswer, and download the response from the API. Swagger will also show you what commands it is running, so that you can run these from an app or from your CLI if you prefer. +- `estimated_operator_cost` - An estimate of the total cost that a particular mix node operator can expect to incur for their participation. This value is calculated by the Nym Validator based on a number of factors, including the cost of running a mix node, such as server hosting fees, and other expenses associated with operating the mix node. + +## Ports +All ``-specific port configuration can be found in `$HOME/.nym///config/config.toml`. If you do edit any port configs, remember to restart your mix node. + +### Mix node port reference +| Default port | Use | +| ------------ | ------------------------- | +| `1789` | Listen for mixnet traffic | +| `1790` | Listen for VerLoc traffic | +| `8000` | Metrics http API endpoint | + + +### Gateway port reference +| Default port | Use | +|--------------|---------------------------| +| `1789` | Listen for Mixnet traffic | +| `9000` | Listen for Client traffic | + +/ diff --git a/documentation/operators/src/nodes/mix-node-setup.md b/documentation/operators/src/nodes/mix-node-setup.md index dc27005feb..8690b17806 100644 --- a/documentation/operators/src/nodes/mix-node-setup.md +++ b/documentation/operators/src/nodes/mix-node-setup.md @@ -1,6 +1,6 @@ # Mix Nodes @@ -198,263 +199,16 @@ Keep reading to find out more about configuration options or troubleshooting if Have a look at the saved configuration files in `$HOME/.nym/mixnodes/` to see more configuration options. -### Describe your mix node (optional) +## Node Description (optional) In order to easily identify your node via human-readable information later on in the development of the testnet when delegated staking is implemented, you can `describe` your mixnode with the following command: ``` ./nym-mixnode describe --id ``` +Node description is a short text that describes your node. It is displayed in the `./nym-mixnode list` command and in the `./nym-mixnode node-details --id ` command. It also shows up in the node explorer to let people know what your node is about and link to your website. -> Remember to restart your mix node process in order for the new description to be propagated. - -### Upgrading your mix node - -Upgrading your node is a two-step process: -* Updating the binary and `~/.nym/mixnodes//config.toml` on your VPS -* Updating the node information in the [mixnet smart contract](../nyx/mixnet-contract.md). **This is the information that is present on the [mixnet explorer](https://explorer.nymtech.net)**. - -#### Step 1: Upgrading your binary -Follow these steps to upgrade your mix node binary and update its config file: -* pause your mix node process. -* replace the existing binary with the newest binary (which you can either [compile yourself](https://nymtech.net/docs/binaries/building-nym.html) or grab from our [releases page](https://github.com/nymtech/nym/releases)). -* re-run `init` with the same values as you used initially. **This will just update the config file, it will not overwrite existing keys**. -* restart your mix node process with the new binary. - -#### Step 2: Updating your node information in the smart contract -Follow these steps to update the information about your mix node which is publically avaliable from the [Nym API](https://validator.nymtech.net/api/swagger/index.html) and information displayed on the [mixnet explorer](https://explorer.nymtech.net). - -You can either do this graphically via the Desktop Wallet, or the CLI. - -#### Updating node information via the Desktop Wallet -* Navigate to the `Bonding` page and click the `Node Settings` link in the top right corner: -\ -![Bonding page](../images/wallet-screenshots/bonding.png) - -* Update the fields in the `Node Settings` page and click `Submit changes to the blockchain`. -\ -![Node Settings Page](../images/wallet-screenshots/node_settings.png) - -#### Updating node information via the CLI -If you want to bond your mix node via the CLI, then check out the [relevant section in the Nym CLI](../../documentation/docs/src/tools/nym-cli.md#upgrade-a-mix-node) docs. - -### Displaying mix node information - -You can always check the details of your mix node with the `node-details` command: - -``` -./nym-mixnode node-details --id -``` - -## VPS Setup and Automation -### Configure your firewall -The following commands will allow you to set up a firewall using `ufw`. - -``` -# check if you have ufw installed -ufw version - -# if it is not installed, install with -sudo apt install ufw -y - -# enable ufw -sudo ufw enable - -# check the status of the firewall -sudo ufw status -``` - -Finally open your mix node's p2p port, as well as ports for ssh and ports `8000` and `1790` for verloc and measurement pings: - -``` -sudo ufw allow 1789,1790,8000,22/tcp - -# check the status of the firewall -sudo ufw status -``` - -For more information about your mix node's port configuration, check the [mix node port reference table](./mix-node-setup.md#mixnode-port-reference) below. - -### Automating your mix node with tmux and systemd - -It's useful to have the mix node automatically start at system boot time. - -#### tmux - -One way is to use `tmux` shell on top of your current VPS terminal. Tmux is a terminal multiplexer, it allows you to create several terminal windows and panes from a single terminal. Processes started in `tmux` keep running after closing the terminal as long as the given `tmux` window was not terminated. - -Use the following command to get `tmux`. - -Platform|Install Command ----|--- -Arch Linux|`pacman -S tmux` -Debian or Ubuntu|`apt install tmux` -Fedora|`dnf install tmux` -RHEL or CentOS|`yum install tmux` -macOS (using Homebrew|`brew install tmux` -macOS (using MacPorts)|`port install tmux` -openSUSE|`zypper install tmux` - -In case it didn't work for your distribution, see how to build `tmux` from [version control](https://github.com/tmux/tmux#from-version-control). - -**Running tmux** - -No when you installed tmux on your VPS, let's run a mixnode on tmux, which allows you to detach your terminal and let the mixnode run on its own on the VPS. - -* Pause your mixnode -* Start tmux with the command -``` -tmux -``` -* The terminal should stay in the same directory, just the layout changed into tmux default layout. -* Start the mixnode again with a command: -``` -./nym-mixnode run --id -``` -* Now, without closing the tmux window, you can close the whole terminal and the mixnode (and any other process running in tmux) will stay active. -* Next time just start your teminal, ssh into the VPS and run the following command to attach back to your previous session: -``` -tmux attach session -``` -* To see keybinding options of tmux press `ctrl`+`b` and after 1 second `?` - -#### systemd - -Here's a systemd service file to do that: - -```ini -[Unit] -Description=Nym Mixnode ({{mix_node_release_version}}) -StartLimitInterval=350 -StartLimitBurst=10 - -[Service] -User= -LimitNOFILE=65536 -ExecStart=/home///nym-mixnode run --id -KillSignal=SIGINT -Restart=on-failure -RestartSec=30 - -[Install] -WantedBy=multi-user.target -``` - -Put the above file onto your system at `/etc/systemd/system/nym-mixnode.service`. - -Change the `` in `ExecStart` to point at your mix node binary (`nym-mixnode`), and the `` so it is the user you are running as. - -If you have built nym in the `$HOME` directory on your server, and your username is `jetpanther`, then the start command might look like this: - -`ExecStart=/home/jetpanther/nym/target/release/nym-mixnode run --id your-id`. Basically, you want the full `/path/to/nym-mixnode run --id whatever-your-node-id-is` - -Then run: - -``` -systemctl enable nym-mixnode.service -``` - -Start your node: - -``` -service nym-mixnode start -``` - -This will cause your node to start at system boot time. If you restart your machine, the node will come back up automatically. - -You can also do `service nym-mixnode stop` or `service nym-mixnode restart`. - -Note: if you make any changes to your systemd script after you've enabled it, you will need to run: - -``` -systemctl daemon-reload -``` - -This lets your operating system know it's ok to reload the service configuration. - -#### Setting the ulimit - -Linux machines limit how many open files a user is allowed to have. This is called a `ulimit`. - -`ulimit` is 1024 by default on most systems. It needs to be set higher, because mix nodes make and receive a lot of connections to other nodes. - -If you see errors such as: - -``` -Failed to accept incoming connection - Os { code: 24, kind: Other, message: "Too many open files" } -``` - -This means that the operating system is preventing network connections from being made. - -##### Set the ulimit via `systemd` service file - -Query the `ulimit` of your mix node with: - -``` -grep -i "open files" /proc/$(ps -A -o pid,cmd|grep nym-mixnode | grep -v grep |head -n 1 | awk '{print $1}')/limits -``` - -You'll get back the hard and soft limits, which looks something like this: - -``` -Max open files 65536 65536 files -``` - -If your output is **the same as above**, your node will not encounter any `ulimit` related issues. - -However if either value is `1024`, you must raise the limit via the systemd service file. Add the line: - -``` -LimitNOFILE=65536 -``` - -Reload the daemon: - -``` -systemctl daemon-reload -``` - -or execute this as root for system-wide setting of `ulimit`: - -``` -echo "DefaultLimitNOFILE=65535" >> /etc/systemd/system.conf -``` - -Reboot your machine and restart your node. When it comes back, use `cat /proc/$(pidof nym-mixnode)/limits | grep "Max open files"` to make sure the limit has changed to 65535. - -##### Set the ulimit on `non-systemd` based distributions - -In case you chose tmux option for mixnode automatization, see your `ulimit` list by running: - -``` -ulimit -a - -# watch for the output line -n --n: file descriptors 1024 -``` - -You can change it either by running a command: - -``` -ulimit -u -n 4096 -``` - -or editing `etc/security/conf` and add the following lines: - -``` -# Example hard limit for max opened files -username hard nofile 4096 - -# Example soft limit for max opened files -username soft nofile 4096 -``` - -Then reboot your server and restart your mixnode. - -## Node Description -Node description is a short text that describes your node. It is displayed in the `./nym-mixnode list` command and in the `./nym-mixnode node-details` command. It also shows up in the node explorer to let people know what your node is about and link to your website. - -To set your node description, create a file called `description.toml` and put it in the same directory as your `config.toml` file (`~/.nym/mixnodes//description.toml`). The file should look like this example: +You can set your node description, by creating a file called `description.toml` and put it in the same directory as your `config.toml` file (`~/.nym/mixnodes//description.toml`). The file should look like this example: ```toml name = "Winston Smith" @@ -463,7 +217,7 @@ link = "https://nymtech.net" location = "Giza, Egypt" ``` -You will need to restart your node for the changes to take effect. +> Remember to restart your mix node process in order for the new description to be propagated. ## Node Families @@ -573,101 +327,7 @@ For more details see [Troubleshooting FAQ](../nodes/troubleshooting.md) -### Virtual IPs and hosting via Google & AWS -For tru internet decentralization we encourage operators to use diverse VPS providers instead of the largest companies offering such services. If for some reasons you have already running AWS or Google and want to setup a mixnode there, please read the following. +## Maintenance -On some services (AWS, Google, etc) the machine's available bind address is not the same as the public IP address. In this case, bind `--host` to the local machine address returned by `$(curl ifconfig.me)`, but also specify `--announce-host` with the public IP. Please make sure that you pass the correct, routable `--announce-host`. - -For example, on a Google machine, you may see the following output from the `ifconfig` command: - -``` -ens4: flags=4163 mtu 1460 - inet 10.126.5.7 netmask 255.255.255.255 broadcast 0.0.0.0 - ... -``` - -The `ens4` interface has the IP `10.126.5.7`. But this isn't the public IP of the machine, it's the IP of the machine on Google's internal network. Google uses virtual routing, so the public IP of this machine is something else, maybe `36.68.243.18`. - -`./nym-mixnode init --host 10.126.5.7`, initalises the mix node, but no packets will be routed because `10.126.5.7` is not on the public internet. - -Trying `nym-mixnode init --host 36.68.243.18`, you'll get back a startup error saying `AddrNotAvailable`. This is because the mix node doesn't know how to bind to a host that's not in the output of `ifconfig`. - -The right thing to do in this situation is to init with a command: -``` -./nym-mixnode init --host 10.126.5.7 --announce-host 36.68.243.18 -``` - -This will bind the mix node to the available host `10.126.5.7`, but announce the mix node's public IP to the directory server as `36.68.243.18`. It's up to you as a node operator to ensure that your public and private IPs match up properly. - -To find the right IP configuration, contact your VPS provider for support. - -## Metrics / API endpoints -The mix node binary exposes several API endpoints that can be pinged in order to gather information about the node, and the Nym API (previously 'Validator API') exposes numerous mix node related endpoints which provide network-wide information about mix nodes, the network topology (the list of avaliable mix nodes for packet routing), and information regarding uptime monitoring and rewarding history. - -### Mix node API endpoints -Since the mix node binary exposes several API endpoints itself, you can ping these easily via curl: - -| Endpoint | Description | Command | -| -------------------- | ------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------- | -| `/description` | Returns the description of the node set with the `describe` command | `curl :8000/description` | -| `/hardware` | Returns the hardware information of the node | `curl :8000/hardware` | -| `/verloc` | Returns the verloc information of the node, updated every 12 hours | `curl :8000/verloc` | - -The code for exposed API endpoints can be found [here](https://github.com/nymtech/nym/tree/release/{{platform_release_version}}/mixnode/src/node/http). - -> You can get more detailed info by appending `?debug` to the URL, like so: -> ``` -> curl http://:8000/stats?debug -> ``` - -### Mix node related Nym API (previously 'Validator API') endpoints -Numerous endpoints are documented on the Nym API (previously 'Validator API')'s [Swagger Documentation](https://validator.nymtech.net/api/swagger/index.html). There you can also try out various requests from your browser, and download the response from the API. Swagger will also show you what commands it is running, so that you can run these from an app or from your CLI if you prefer. - -#### Mix node Reward Estimation API endpoint - -The Reward Estimation API endpoint allows mixnode operators to estimate the rewards they could earn for running a Nym mixnode with a specific `MIX_ID`. - -> The `` can be found in the "Mix ID" column of the [Network Explorer](https://explorer.nymtech.net/network-components/mixnodes/active). - -The endpoint is a particularly common for mixnode operators as it can provide an estimate of potential earnings based on factors such as the amount of traffic routed through the mixnode, the quality of the mixnode's performance, and the overall demand for mixnodes in the network. This information can be useful for mixnode operators in deciding whether or not to run a mix node and in optimizing its operations for maximum profitability. - -Using this API endpoint returns information about the Reward Estimation: - -``` -/status/mixnode//reward-estimation -``` - -Query Response: - -``` - "estimation": { - "total_node_reward": "942035.916721770541325331", - "operator": "161666.263307386408152071", - "delegates": "780369.65341438413317326", - "operating_cost": "54444.444444444444444443" - }, -``` - -> The unit of value is measured in `uNYM`. - -- `estimated_total_node_reward` - An estimate of the total amount of rewards that a particular mix node can expect to receive during the current epoch. This value is calculated by the Nym Validator based on a number of factors, including the current state of the network, the number of mix nodes currently active in the network, and the amount of network traffic being processed by the mix node. - -- `estimated_operator_reward` - An estimate of the amount of rewards that a particular mix node operator can expect to receive. This value is calculated by the Nym Validator based on a number of factors, including the amount of traffic being processed by the mix node, the quality of service provided by the mix node, and the operator's stake in the network. - -- `estimated_delegators_reward` - An estimate of the amount of rewards that mix node delegators can expect to receive individually. This value is calculated by the Nym Validator based on a number of factors, including the amount of traffic being processed by the mix node, the quality of service provided by the mix node, and the delegator's stake in the network. - -- `estimated_node_profit` - An estimate of the profit that a particular mix node operator can expect to earn. This value is calculated by subtracting the mix node operator's `operating_costs` from their `estimated_operator_reward` for the current epoch. - -- `estimated_operator_cost` - An estimate of the total cost that a particular mix node operator can expect to incur for their participation. This value is calculated by the Nym Validator based on a number of factors, including the cost of running a mix node, such as server hosting fees, and other expenses associated with operating the mix node. - -## Ports -All mix node-specific port configuration can be found in `$HOME/.nym/mixnodes//config/config.toml`. If you do edit any port configs, remember to restart your mix node. - -### Mix node port reference -| Default port | Use | -| ------------ | ------------------------- | -| `1789` | Listen for mixnet traffic | -| `1790` | Listen for VerLoc traffic | -| `8000` | Metrics http API endpoint | -/ +For mixnode upgrade, firewall setup, port configuration, API endpoints, VPS suggestions, automation and more, see the [maintenance page](./maintenance.md) From 34e2affef87321f61bf7e982c94a20966f84713a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 23 Jul 2023 02:08:02 +0000 Subject: [PATCH 007/361] Bump word-wrap from 1.2.3 to 1.2.5 in /nym-api/tests Bumps [word-wrap](https://github.com/jonschlinkert/word-wrap) from 1.2.3 to 1.2.5. - [Release notes](https://github.com/jonschlinkert/word-wrap/releases) - [Commits](https://github.com/jonschlinkert/word-wrap/compare/1.2.3...1.2.5) --- updated-dependencies: - dependency-name: word-wrap dependency-type: indirect ... Signed-off-by: dependabot[bot] --- nym-api/tests/package-lock.json | 12 ++++++------ nym-api/tests/yarn.lock | 6 +++--- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/nym-api/tests/package-lock.json b/nym-api/tests/package-lock.json index 020a0426be..e816245d61 100644 --- a/nym-api/tests/package-lock.json +++ b/nym-api/tests/package-lock.json @@ -4758,9 +4758,9 @@ } }, "node_modules/word-wrap": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", - "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", "engines": { "node": ">=0.10.0" } @@ -8318,9 +8318,9 @@ } }, "word-wrap": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", - "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==" + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==" }, "wrap-ansi": { "version": "7.0.0", diff --git a/nym-api/tests/yarn.lock b/nym-api/tests/yarn.lock index 9e5476cf80..b3e25d9c01 100644 --- a/nym-api/tests/yarn.lock +++ b/nym-api/tests/yarn.lock @@ -2866,9 +2866,9 @@ which@^2.0.1: isexe "^2.0.0" word-wrap@^1.2.3: - version "1.2.3" - resolved "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz" - integrity sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ== + version "1.2.5" + resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.5.tgz#d2c45c6dd4fbce621a66f136cbe328afd0410b34" + integrity sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA== wrap-ansi@^7.0.0: version "7.0.0" From cf79bdc098a8bbd201b43c90df8481d2b76acd7b Mon Sep 17 00:00:00 2001 From: serinko <97586125+serinko@users.noreply.github.com> Date: Mon, 24 Jul 2023 12:53:32 +0200 Subject: [PATCH 008/361] operators-guide: create NR page & edit maintenance page --- documentation/operators/src/SUMMARY.md | 10 +- .../operators/src/nodes/gateway-setup.md | 2 +- .../operators/src/nodes/maintenance.md | 83 +++++++- .../operators/src/nodes/mix-node-setup.md | 2 +- .../src/nodes/network-requester-setup.md | 179 +++++++++++++++++- 5 files changed, 269 insertions(+), 7 deletions(-) diff --git a/documentation/operators/src/SUMMARY.md b/documentation/operators/src/SUMMARY.md index 796c7d41ed..3e0fcf4280 100644 --- a/documentation/operators/src/SUMMARY.md +++ b/documentation/operators/src/SUMMARY.md @@ -1,9 +1,17 @@ +# Summary + - [Introduction](introduction.md) +# Binaries +- [Pre-built Binaries](../../docs/src/binaries/pre-built-binaries.md) +- [Building from Source](../../docs/src/binaries/building-nym.md) +- [Binary Initialisation and Configuration](../../docs/src/binaries/init-and-config.md) + + # Operators Guides - [Node Setup Guides](./nodes/setup-guides.md) -- [Preliminary Steps](./preliminary-steps.md) + - [Preliminary Steps](./preliminary-steps.md) - [Mix Node](./nodes/mix-node-setup.md) - [Gateway](./nodes/gateway-setup.md) - [Network Requester](./nodes/network-requester-setup.md) diff --git a/documentation/operators/src/nodes/gateway-setup.md b/documentation/operators/src/nodes/gateway-setup.md index eaacfaf52a..dd5c65e2ad 100644 --- a/documentation/operators/src/nodes/gateway-setup.md +++ b/documentation/operators/src/nodes/gateway-setup.md @@ -4,7 +4,7 @@ TODO ---> # Gateways -> The Nym gateway was built in the [building nym](../binaries/building-nym.md) section. If you haven't yet built Nym and want to run the code, go there first. +> The Nym gateway was built in the [building nym](https://nymtech.net/operators/binaries/building-nym.html) section. If you haven't yet built Nym and want to run the code, go there first. > Any syntax in `<>` brackets is a user's unique variable. Exchange with a corresponding name without the `<>` brackets. diff --git a/documentation/operators/src/nodes/maintenance.md b/documentation/operators/src/nodes/maintenance.md index 583bb6aa47..9216556050 100644 --- a/documentation/operators/src/nodes/maintenance.md +++ b/documentation/operators/src/nodes/maintenance.md @@ -20,6 +20,8 @@ Follow these steps to upgrade your mix node binary and update its config file: * re-run `init` with the same values as you used initially. **This will just update the config file, it will not overwrite existing keys**. * restart your mix node process with the new binary. +> In case of a network requester this is all all, the following step is only for mix nodes and gateways. + ### Step 2: Updating your node information in the smart contract Follow these steps to update the information about your `` which is publicly available from the [Nym API](https://validator.nymtech.net/api/swagger/index.html) and information displayed on the [mixnet explorer](https://explorer.nymtech.net). @@ -38,6 +40,37 @@ You can either do this graphically via the Desktop Wallet, or the CLI. If you want to bond your `` via the CLI, then check out the [relevant section in the Nym CLI](../../documentation/docs/src/tools/nym-cli.md#upgrade-a-mix-node) docs. +### Upgrading Network Requester to >= v1.1.10 from +``` + +Copy the old keys from your client to the network-requester configuration that was created above: + +``` +cp -vr ~/.nym/clients/myoldclient/data/* ~/.nym/service-providers/network-requester//data +``` + +Edit the configuration to match what you used on your client. Specifically, edit the configuration file at: + +``` +~/.nym/service-providers/network-requester//config/config.toml +``` + +Ensure that the fields `gateway_id`, `gateway_owner`, `gateway_listener` in the new config match those in the old client config at: + +``` +~/.nym/clients/myoldclient/config/config.toml +``` + + ## VPS Setup and Automation ### Configure your firewall Although your `` is now ready to recieve traffic, your server may not be. The following commands will allow you to set up a firewall using `ufw`. @@ -65,11 +98,16 @@ sudo ufw allow 1789,1790,8000,22/tcp # for gateway sudo ufw allow 1789,22,9000/tcp -# check the status of the firewall +# for network requester +sudo ufw allow 22,9000/tcp +``` + +Check the status of the firewall: +``` sudo ufw status ``` -For more information about your mix node's port configuration, check the [mix node port reference table](./mix-node-setup.md#mixnode-port-reference) or [gateway port reference table](https://nymtech.net/docs/nodes/gateway-setup.html#gateway-port-reference) below. +For more information about your node's port configuration, check the [port reference table](https://nymtech.net/docs/nodes/gateway-setup.html#ports) below. ### Automating your node with tmux and systemd @@ -162,6 +200,39 @@ WantedBy=multi-user.target * Put the above file onto your system at `/etc/systemd/system/nym-gateway.service`. +For network requester: + +```ini +[Unit] +Description=Nym Network Requester ({{platform_release_version}}) +StartLimitInterval=350 +StartLimitBurst=10 + +[Service] +User=nym # replace this with whatever user you wish +LimitNOFILE=65536 +# remember to add the `--enable-statistics` flag if running as part of a service grant and check the path to your nym-network-requester binary +ExecStart=/home/nym/nym-network-requester run --id +KillSignal=SIGINT +Restart=on-failure +RestartSec=30 + +[Install] +WantedBy=multi-user.target +``` + +Now enable and start your requester: + +``` +systemctl enable nym-network-requester.service +systemctl start nym-network-requester.service + +# you can always check your requester has succesfully started with: +systemctl status nym-network-requester.service +``` +* Put the above file onto your system at `/etc/systemd/system/nym-network-requester.service`. + + Change the `` in `ExecStart` to point at your `` binary (`nym-mixnode` or `nym-gateway), and the `` so it is the user you are running as. If you have built nym in the `$HOME` directory on your server, and your username is `jetpanther`, then the start command for nym mixnode might look like this: @@ -351,7 +422,7 @@ Query Response: - `estimated_operator_cost` - An estimate of the total cost that a particular mix node operator can expect to incur for their participation. This value is calculated by the Nym Validator based on a number of factors, including the cost of running a mix node, such as server hosting fees, and other expenses associated with operating the mix node. ## Ports -All ``-specific port configuration can be found in `$HOME/.nym///config/config.toml`. If you do edit any port configs, remember to restart your mix node. +All ``-specific port configuration can be found in `$HOME/.nym///config/config.toml`. If you do edit any port configs, remember to restart your client and node processes. ### Mix node port reference | Default port | Use | @@ -367,4 +438,10 @@ All ``-specific port configuration can be found in `$HOME/.nym// The Nym mix node binary was built in the [building nym](https://nymtech.net/docs/binaries/building-nym.html) section. If you haven't yet built Nym and want to run the code, go there first. +> The Nym mix node binary was built in the [building nym](https://nymtech.net/operators/binaries/building-nym.html) section. If you haven't yet built Nym and want to run the code, go there first. > Any syntax in `<>` brackets is a user's unique variable. Exchange with a corresponding name without the `<>` brackets. diff --git a/documentation/operators/src/nodes/network-requester-setup.md b/documentation/operators/src/nodes/network-requester-setup.md index 797dfd7795..1f5e5eee0c 100644 --- a/documentation/operators/src/nodes/network-requester-setup.md +++ b/documentation/operators/src/nodes/network-requester-setup.md @@ -1 +1,178 @@ -# Network Requester + +# Network Requesters + +> The Nym gateway was built in the [building nym](https://nymtech.net/operators/binaries/building-nym.html) section. If you haven't yet built Nym and want to run the code, go there first. + +> Any syntax in `<>` brackets is a user's unique variable. Exchange with a corresponding name without the `<>` brackets. + +## Current version +``` + +``` + +## Preliminary steps + +Make sure you do the preparation listed in the [preliminary steps page](../preliminary-steps.md) before setting up your network requester. + +## Network Requester Whitelist +If you have access to a server, you can run the network requester, which allows Nym users to send outbound requests from their local machine through the mixnet to a server, which then makes the request on their behalf, shielding them (and their metadata) from clearnet, untrusted and unknown infrastructure, such as email or message client servers. + +By default the network requester is **not** an open proxy (although it can be used as one). It uses a file called `allowed.list` (located in `~/.nym/service-providers/network-requester//`) as a whitelist for outbound requests. + +Any request to a URL which is not on this list will be blocked. + +On startup, if this file is not present, the requester will grab the default whitelist from [Nym's default list](https://nymtech.net/.wellknown/network-requester/standard-allowed-list.txt) automatically. + +This default whitelist is useful for knowing that the majority of network requesters are able to support certain apps 'out of the box'. + +**Operators of a network requester are of course free to edit this file and add the URLs of services they wish to support to it!** You can find instructions below on adding your own URLs or IPs to this list. + +The domains and IPs on the default whitelist can be broken down by application as follows: + + +``` +# Keybase +keybaseapi.com +s3.amazonaws.com +amazonaws.com +twitter.com +keybase.io +gist.githubusercontent.com + +# Used to for uptime healthcheck (see the section on testing your requester below for more) +nymtech.net + +# Blockstream Green Bitcoin Wallet +blockstream.info +blockstream.com +greenaddress.it + +# Electrum Bitcoin Wallet +electrum.org + +# Helios Ethereum Client +alchemy.com +lightclientdata.org +p2pify.com + +# Telegram - these IPs have been copied from https://core.telegram.org/resources/cidr.txt as Telegram does +# not seem to route by domain as the other apps on this list do +91.108.56.0/22 +91.108.4.0/22 +91.108.8.0/22 +91.108.16.0/22 +91.108.12.0/22 +149.154.160.0/20 +91.105.192.0/23 +91.108.20.0/22 +185.76.151.0/24 +2001:b28:f23d::/48 +2001:b28:f23f::/48 +2001:67c:4e8::/48 +2001:b28:f23c::/48 +2a0a:f280::/32 + +# Matrix +matrix.org + +``` + +## Network Requester Directory +You can find a list of Network Requesters running the default whitelist in the [explorer](https://explorer.nymtech.net/network-components/service-providers). This list comprises of the NRs running as infrastructure for NymConnect. + +> We are currently working on a smart-contract based solution more in line with how Mix nodes and Gateways announce themselves to the network. + +## Viewing command help + +To begin, move to `/taget/release` directory from which you run the node commands: + +``` +cd target/release +``` + +The `./nym-network-requester --help ` command can be used to show a list of available parameters. + +~~~admonish example collapsible=true title="Console output" +``` + +``` +~~~ + +You can check the required parameters for available commands by running: + +``` +./nym-network-requester --help +``` + +## Initializing and running your network requester + +The network-requester needs to be initialized before it can be run. This is required for the embedded nym-client to connect successfully to the mixnet. We want to specify an `` using the `--id` command and give it a value of your choice. The following command will achieve that: + +``` + ./nym-network-requester init --id +``` + +In the following we used `example`. + +~~~admonish example collapsible=true title="Console output" +``` + +``` +~~~ + + +Now that we have initialized our network-requester, we can start it with the following command: + +``` + ./nym-network-requester run --id +``` + +## Maintenance + +For network requester upgrade (including an upgrade from `= v1.1.10`), firewall setup, port configuration, API endpoints, VPS suggestions, automation and more, see the [maintenance page](./maintenance.md). + + +## Using your network requester + +The next thing to do is use your requester, share its address with friends (or whoever you want to help privacy-enhance their app traffic). Is this safe to do? If it was an open proxy, this would be unsafe, because any Nym user could make network requests to any system on the internet. + +To make things a bit less stressful for administrators, the Network Requester drops all incoming requests by default. In order for it to make requests, you need to add specific domains to the `allowed.list` file at `$HOME/.nym/service-providers/network-requester/allowed.list`. + +### Supporting custom domains with your network requester +It is easy to add new domains and services to your network requester - simply find out which endpoints (both URLs and raw IP addresses are supported) you need to whitelist, and then add these endpoints to your `allowed.list`. + +How to go about this? Have a look in your nym-network-requester config directory: + +``` +ls $HOME/.nym/service-providers/network-requester/ + +# returns: allowed.list unknown.list +``` + +We already know that `allowed.list` is what lets requests go through. All unknown requests are logged to `unknown.list`. If you want to try using a new client type, just start the new application, point it at your local [socks client](https://nymtech.net/docs/clients/socks5-client.html) (configured to use your remote `nym-network-requester`), and keep copying URLs from `unknown.list` into `allowed.list` (it may take multiple tries until you get all of them, depending on the complexity of the application). Make sure to restart your network requester! + +> If you are adding custom domains, please note that whilst they may appear in the logs of your network-requester as something like `api-0.core.keybaseapi.com:443`, you **only need** to include the main domain name, in this instance `keybaseapi.com` + +### Running an open proxy +If you *really* want to run an open proxy, perhaps for testing purposes for your own use or among a small group of trusted friends, it is possible to do so. You can disable network checks by passing the flag `--open-proxy` flag when you run it. If you run in this configuration, you do so at your own risk. + +## Testing your network requester +1. Make sure `nymtech.net` is in your `allowed.list` (remember to restart your network requester). + +2. Ensure that your network-requester is initialized and running. + +3. In another terminal window, run the following: + +``` +curl -x socks5h://localhost:1080 https://nymtech.net/.wellknown/connect/healthcheck.json +``` + +This command should return the following: + +``` +{ "status": "ok" } +``` + From f2a395fe42b52beebf93b2acd53b57f819f3e4f3 Mon Sep 17 00:00:00 2001 From: serinko <97586125+serinko@users.noreply.github.com> Date: Wed, 26 Jul 2023 11:46:36 +0200 Subject: [PATCH 009/361] operators-guide: create validator tutorial & add maintenance points --- documentation/operators/src/SUMMARY.md | 6 +- documentation/operators/src/introduction.md | 2 +- .../operators/src/nodes/maintenance.md | 219 +++++++++++++++++- .../operators/src/nodes/troubleshooting.md | 17 ++ 4 files changed, 231 insertions(+), 13 deletions(-) diff --git a/documentation/operators/src/SUMMARY.md b/documentation/operators/src/SUMMARY.md index 3e0fcf4280..f767aec6f7 100644 --- a/documentation/operators/src/SUMMARY.md +++ b/documentation/operators/src/SUMMARY.md @@ -10,12 +10,14 @@ # Operators Guides -- [Node Setup Guides](./nodes/setup-guides.md) +- [Mixnet Nodes Setup](./nodes/setup-guides.md) - [Preliminary Steps](./preliminary-steps.md) - [Mix Node](./nodes/mix-node-setup.md) - [Gateway](./nodes/gateway-setup.md) - [Network Requester](./nodes/network-requester-setup.md) - - [Validator](./nodes/validator-setup.md) +- [Nyx Validator Setup](./nodes/validator-setup.md) - [Maintenance](./nodes/maintenance.md) - [Troubleshooting](./nodes/troubleshooting.md) + diff --git a/documentation/operators/src/introduction.md b/documentation/operators/src/introduction.md index a97860f292..48b5984eef 100644 --- a/documentation/operators/src/introduction.md +++ b/documentation/operators/src/introduction.md @@ -1,6 +1,6 @@ # Introduction -This is Nym's operators guide, containing information and setup tutorials about the various pieces of Nym mixnet infrastructure nodes, such as mix node, gateway and network requester. +This is Nym's operators guide, containing information and setup tutorials about the various pieces of Nym mixnet infrastructure nodes, such as mix node, gateway and network requester as well as Nyx chain validators. If you are new to Nym and want to learn about the mixnet, how to integrate with the network, developer tutorials and quickstart guides, check out the [Developer Portal](https://nymtech.net/developers/). diff --git a/documentation/operators/src/nodes/maintenance.md b/documentation/operators/src/nodes/maintenance.md index 9216556050..1ac31c959c 100644 --- a/documentation/operators/src/nodes/maintenance.md +++ b/documentation/operators/src/nodes/maintenance.md @@ -2,8 +2,7 @@ ## Upgrading your node @@ -70,10 +69,34 @@ Ensure that the fields `gateway_id`, `gateway_owner`, `gateway_listener` in the ~/.nym/clients/myoldclient/config/config.toml ``` +### Upgrading your validator + +Upgrading from `v0.31.1` -> `v0.32.0` process is fairly simple. Grab the `v0.32.0` release tarball from the [`nyxd` releases page](https://github.com/nymtech/nyxd/releases), and untar it. Inside are two files: + +- the new validator (`nyxd`) v0.32.0 +- the new wasmvm (it depends on your platform, but most common filename is `libwasmvm.x86_64.so`) + +Wait for the upgrade height to be reached and the chain to halt awaiting upgrade, then: + +* copy `libwasmvm.x86_64.so` to the default LD_LIBRARY_PATH on your system (on Ubuntu 20.04 this is `/lib/x86_64-linux-gnu/`) replacing your existing file with the same name. +* swap in your new `nyxd` binary and restart. + +You can also use something like [Cosmovisor](https://github.com/cosmos/cosmos-sdk/tree/main/tools/cosmovisor) - grab the relevant information from the current upgrade proposal [here](https://nym.explorers.guru/proposal/9). + +Note: Cosmovisor will swap the `nyxd` binary, but you'll need to already have the `libwasmvm.x86_64.so` in place. + +#### Common reasons for your validator being jailed + +The most common reason for your validator being jailed is that your validator is out of memory because of bloated syslogs. + +Running the command `df -H` will return the size of the various partitions of your VPS. + +If the `/dev/sda` partition is almost full, try pruning some of the `.gz` syslog archives and restart your validator process. + ## VPS Setup and Automation ### Configure your firewall -Although your `` is now ready to recieve traffic, your server may not be. The following commands will allow you to set up a firewall using `ufw`. +Although your `` is now ready to receive traffic, your server may not be. The following commands will allow you to set up a firewall using `ufw`. ``` # check if you have ufw installed @@ -100,6 +123,9 @@ sudo ufw allow 1789,22,9000/tcp # for network requester sudo ufw allow 22,9000/tcp + +# for validator +sudo ufw allow 1317,26656,26660,22,80,443/tcp ``` Check the status of the firewall: @@ -156,7 +182,7 @@ tmux attach-session Here's a systemd service file to do that: -For mixnode: +##### For Mixnode ```ini [Unit] @@ -178,7 +204,7 @@ WantedBy=multi-user.target * Put the above file onto your system at `/etc/systemd/system/nym-mixnode.service`. -For gateway: +##### For Gateway ```ini [Unit] @@ -200,7 +226,7 @@ WantedBy=multi-user.target * Put the above file onto your system at `/etc/systemd/system/nym-gateway.service`. -For network requester: +##### For Network requester ```ini [Unit] @@ -232,8 +258,41 @@ systemctl status nym-network-requester.service ``` * Put the above file onto your system at `/etc/systemd/system/nym-network-requester.service`. +##### For Validator -Change the `` in `ExecStart` to point at your `` binary (`nym-mixnode` or `nym-gateway), and the `` so it is the user you are running as. +Below is a systemd unit file to place at `/etc/systemd/system/nymd.service`: + +```ini +[Unit] +Description=Nyxd +StartLimitInterval=350 +StartLimitBurst=10 + +[Service] +User=nyx # change to your user +Type=simple +Environment="LD_LIBRARY_PATH=/home/youruser/path/to/nyx/binaries" # change to correct path +ExecStart=/home/youruser/path/to/nyx/binaries/nymd start # change to correct path +Restart=on-failure +RestartSec=30 +LimitNOFILE=infinity + +[Install] +WantedBy=multi-user.target +``` + +Proceed to start it with: + +``` +systemctl daemon-reload # to pickup the new unit file +systemctl enable nymd # to enable the service +systemctl start nymd # to actually start the service +journalctl -f # to monitor system logs showing the service start +``` + +##### Following steps for Nym Mixnet nodes + +Change the `` in `ExecStart` to point at your `` binary (`nym-mixnode`, `nym-gateway` or `nym-network-requester`), and the `` so it is the user you are running as. If you have built nym in the `$HOME` directory on your server, and your username is `jetpanther`, then the start command for nym mixnode might look like this: @@ -288,14 +347,22 @@ This means that the operating system is preventing network connections from bein #### Set the ulimit via `systemd` service file +> Replace `` variable with `nym-mixnode`, `nym-gateway` or `nym-network-requester` according the node you running on your machine. + The ulimit setup is relevant for maintenance of nym mixnode only. -Query the `ulimit` of your mix node with: +Query the `ulimit` of your `` with: ``` -grep -i "open files" /proc/$(ps -A -o pid,cmd|grep nym-mixnode | grep -v grep |head -n 1 | awk '{print $1}')/limits +# for nym-mixnode, nym-gateway and nym-network requester: +grep -i "open files" /proc/$(ps -A -o pid,cmd|grep | grep -v grep |head -n 1 | awk '{print $1}')/limits + +# for nyx validator: +grep -i "open files" /proc/$(ps -A -o pid,cmd|grep nymd | grep -v grep |head -n 1 | awk '{print $1}')/limits ``` + + You'll get back the hard and soft limits, which looks something like this: ``` @@ -322,7 +389,15 @@ or execute this as root for system-wide setting of `ulimit`: echo "DefaultLimitNOFILE=65535" >> /etc/systemd/system.conf ``` -Reboot your machine and restart your node. When it comes back, use `cat /proc/$(pidof nym-mixnode)/limits | grep "Max open files"` to make sure the limit has changed to 65535. +Reboot your machine and restart your node. When it comes back, use: +``` +# for nym-mixnode, nym-gateway and nym-network requester: +cat /proc/$(pidof )/limits | grep "Max open files"` to make sure the limit has changed to 65535. + +# for validator +cat /proc/$(pidof nym-validator)/limits | grep "Max open files" +``` +Make sure the limit has changed to 65535. #### Set the ulimit on `non-systemd` based distributions @@ -421,6 +496,121 @@ Query Response: - `estimated_operator_cost` - An estimate of the total cost that a particular mix node operator can expect to incur for their participation. This value is calculated by the Nym Validator based on a number of factors, including the cost of running a mix node, such as server hosting fees, and other expenses associated with operating the mix node. +### Validator: Installing and configuring nginx for HTTPS +#### Setup +[Nginx](https://www.nginx.com/resources/glossary/nginx/#:~:text=NGINX%20is%20open%20source%20software,%2C%20media%20streaming%2C%20and%20more.&text=In%20addition%20to%20its%20HTTP,%2C%20TCP%2C%20and%20UDP%20servers.) is an open source software used for operating high-performance web servers. It allows us to set up reverse proxying on our validator server to improve performance and security. + +Install `nginx` and allow the 'Nginx Full' rule in your firewall: + +``` +sudo ufw allow 'Nginx Full' +``` + +Check nginx is running via systemctl: + +``` +systemctl status nginx +``` + +Which should return: + +``` +● nginx.service - A high performance web server and a reverse proxy server + Loaded: loaded (/lib/systemd/system/nginx.service; enabled; vendor preset: enabled) + Active: active (running) since Fri 2018-04-20 16:08:19 UTC; 3 days ago + Docs: man:nginx(8) + Main PID: 2369 (nginx) + Tasks: 2 (limit: 1153) + CGroup: /system.slice/nginx.service + ├─2369 nginx: master process /usr/sbin/nginx -g daemon on; master_process on; + └─2380 nginx: worker process +``` + +#### Configuration + +Proxying your validator's port `26657` to nginx port `80` can then be done by creating a file with the following at `/etc/nginx/conf.d/validator.conf`: + +``` +server { + listen 80; + listen [::]:80; + server_name "domain_name"; + + location / { + proxy_pass http://127.0.0.1:26657; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header Host $host; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } +} +``` + +Followed by: + +``` +sudo apt install certbot nginx python3 +certbot --nginx -d nym-validator.yourdomain.com -m you@yourdomain.com --agree-tos --noninteractive --redirect +``` + +```admonish caution title="" +If using a VPS running Ubuntu 20: replace `certbot nginx python3` with `python3-certbot-nginx` +``` + +These commands will get you an https encrypted nginx proxy in front of the API. + +### Configuring Prometheus metrics (optional) + +Configure Prometheus with the following commands (adapted from NodesGuru's [Agoric setup guide](https://nodes.guru/agoric/setup-guide/en)): + +``` +echo 'export OTEL_EXPORTER_PROMETHEUS_PORT=9464' >> $HOME/.bashrc +source ~/.bashrc +sed -i '/\[telemetry\]/{:a;n;/enabled/s/false/true/;Ta}' $HOME/.nymd/config/app.toml +sed -i "s/prometheus-retention-time = 0/prometheus-retention-time = 60/g" $HOME/.nymd/config/app.toml +sudo ufw allow 9464 +echo 'Metrics URL: http://'$(curl -s ifconfig.me)':26660/metrics' +``` + +Your validator's metrics will be available to you at the returned 'Metrics URL'. + +~~~admonish example collapsible=true title="Console output" +``` +# HELP go_gc_duration_seconds A summary of the pause duration of garbage collection cycles. +# TYPE go_gc_duration_seconds summary +go_gc_duration_seconds{quantile="0"} 6.7969e-05 +go_gc_duration_seconds{quantile="0.25"} 7.864e-05 +go_gc_duration_seconds{quantile="0.5"} 8.4591e-05 +go_gc_duration_seconds{quantile="0.75"} 0.000115919 +go_gc_duration_seconds{quantile="1"} 0.001137591 +go_gc_duration_seconds_sum 0.356555301 +go_gc_duration_seconds_count 2448 +# HELP go_goroutines Number of goroutines that currently exist. +# TYPE go_goroutines gauge +go_goroutines 668 +# HELP go_info Information about the Go environment. +# TYPE go_info gauge +go_info{version="go1.15.7"} 1 +# HELP go_memstats_alloc_bytes Number of bytes allocated and still in use. +# TYPE go_memstats_alloc_bytes gauge +go_memstats_alloc_bytes 1.62622216e+08 +# HELP go_memstats_alloc_bytes_total Total number of bytes allocated, even if freed. +# TYPE go_memstats_alloc_bytes_total counter +go_memstats_alloc_bytes_total 2.09341707264e+11 +# HELP go_memstats_buck_hash_sys_bytes Number of bytes used by the profiling bucket hash table. +# TYPE go_memstats_buck_hash_sys_bytes gauge +go_memstats_buck_hash_sys_bytes 5.612319e+06 +# HELP go_memstats_frees_total Total number of frees. +# TYPE go_memstats_frees_total counter +go_memstats_frees_total 2.828263344e+09 +# HELP go_memstats_gc_cpu_fraction The fraction of this program's available CPU time used by the GC since the program started. +# TYPE go_memstats_gc_cpu_fraction gauge +go_memstats_gc_cpu_fraction 0.03357798610671518 +# HELP go_memstats_gc_sys_bytes Number of bytes used for garbage collection system metadata. +# TYPE go_memstats_gc_sys_bytes gauge +go_memstats_gc_sys_bytes 1.3884192e+07 +``` +~~~ + ## Ports All ``-specific port configuration can be found in `$HOME/.nym///config/config.toml`. If you do edit any port configs, remember to restart your client and node processes. @@ -444,4 +634,13 @@ All ``-specific port configuration can be found in `$HOME/.nym// + + + # Troubleshooting + +### Common reasons for your validator being jailed + +The most common reason for your validator being jailed is that your validator is out of memory because of bloated syslogs. + +Running the command `df -H` will return the size of the various partitions of your VPS. + +If the `/dev/sda` partition is almost full, try pruning some of the `.gz` syslog archives and restart your validator process. + From c120196be0061bc27f071a2d5058c8af2337dcd2 Mon Sep 17 00:00:00 2001 From: serinko <97586125+serinko@users.noreply.github.com> Date: Wed, 26 Jul 2023 12:55:05 +0200 Subject: [PATCH 010/361] operators-guide: incorporated PR #3656 --- documentation/operators/src/nodes/gateway-setup.md | 1 + documentation/operators/src/nodes/maintenance.md | 14 ++++++++++++++ .../operators/src/nodes/mix-node-setup.md | 2 ++ .../operators/src/nodes/network-requester-setup.md | 2 ++ 4 files changed, 19 insertions(+) diff --git a/documentation/operators/src/nodes/gateway-setup.md b/documentation/operators/src/nodes/gateway-setup.md index dd5c65e2ad..cdc72700e2 100644 --- a/documentation/operators/src/nodes/gateway-setup.md +++ b/documentation/operators/src/nodes/gateway-setup.md @@ -45,6 +45,7 @@ You can also check the various arguments required for individual commands with: ``` ./nym-gateway --help ``` +> Adding `--no-banner` startup flag will prevent Nym banner being printed even if run in tty environment. ### Initialising your gateway To check available configuration options use: diff --git a/documentation/operators/src/nodes/maintenance.md b/documentation/operators/src/nodes/maintenance.md index 1ac31c959c..d4884bad84 100644 --- a/documentation/operators/src/nodes/maintenance.md +++ b/documentation/operators/src/nodes/maintenance.md @@ -4,6 +4,20 @@ TODO - [x] Compare mixnode, gateway, NR steps and validator of upgrading and automation and make a generic page - this one - for all of them with additional notes for particular nodes ---> +## Useful commands + +> Adding `--no-banner` startup flag will prevent Nym banner being printed even if run in tty environment. + +**build-info** + +A `build-info` command prints the build information like commit hash, rust version, binary version just like what command `--version` does. However, you can also specify an `--output=json` flag that will format the whole output as a json, making it an order of magnitude easier to parse. + +For example `./target/debug/nym-network-requester --no-banner build-info --output json` will return: + +``` +{"binary_name":"nym-network-requester","build_timestamp":"2023-07-24T15:38:37.00657Z","build_version":"1.1.23","commit_sha":"c70149400206dce24cf20babb1e64f22202672dd","commit_timestamp":"2023-07-24T14:45:45Z","commit_branch":"feature/simplify-cli-parsing","rustc_version":"1.71.0","rustc_channel":"stable","cargo_profile":"debug"} +``` + ## Upgrading your node > The process is the similar for mixnode, gateway and network requester. In the following steps we use a placeholder `` in the commands, please change it for the type of node you want to upgrade. Any particularities for the given type of node are included. diff --git a/documentation/operators/src/nodes/mix-node-setup.md b/documentation/operators/src/nodes/mix-node-setup.md index 435f560a59..8797c13056 100644 --- a/documentation/operators/src/nodes/mix-node-setup.md +++ b/documentation/operators/src/nodes/mix-node-setup.md @@ -67,6 +67,8 @@ You can also check the various arguments required for individual commands with: ./nym-mixnode --help ``` +> Adding `--no-banner` startup flag will prevent Nym banner being printed even if run in tty environment. + ### Initialising your mix node To check available configuration options for initializing your node use: diff --git a/documentation/operators/src/nodes/network-requester-setup.md b/documentation/operators/src/nodes/network-requester-setup.md index 1f5e5eee0c..33dc3c7c90 100644 --- a/documentation/operators/src/nodes/network-requester-setup.md +++ b/documentation/operators/src/nodes/network-requester-setup.md @@ -107,6 +107,8 @@ You can check the required parameters for available commands by running: ./nym-network-requester --help ``` +> Adding `--no-banner` startup flag will prevent Nym banner being printed even if run in tty environment. + ## Initializing and running your network requester The network-requester needs to be initialized before it can be run. This is required for the embedded nym-client to connect successfully to the mixnet. We want to specify an `` using the `--id` command and give it a value of your choice. The following command will achieve that: From 44fa52a7a76741bbc5f22219b0efd795f93fecd5 Mon Sep 17 00:00:00 2001 From: pierre Date: Thu, 27 Jul 2023 11:08:24 +0200 Subject: [PATCH 011/361] ci: fix release strapi actions --- .github/workflows/nym-connect-publish-macos.yml | 4 ++-- .github/workflows/nym-connect-publish-ubuntu.yml | 5 +++-- .github/workflows/nym-connect-publish-windows10.yml | 4 ++-- .github/workflows/nym-wallet-publish-macos.yml | 4 ++-- .github/workflows/nym-wallet-publish-ubuntu.yml | 4 ++-- .github/workflows/nym-wallet-publish-windows10.yml | 4 ++-- 6 files changed, 13 insertions(+), 12 deletions(-) diff --git a/.github/workflows/nym-connect-publish-macos.yml b/.github/workflows/nym-connect-publish-macos.yml index 45dac7e87a..a4176eb285 100644 --- a/.github/workflows/nym-connect-publish-macos.yml +++ b/.github/workflows/nym-connect-publish-macos.yml @@ -19,7 +19,7 @@ jobs: outputs: release_id: ${{ steps.create-release.outputs.id }} - release_date: ${{ fromJSON(steps.create-release.outputs.assets)[0].created_at }} + release_date: ${{ fromJSON(steps.create-release.outputs.assets)[0].published_at }} version: ${{ steps.release-info.outputs.version }} filename: ${{ steps.release-info.outputs.filename }} file_hash: ${{ steps.release-info.outputs.file_hash }} @@ -118,7 +118,7 @@ jobs: ref=${{ github.ref_name }} semver="${ref##nym-connect-}" && semver="${semver##v}" echo "version=${semver}" >> "$GITHUB_OUTPUT" - echo "filename=nym-connect_${version}_x64_en-US.msi" >> "$GITHUB_OUTPUT" + echo "filename=nym-connect_${semver}_x64_en-US.msi" >> "$GITHUB_OUTPUT" echo "file_hash=${{ hashFiles('nym-connect/desktop/target/release/bundle/dmg/nym-connect_*_x64.dmg') }}" >> "$GITHUB_OUTPUT" push-release-data: diff --git a/.github/workflows/nym-connect-publish-ubuntu.yml b/.github/workflows/nym-connect-publish-ubuntu.yml index cc5e1f3f00..caec73b10a 100644 --- a/.github/workflows/nym-connect-publish-ubuntu.yml +++ b/.github/workflows/nym-connect-publish-ubuntu.yml @@ -19,7 +19,7 @@ jobs: outputs: release_id: ${{ steps.create-release.outputs.id }} - release_date: ${{ fromJSON(steps.create-release.outputs.assets)[0].created_at }} + release_date: ${{ fromJSON(steps.create-release.outputs.assets)[0].published_at }} version: ${{ steps.release-info.outputs.version }} filename: ${{ steps.release-info.outputs.filename }} file_hash: ${{ steps.release-info.outputs.file_hash }} @@ -85,8 +85,9 @@ jobs: ref=${{ github.ref_name }} semver="${ref##nym-connect-}" && semver="${semver##v}" echo "version=${semver}" >> "$GITHUB_OUTPUT" - echo "filename=nym-connect_${version}_amd64.AppImage" >> "$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 diff --git a/.github/workflows/nym-connect-publish-windows10.yml b/.github/workflows/nym-connect-publish-windows10.yml index 1b4b1be2cc..aeb04dc872 100644 --- a/.github/workflows/nym-connect-publish-windows10.yml +++ b/.github/workflows/nym-connect-publish-windows10.yml @@ -19,7 +19,7 @@ jobs: outputs: release_id: ${{ steps.create-release.outputs.id }} - release_date: ${{ fromJSON(steps.create-release.outputs.assets)[0].created_at }} + release_date: ${{ fromJSON(steps.create-release.outputs.assets)[0].published_at }} version: ${{ steps.release-info.outputs.version }} filename: ${{ steps.release-info.outputs.filename }} file_hash: ${{ steps.release-info.outputs.file_hash }} @@ -104,7 +104,7 @@ jobs: ref=${{ github.ref_name }} semver="${ref##nym-connect-}" && semver="${semver##v}" echo "version=${semver}" >> "$GITHUB_OUTPUT" - echo "filename=nym-connect_${version}_x64_en-US.msi" >> "$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: diff --git a/.github/workflows/nym-wallet-publish-macos.yml b/.github/workflows/nym-wallet-publish-macos.yml index 408a3b7406..5db06a8f52 100644 --- a/.github/workflows/nym-wallet-publish-macos.yml +++ b/.github/workflows/nym-wallet-publish-macos.yml @@ -19,7 +19,7 @@ jobs: outputs: release_id: ${{ steps.create-release.outputs.id }} - release_date: ${{ fromJSON(steps.create-release.outputs.assets)[0].created_at }} + release_date: ${{ fromJSON(steps.create-release.outputs.assets)[0].published_at }} version: ${{ steps.release-info.outputs.version }} filename: ${{ steps.release-info.outputs.filename }} file_hash: ${{ steps.release-info.outputs.file_hash }} @@ -106,7 +106,7 @@ jobs: ref=${{ github.ref_name }} semver="${ref##nym-wallet-}" && semver="${semver##v}" echo "version=${semver}" >> "$GITHUB_OUTPUT" - echo "filename=nym-wallet_${version}_x64.dmg" >> "$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: diff --git a/.github/workflows/nym-wallet-publish-ubuntu.yml b/.github/workflows/nym-wallet-publish-ubuntu.yml index 9e9ee904cd..52887950fa 100644 --- a/.github/workflows/nym-wallet-publish-ubuntu.yml +++ b/.github/workflows/nym-wallet-publish-ubuntu.yml @@ -19,7 +19,7 @@ jobs: outputs: release_id: ${{ steps.create-release.outputs.id }} - release_date: ${{ fromJSON(steps.create-release.outputs.assets)[0].created_at }} + release_date: ${{ fromJSON(steps.create-release.outputs.assets)[0].published_at }} version: ${{ steps.release-info.outputs.version }} filename: ${{ steps.release-info.outputs.filename }} file_hash: ${{ steps.release-info.outputs.file_hash }} @@ -83,7 +83,7 @@ jobs: ref=${{ github.ref_name }} semver="${ref##nym-wallet-}" && semver="${semver##v}" echo "version=${semver}" >> "$GITHUB_OUTPUT" - echo "filename=nym-wallet_${version}_amd64.AppImage" >> "$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: diff --git a/.github/workflows/nym-wallet-publish-windows10.yml b/.github/workflows/nym-wallet-publish-windows10.yml index 56a332a75d..a7be3e4a37 100644 --- a/.github/workflows/nym-wallet-publish-windows10.yml +++ b/.github/workflows/nym-wallet-publish-windows10.yml @@ -19,7 +19,7 @@ jobs: outputs: release_id: ${{ steps.create-release.outputs.id }} - release_date: ${{ fromJSON(steps.create-release.outputs.assets)[0].created_at }} + release_date: ${{ fromJSON(steps.create-release.outputs.assets)[0].published_at }} version: ${{ steps.release-info.outputs.version }} filename: ${{ steps.release-info.outputs.filename }} file_hash: ${{ steps.release-info.outputs.file_hash }} @@ -103,7 +103,7 @@ jobs: ref=${{ github.ref_name }} semver="${ref##nym-wallet-}" && semver="${semver##v}" echo "version=${semver}" >> "$GITHUB_OUTPUT" - echo "filename=nym-wallet_${version}_x64_en-US.msi" >> "$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: From aedacf6c6544c4c59d337ef1575dc08af3030ab8 Mon Sep 17 00:00:00 2001 From: pierre Date: Thu, 27 Jul 2023 11:08:24 +0200 Subject: [PATCH 012/361] ci: fix release strapi actions --- .github/workflows/nym-connect-publish-macos.yml | 4 ++-- .github/workflows/nym-connect-publish-ubuntu.yml | 5 +++-- .github/workflows/nym-connect-publish-windows10.yml | 4 ++-- .github/workflows/nym-wallet-publish-macos.yml | 4 ++-- .github/workflows/nym-wallet-publish-ubuntu.yml | 4 ++-- .github/workflows/nym-wallet-publish-windows10.yml | 4 ++-- 6 files changed, 13 insertions(+), 12 deletions(-) diff --git a/.github/workflows/nym-connect-publish-macos.yml b/.github/workflows/nym-connect-publish-macos.yml index 45dac7e87a..a4176eb285 100644 --- a/.github/workflows/nym-connect-publish-macos.yml +++ b/.github/workflows/nym-connect-publish-macos.yml @@ -19,7 +19,7 @@ jobs: outputs: release_id: ${{ steps.create-release.outputs.id }} - release_date: ${{ fromJSON(steps.create-release.outputs.assets)[0].created_at }} + release_date: ${{ fromJSON(steps.create-release.outputs.assets)[0].published_at }} version: ${{ steps.release-info.outputs.version }} filename: ${{ steps.release-info.outputs.filename }} file_hash: ${{ steps.release-info.outputs.file_hash }} @@ -118,7 +118,7 @@ jobs: ref=${{ github.ref_name }} semver="${ref##nym-connect-}" && semver="${semver##v}" echo "version=${semver}" >> "$GITHUB_OUTPUT" - echo "filename=nym-connect_${version}_x64_en-US.msi" >> "$GITHUB_OUTPUT" + echo "filename=nym-connect_${semver}_x64_en-US.msi" >> "$GITHUB_OUTPUT" echo "file_hash=${{ hashFiles('nym-connect/desktop/target/release/bundle/dmg/nym-connect_*_x64.dmg') }}" >> "$GITHUB_OUTPUT" push-release-data: diff --git a/.github/workflows/nym-connect-publish-ubuntu.yml b/.github/workflows/nym-connect-publish-ubuntu.yml index cc5e1f3f00..caec73b10a 100644 --- a/.github/workflows/nym-connect-publish-ubuntu.yml +++ b/.github/workflows/nym-connect-publish-ubuntu.yml @@ -19,7 +19,7 @@ jobs: outputs: release_id: ${{ steps.create-release.outputs.id }} - release_date: ${{ fromJSON(steps.create-release.outputs.assets)[0].created_at }} + release_date: ${{ fromJSON(steps.create-release.outputs.assets)[0].published_at }} version: ${{ steps.release-info.outputs.version }} filename: ${{ steps.release-info.outputs.filename }} file_hash: ${{ steps.release-info.outputs.file_hash }} @@ -85,8 +85,9 @@ jobs: ref=${{ github.ref_name }} semver="${ref##nym-connect-}" && semver="${semver##v}" echo "version=${semver}" >> "$GITHUB_OUTPUT" - echo "filename=nym-connect_${version}_amd64.AppImage" >> "$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 diff --git a/.github/workflows/nym-connect-publish-windows10.yml b/.github/workflows/nym-connect-publish-windows10.yml index 1b4b1be2cc..aeb04dc872 100644 --- a/.github/workflows/nym-connect-publish-windows10.yml +++ b/.github/workflows/nym-connect-publish-windows10.yml @@ -19,7 +19,7 @@ jobs: outputs: release_id: ${{ steps.create-release.outputs.id }} - release_date: ${{ fromJSON(steps.create-release.outputs.assets)[0].created_at }} + release_date: ${{ fromJSON(steps.create-release.outputs.assets)[0].published_at }} version: ${{ steps.release-info.outputs.version }} filename: ${{ steps.release-info.outputs.filename }} file_hash: ${{ steps.release-info.outputs.file_hash }} @@ -104,7 +104,7 @@ jobs: ref=${{ github.ref_name }} semver="${ref##nym-connect-}" && semver="${semver##v}" echo "version=${semver}" >> "$GITHUB_OUTPUT" - echo "filename=nym-connect_${version}_x64_en-US.msi" >> "$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: diff --git a/.github/workflows/nym-wallet-publish-macos.yml b/.github/workflows/nym-wallet-publish-macos.yml index 408a3b7406..5db06a8f52 100644 --- a/.github/workflows/nym-wallet-publish-macos.yml +++ b/.github/workflows/nym-wallet-publish-macos.yml @@ -19,7 +19,7 @@ jobs: outputs: release_id: ${{ steps.create-release.outputs.id }} - release_date: ${{ fromJSON(steps.create-release.outputs.assets)[0].created_at }} + release_date: ${{ fromJSON(steps.create-release.outputs.assets)[0].published_at }} version: ${{ steps.release-info.outputs.version }} filename: ${{ steps.release-info.outputs.filename }} file_hash: ${{ steps.release-info.outputs.file_hash }} @@ -106,7 +106,7 @@ jobs: ref=${{ github.ref_name }} semver="${ref##nym-wallet-}" && semver="${semver##v}" echo "version=${semver}" >> "$GITHUB_OUTPUT" - echo "filename=nym-wallet_${version}_x64.dmg" >> "$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: diff --git a/.github/workflows/nym-wallet-publish-ubuntu.yml b/.github/workflows/nym-wallet-publish-ubuntu.yml index 9e9ee904cd..52887950fa 100644 --- a/.github/workflows/nym-wallet-publish-ubuntu.yml +++ b/.github/workflows/nym-wallet-publish-ubuntu.yml @@ -19,7 +19,7 @@ jobs: outputs: release_id: ${{ steps.create-release.outputs.id }} - release_date: ${{ fromJSON(steps.create-release.outputs.assets)[0].created_at }} + release_date: ${{ fromJSON(steps.create-release.outputs.assets)[0].published_at }} version: ${{ steps.release-info.outputs.version }} filename: ${{ steps.release-info.outputs.filename }} file_hash: ${{ steps.release-info.outputs.file_hash }} @@ -83,7 +83,7 @@ jobs: ref=${{ github.ref_name }} semver="${ref##nym-wallet-}" && semver="${semver##v}" echo "version=${semver}" >> "$GITHUB_OUTPUT" - echo "filename=nym-wallet_${version}_amd64.AppImage" >> "$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: diff --git a/.github/workflows/nym-wallet-publish-windows10.yml b/.github/workflows/nym-wallet-publish-windows10.yml index 56a332a75d..a7be3e4a37 100644 --- a/.github/workflows/nym-wallet-publish-windows10.yml +++ b/.github/workflows/nym-wallet-publish-windows10.yml @@ -19,7 +19,7 @@ jobs: outputs: release_id: ${{ steps.create-release.outputs.id }} - release_date: ${{ fromJSON(steps.create-release.outputs.assets)[0].created_at }} + release_date: ${{ fromJSON(steps.create-release.outputs.assets)[0].published_at }} version: ${{ steps.release-info.outputs.version }} filename: ${{ steps.release-info.outputs.filename }} file_hash: ${{ steps.release-info.outputs.file_hash }} @@ -103,7 +103,7 @@ jobs: ref=${{ github.ref_name }} semver="${ref##nym-wallet-}" && semver="${semver##v}" echo "version=${semver}" >> "$GITHUB_OUTPUT" - echo "filename=nym-wallet_${version}_x64_en-US.msi" >> "$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: From a47899aa77a5c1bee423cf7951c72d94bc5b4db5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bogdan-=C8=98tefan=20Neac=C5=9Fu?= Date: Thu, 27 Jul 2023 12:18:08 +0300 Subject: [PATCH 013/361] Apply fix from feature/ephemera to develop too (#3698) --- common/crypto/Cargo.toml | 4 +- .../crypto/src/asymmetric/encryption/mod.rs | 6 + common/crypto/src/asymmetric/identity/mod.rs | 2 + common/nymsphinx/acknowledgements/Cargo.toml | 2 +- common/nymsphinx/addressing/Cargo.toml | 2 +- common/nymsphinx/forwarding/Cargo.toml | 2 +- common/nymsphinx/params/Cargo.toml | 4 + common/nymsphinx/params/src/packet_sizes.rs | 43 +- common/nymsphinx/routing/Cargo.toml | 4 +- common/nymsphinx/types/Cargo.toml | 8 +- common/nymsphinx/types/src/lib.rs | 28 ++ common/topology/Cargo.toml | 4 +- common/topology/src/error.rs | 1 + contracts/Cargo.lock | 448 +----------------- nym-connect/desktop/Cargo.lock | 2 - nym-wallet/Cargo.lock | 137 +----- 16 files changed, 112 insertions(+), 585 deletions(-) diff --git a/common/crypto/Cargo.toml b/common/crypto/Cargo.toml index c6521d9056..27702da016 100644 --- a/common/crypto/Cargo.toml +++ b/common/crypto/Cargo.toml @@ -27,7 +27,7 @@ thiserror = "1.0.37" zeroize = { workspace = true, optional = true, features = ["zeroize_derive"] } # internal -nym-sphinx-types = { path = "../nymsphinx/types", version = "0.2.0" } +nym-sphinx-types = { path = "../nymsphinx/types", version = "0.2.0", default-features = false } nym-pemstore = { path = "../../common/pemstore", version = "0.3.0" } [dev-dependencies] @@ -38,3 +38,5 @@ serde = ["serde_crate", "serde_bytes", "ed25519-dalek/serde", "x25519-dalek/serd asymmetric = ["x25519-dalek", "ed25519-dalek", "zeroize"] hashing = ["blake3", "digest", "hkdf", "hmac", "generic-array"] symmetric = ["aes", "ctr", "cipher", "generic-array"] +sphinx = ["nym-sphinx-types/sphinx"] +outfox = ["nym-sphinx-types/outfox"] diff --git a/common/crypto/src/asymmetric/encryption/mod.rs b/common/crypto/src/asymmetric/encryption/mod.rs index 5052e785a0..6c45d41d60 100644 --- a/common/crypto/src/asymmetric/encryption/mod.rs +++ b/common/crypto/src/asymmetric/encryption/mod.rs @@ -273,36 +273,42 @@ impl PemStorableKey for PrivateKey { } // compatibility with sphinx keys: +#[cfg(feature = "sphinx")] impl From for nym_sphinx_types::PublicKey { fn from(key: PublicKey) -> Self { nym_sphinx_types::PublicKey::from(key.to_bytes()) } } +#[cfg(feature = "sphinx")] impl<'a> From<&'a PublicKey> for nym_sphinx_types::PublicKey { fn from(key: &'a PublicKey) -> Self { nym_sphinx_types::PublicKey::from((*key).to_bytes()) } } +#[cfg(feature = "sphinx")] impl From for PublicKey { fn from(pub_key: nym_sphinx_types::PublicKey) -> Self { Self(x25519_dalek::PublicKey::from(*pub_key.as_bytes())) } } +#[cfg(feature = "sphinx")] impl From for nym_sphinx_types::PrivateKey { fn from(key: PrivateKey) -> Self { nym_sphinx_types::PrivateKey::from(key.to_bytes()) } } +#[cfg(feature = "sphinx")] impl<'a> From<&'a PrivateKey> for nym_sphinx_types::PrivateKey { fn from(key: &'a PrivateKey) -> Self { nym_sphinx_types::PrivateKey::from(key.to_bytes()) } } +#[cfg(feature = "sphinx")] impl From for PrivateKey { fn from(private_key: nym_sphinx_types::PrivateKey) -> Self { let private_key_bytes = private_key.to_bytes(); diff --git a/common/crypto/src/asymmetric/identity/mod.rs b/common/crypto/src/asymmetric/identity/mod.rs index c552206da3..efe11acf4f 100644 --- a/common/crypto/src/asymmetric/identity/mod.rs +++ b/common/crypto/src/asymmetric/identity/mod.rs @@ -5,6 +5,7 @@ pub use ed25519_dalek::ed25519::signature::Signature as SignatureTrait; pub use ed25519_dalek::SignatureError; pub use ed25519_dalek::{Verifier, PUBLIC_KEY_LENGTH, SECRET_KEY_LENGTH, SIGNATURE_LENGTH}; use nym_pemstore::traits::{PemStorableKey, PemStorableKeyPair}; +#[cfg(feature = "sphinx")] use nym_sphinx_types::{DestinationAddressBytes, DESTINATION_ADDRESS_LENGTH}; use std::fmt::{self, Display, Formatter}; use std::str::FromStr; @@ -113,6 +114,7 @@ impl Display for PublicKey { } impl PublicKey { + #[cfg(feature = "sphinx")] pub fn derive_destination_address(&self) -> DestinationAddressBytes { let mut temporary_address = [0u8; DESTINATION_ADDRESS_LENGTH]; let public_key_bytes = self.to_bytes(); diff --git a/common/nymsphinx/acknowledgements/Cargo.toml b/common/nymsphinx/acknowledgements/Cargo.toml index 95b3d8b30c..da9977d1f3 100644 --- a/common/nymsphinx/acknowledgements/Cargo.toml +++ b/common/nymsphinx/acknowledgements/Cargo.toml @@ -19,7 +19,7 @@ nym-pemstore = { path = "../../pemstore" } nym-sphinx-addressing = { path = "../addressing" } nym-sphinx-params = { path = "../params" } nym-sphinx-routing = { path = "../routing" } -nym-sphinx-types = { path = "../types" } +nym-sphinx-types = { path = "../types", features = ["sphinx"] } nym-topology = { path = "../../topology" } [features] diff --git a/common/nymsphinx/addressing/Cargo.toml b/common/nymsphinx/addressing/Cargo.toml index f9c646e04b..c6bc2856e3 100644 --- a/common/nymsphinx/addressing/Cargo.toml +++ b/common/nymsphinx/addressing/Cargo.toml @@ -9,7 +9,7 @@ repository = { workspace = true } [dependencies] nym-crypto = { path = "../../crypto", features = ["asymmetric"] } # all addresses are expressed in terms on their crypto keys -nym-sphinx-types = { path = "../types" } # we need to be able to refer to some types defined inside sphinx crate +nym-sphinx-types = { path = "../types", features = ["sphinx"] } # we need to be able to refer to some types defined inside sphinx crate serde = "1.0" # implementing serialization/deserialization for some types, like `Recipient` thiserror = "1.0.37" diff --git a/common/nymsphinx/forwarding/Cargo.toml b/common/nymsphinx/forwarding/Cargo.toml index 6509098398..4060cbf076 100644 --- a/common/nymsphinx/forwarding/Cargo.toml +++ b/common/nymsphinx/forwarding/Cargo.toml @@ -10,6 +10,6 @@ repository = { workspace = true } [dependencies] nym-sphinx-addressing = { path = "../addressing" } nym-sphinx-params = { path = "../params" } -nym-sphinx-types = { path = "../types" } +nym-sphinx-types = { path = "../types", features = ["sphinx", "outfox"] } nym-outfox = { path = "../../../nym-outfox" } thiserror = "1" diff --git a/common/nymsphinx/params/Cargo.toml b/common/nymsphinx/params/Cargo.toml index bc9b40b9a8..57872bb0ca 100644 --- a/common/nymsphinx/params/Cargo.toml +++ b/common/nymsphinx/params/Cargo.toml @@ -13,3 +13,7 @@ serde = { workspace = true, features = ["derive"] } nym-crypto = { path = "../../crypto", features = ["hashing", "symmetric"] } nym-sphinx-types = { path = "../types" } + +[features] +sphinx = ["nym-crypto/sphinx", "nym-sphinx-types/outfox"] +outfox = ["nym-crypto/outfox", "nym-sphinx-types/outfox"] diff --git a/common/nymsphinx/params/src/packet_sizes.rs b/common/nymsphinx/params/src/packet_sizes.rs index a6613f3024..58d9681efc 100644 --- a/common/nymsphinx/params/src/packet_sizes.rs +++ b/common/nymsphinx/params/src/packet_sizes.rs @@ -1,11 +1,11 @@ // Copyright 2021-2023 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use crate::{PacketType, FRAG_ID_LEN}; -use nym_sphinx_types::header::HEADER_SIZE; -use nym_sphinx_types::{ - MIN_PACKET_SIZE, MIX_PARAMS_LEN, OUTFOX_PACKET_OVERHEAD, PAYLOAD_OVERHEAD_SIZE, -}; +use crate::PacketType; +#[cfg(feature = "sphinx")] +use nym_sphinx_types::{header::HEADER_SIZE, PAYLOAD_OVERHEAD_SIZE}; +#[cfg(feature = "outfox")] +use nym_sphinx_types::{MIN_PACKET_SIZE, MIX_PARAMS_LEN, OUTFOX_PACKET_OVERHEAD}; use serde::{Deserialize, Serialize}; use std::cmp::Ordering; use std::convert::TryFrom; @@ -14,6 +14,7 @@ use std::str::FromStr; use thiserror::Error; // each sphinx packet contains mandatory header and payload padding + markers +#[cfg(feature = "sphinx")] const SPHINX_PACKET_OVERHEAD: usize = HEADER_SIZE + PAYLOAD_OVERHEAD_SIZE; // it's up to the smart people to figure those values out : ) @@ -22,15 +23,23 @@ const SPHINX_PACKET_OVERHEAD: usize = HEADER_SIZE + PAYLOAD_OVERHEAD_SIZE; // TODO: I'm not entirely sure if we can easily extract `::NonceSize` // into a const usize before relevant stuff is stabilised in rust... +#[cfg(feature = "sphinx")] const ACK_IV_SIZE: usize = 16; -const ACK_PACKET_SIZE: usize = ACK_IV_SIZE + FRAG_ID_LEN + SPHINX_PACKET_OVERHEAD; +#[cfg(feature = "sphinx")] +const ACK_PACKET_SIZE: usize = ACK_IV_SIZE + crate::FRAG_ID_LEN + SPHINX_PACKET_OVERHEAD; +#[cfg(feature = "sphinx")] const REGULAR_PACKET_SIZE: usize = 2 * 1024 + SPHINX_PACKET_OVERHEAD; +#[cfg(feature = "sphinx")] const EXTENDED_PACKET_SIZE_8: usize = 8 * 1024 + SPHINX_PACKET_OVERHEAD; +#[cfg(feature = "sphinx")] const EXTENDED_PACKET_SIZE_16: usize = 16 * 1024 + SPHINX_PACKET_OVERHEAD; +#[cfg(feature = "sphinx")] const EXTENDED_PACKET_SIZE_32: usize = 32 * 1024 + SPHINX_PACKET_OVERHEAD; +#[cfg(feature = "outfox")] const OUTFOX_ACK_PACKET_SIZE: usize = MIN_PACKET_SIZE + OUTFOX_PACKET_OVERHEAD; +#[cfg(feature = "outfox")] const OUTFOX_REGULAR_PACKET_SIZE: usize = 2 * 1024 + OUTFOX_PACKET_OVERHEAD; #[derive(Debug, Error)] @@ -153,38 +162,55 @@ impl TryFrom for PacketSize { impl PacketSize { pub const fn size(self) -> usize { + #[allow(unreachable_patterns)] match self { + #[cfg(feature = "sphinx")] PacketSize::RegularPacket => REGULAR_PACKET_SIZE, + #[cfg(feature = "sphinx")] PacketSize::AckPacket => ACK_PACKET_SIZE, + #[cfg(feature = "sphinx")] PacketSize::ExtendedPacket8 => EXTENDED_PACKET_SIZE_8, + #[cfg(feature = "sphinx")] PacketSize::ExtendedPacket16 => EXTENDED_PACKET_SIZE_16, + #[cfg(feature = "sphinx")] PacketSize::ExtendedPacket32 => EXTENDED_PACKET_SIZE_32, + #[cfg(feature = "outfox")] PacketSize::OutfoxRegularPacket => OUTFOX_REGULAR_PACKET_SIZE, + #[cfg(feature = "outfox")] PacketSize::OutfoxAckPacket => OUTFOX_ACK_PACKET_SIZE, + _ => 0, } } pub const fn header_size(&self) -> usize { + #[allow(unreachable_patterns)] match self { + #[cfg(feature = "sphinx")] PacketSize::RegularPacket | PacketSize::AckPacket | PacketSize::ExtendedPacket8 | PacketSize::ExtendedPacket16 | PacketSize::ExtendedPacket32 => HEADER_SIZE, + #[cfg(feature = "outfox")] PacketSize::OutfoxRegularPacket | PacketSize::OutfoxAckPacket => MIX_PARAMS_LEN, + _ => 0, } } pub const fn payload_overhead(&self) -> usize { + #[allow(unreachable_patterns)] match self { + #[cfg(feature = "sphinx")] PacketSize::RegularPacket | PacketSize::AckPacket | PacketSize::ExtendedPacket8 | PacketSize::ExtendedPacket16 | PacketSize::ExtendedPacket32 => PAYLOAD_OVERHEAD_SIZE, + #[cfg(feature = "outfox")] PacketSize::OutfoxRegularPacket | PacketSize::OutfoxAckPacket => { OUTFOX_PACKET_OVERHEAD - MIX_PARAMS_LEN // Mix params are calculated into the total overhead so we take them out here } + _ => 0, } } @@ -242,11 +268,16 @@ impl PacketSize { plaintext_size: usize, packet_type: PacketType, ) -> Result { + #[allow(unreachable_patterns)] let overhead = match packet_type { + #[cfg(feature = "sphinx")] PacketType::Mix => SPHINX_PACKET_OVERHEAD, #[allow(deprecated)] + #[cfg(feature = "sphinx")] PacketType::Vpn => SPHINX_PACKET_OVERHEAD, + #[cfg(feature = "outfox")] PacketType::Outfox => OUTFOX_PACKET_OVERHEAD, + _ => 0, }; let packet_size = plaintext_size + overhead; Self::get_type(packet_size) diff --git a/common/nymsphinx/routing/Cargo.toml b/common/nymsphinx/routing/Cargo.toml index c0f7cf7628..37b8d5a4dc 100644 --- a/common/nymsphinx/routing/Cargo.toml +++ b/common/nymsphinx/routing/Cargo.toml @@ -11,4 +11,6 @@ repository = { workspace = true } thiserror = { workspace = true } nym-sphinx-addressing = { path = "../addressing" } -nym-sphinx-types = { path = "../types" } \ No newline at end of file +nym-sphinx-types = { path = "../types", features = ["sphinx"] } + +[features] \ No newline at end of file diff --git a/common/nymsphinx/types/Cargo.toml b/common/nymsphinx/types/Cargo.toml index 2c52ef03d9..0569ef1406 100644 --- a/common/nymsphinx/types/Cargo.toml +++ b/common/nymsphinx/types/Cargo.toml @@ -8,6 +8,10 @@ license = { workspace = true } repository = { workspace = true } [dependencies] -sphinx-packet = { version = "0.1.0" } -nym-outfox = { path = "../../../nym-outfox" } +sphinx-packet = { version = "0.1.0", optional = true } +nym-outfox = { path = "../../../nym-outfox", optional = true } thiserror = "1" + +[features] +sphinx = ["sphinx-packet"] +outfox = ["nym-outfox"] diff --git a/common/nymsphinx/types/src/lib.rs b/common/nymsphinx/types/src/lib.rs index bd3063f939..d75060e9b0 100644 --- a/common/nymsphinx/types/src/lib.rs +++ b/common/nymsphinx/types/src/lib.rs @@ -1,12 +1,15 @@ // Copyright 2021 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 +#[cfg(feature = "outfox")] pub use nym_outfox::{ constants::MIN_PACKET_SIZE, constants::MIX_PARAMS_LEN, constants::OUTFOX_PACKET_OVERHEAD, error::OutfoxError, }; // re-exporting types and constants available in sphinx +#[cfg(feature = "outfox")] use nym_outfox::packet::{OutfoxPacket, OutfoxProcessedPacket}; +#[cfg(feature = "sphinx")] pub use sphinx_packet::{ constants::{ self, DESTINATION_ADDRESS_LENGTH, IDENTIFIER_LENGTH, MAX_PATH_LENGTH, NODE_ADDRESS_LENGTH, @@ -20,6 +23,7 @@ pub use sphinx_packet::{ surb::{SURBMaterial, SURB}, Error as SphinxError, ProcessedPacket, }; +#[cfg(feature = "sphinx")] use sphinx_packet::{SphinxPacket, SphinxPacketBuilder}; use std::{array::TryFromSliceError, fmt}; use thiserror::Error; @@ -27,9 +31,11 @@ use thiserror::Error; #[derive(Error, Debug)] pub enum NymPacketError { #[error("Sphinx error: {0}")] + #[cfg(feature = "sphinx")] Sphinx(#[from] sphinx_packet::Error), #[error("Outfox error: {0}")] + #[cfg(feature = "outfox")] Outfox(#[from] nym_outfox::error::OutfoxError), #[error("{0}")] @@ -38,31 +44,40 @@ pub enum NymPacketError { #[allow(clippy::large_enum_variant)] pub enum NymPacket { + #[cfg(feature = "sphinx")] Sphinx(SphinxPacket), + #[cfg(feature = "outfox")] Outfox(OutfoxPacket), } pub enum NymProcessedPacket { + #[cfg(feature = "sphinx")] Sphinx(ProcessedPacket), + #[cfg(feature = "outfox")] Outfox(OutfoxProcessedPacket), } impl fmt::Debug for NymPacket { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + #[allow(unreachable_patterns)] match &self { + #[cfg(feature = "sphinx")] NymPacket::Sphinx(packet) => f .debug_struct("NymPacket::Sphinx") .field("len", &packet.len()) .finish(), + #[cfg(feature = "outfox")] NymPacket::Outfox(packet) => f .debug_struct("NymPacket::Outfox") .field("len", &packet.len()) .finish(), + _ => write!(f, ""), } } } impl NymPacket { + #[cfg(feature = "sphinx")] pub fn sphinx_build>( size: usize, message: M, @@ -76,10 +91,12 @@ impl NymPacket { .build_packet(message, route, destination, delays)?, )) } + #[cfg(feature = "sphinx")] pub fn sphinx_from_bytes(bytes: &[u8]) -> Result { Ok(NymPacket::Sphinx(SphinxPacket::from_bytes(bytes)?)) } + #[cfg(feature = "outfox")] pub fn outfox_build>( payload: M, route: &[Node], @@ -94,14 +111,19 @@ impl NymPacket { )?)) } + #[cfg(feature = "outfox")] pub fn outfox_from_bytes(bytes: &[u8]) -> Result { Ok(NymPacket::Outfox(OutfoxPacket::try_from(bytes)?)) } pub fn len(&self) -> usize { + #[allow(unreachable_patterns)] match self { + #[cfg(feature = "sphinx")] NymPacket::Sphinx(packet) => packet.len(), + #[cfg(feature = "outfox")] NymPacket::Outfox(packet) => packet.len(), + _ => 0, } } @@ -110,12 +132,17 @@ impl NymPacket { } pub fn to_bytes(&self) -> Result, NymPacketError> { + #[allow(unreachable_patterns)] match self { + #[cfg(feature = "sphinx")] NymPacket::Sphinx(packet) => Ok(packet.to_bytes()), + #[cfg(feature = "outfox")] NymPacket::Outfox(packet) => Ok(packet.to_bytes()?), + _ => Ok(vec![]), } } + #[cfg(feature = "sphinx")] pub fn process( self, node_secret_key: &PrivateKey, @@ -124,6 +151,7 @@ impl NymPacket { NymPacket::Sphinx(packet) => { Ok(NymProcessedPacket::Sphinx(packet.process(node_secret_key)?)) } + #[cfg(feature = "outfox")] NymPacket::Outfox(mut packet) => { let next_address = packet.decode_next_layer(node_secret_key)?; Ok(NymProcessedPacket::Outfox(OutfoxProcessedPacket::new( diff --git a/common/topology/Cargo.toml b/common/topology/Cargo.toml index 1d41a93ee4..bfe2c98573 100644 --- a/common/topology/Cargo.toml +++ b/common/topology/Cargo.toml @@ -19,10 +19,10 @@ thiserror = "1.0.37" async-trait = { workspace = true, optional = true } ## internal -nym-crypto = { path = "../crypto" } +nym-crypto = { path = "../crypto", features = ["sphinx", "outfox"] } nym-mixnet-contract-common = { path = "../cosmwasm-smart-contracts/mixnet-contract" } nym-sphinx-addressing = { path = "../nymsphinx/addressing" } -nym-sphinx-types = { path = "../nymsphinx/types" } +nym-sphinx-types = { path = "../nymsphinx/types", features = ["sphinx", "outfox"] } nym-sphinx-routing = { path = "../nymsphinx/routing" } nym-bin-common = { path = "../bin-common" } diff --git a/common/topology/src/error.rs b/common/topology/src/error.rs index bb36b42886..3f2523576f 100644 --- a/common/topology/src/error.rs +++ b/common/topology/src/error.rs @@ -41,6 +41,7 @@ pub enum NymTopologyError { PayloadBuilder, #[error("Outfox: {0}")] + #[cfg(feature = "outfox")] Outfox(#[from] nym_sphinx_types::OutfoxError), #[error("{0}")] diff --git a/contracts/Cargo.lock b/contracts/Cargo.lock index 0c3efd0a24..3d719dd6dc 100644 --- a/contracts/Cargo.lock +++ b/contracts/Cargo.lock @@ -2,29 +2,6 @@ # It is not intended for manual editing. version = 3 -[[package]] -name = "aead" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0" -dependencies = [ - "crypto-common", - "generic-array 0.14.6", -] - -[[package]] -name = "aes" -version = "0.7.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e8b47f52ea9bae42228d07ec09eb676433d7c4ed1ebdf0f1d1c29ed446f1ab8" -dependencies = [ - "cfg-if", - "cipher 0.3.0", - "cpufeatures", - "ctr", - "opaque-debug 0.3.0", -] - [[package]] name = "ahash" version = "0.7.6" @@ -42,18 +19,6 @@ version = "1.0.70" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7de8ce5e0f9f8d88245311066a578d72b7af3e7088f32783804676302df237e4" -[[package]] -name = "arrayref" -version = "0.3.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b4930d2cb77ce62f89ee5d5289b4ac049559b1c45539271f5ed4fdc7db34545" - -[[package]] -name = "arrayvec" -version = "0.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711" - [[package]] name = "autocfg" version = "1.1.0" @@ -90,39 +55,13 @@ version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" -[[package]] -name = "blake2" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94cb07b0da6a73955f8fb85d24c466778e70cda767a568229b104f0264089330" -dependencies = [ - "byte-tools", - "crypto-mac 0.7.0", - "digest 0.8.1", - "opaque-debug 0.2.3", -] - -[[package]] -name = "blake3" -version = "1.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "199c42ab6972d92c9f8995f086273d25c42fc0f7b2a1fcefba465c1352d25ba5" -dependencies = [ - "arrayref", - "arrayvec", - "cc", - "cfg-if", - "constant_time_eq", - "digest 0.10.7", -] - [[package]] name = "block-buffer" version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" dependencies = [ - "generic-array 0.14.6", + "generic-array", ] [[package]] @@ -131,7 +70,7 @@ version = "0.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" dependencies = [ - "generic-array 0.14.6", + "generic-array", ] [[package]] @@ -146,12 +85,6 @@ version = "3.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b1ce199063694f33ffb7dd4e0ee620741495c32833cde5aa08f02a0bf96f0c8" -[[package]] -name = "byte-tools" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3b5ca7a04898ad4bcd41c90c5285445ff5b791899bb1b0abdd2a2aa791211d7" - [[package]] name = "byteorder" version = "1.4.3" @@ -179,60 +112,6 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" -[[package]] -name = "chacha" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ddf3c081b5fba1e5615640aae998e0fbd10c24cbd897ee39ed754a77601a4862" -dependencies = [ - "byteorder", - "keystream", -] - -[[package]] -name = "chacha20" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3613f74bd2eac03dad61bd53dbe620703d4371614fe0bc3b9f04dd36fe4e818" -dependencies = [ - "cfg-if", - "cipher 0.4.4", - "cpufeatures", -] - -[[package]] -name = "chacha20poly1305" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10cd79432192d1c0f4e1a0fef9527696cc039165d729fb41b3f4f4f354c2dc35" -dependencies = [ - "aead", - "chacha20", - "cipher 0.4.4", - "poly1305", - "zeroize", -] - -[[package]] -name = "cipher" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ee52072ec15386f770805afd189a01c8841be8696bed250fa2f13c4c0d6dfb7" -dependencies = [ - "generic-array 0.14.6", -] - -[[package]] -name = "cipher" -version = "0.4.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" -dependencies = [ - "crypto-common", - "inout", - "zeroize", -] - [[package]] name = "coconut-test" version = "0.1.0" @@ -264,12 +143,6 @@ version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "520fbf3c07483f94e3e3ca9d0cfd913d7718ef2483d2cfd91c0d9e91474ab913" -[[package]] -name = "constant_time_eq" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7144d30dcf0fafbce74250a3963025d8d52177934239851c917d29f1df280c2" - [[package]] name = "cosmwasm-crypto" version = "1.2.5" @@ -355,49 +228,6 @@ dependencies = [ "libc", ] -[[package]] -name = "crossbeam-channel" -version = "0.5.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a33c2bf77f2df06183c3aa30d1e96c0695a313d4f9c453cc3762a6db39f99200" -dependencies = [ - "cfg-if", - "crossbeam-utils", -] - -[[package]] -name = "crossbeam-deque" -version = "0.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce6fd6f855243022dcecf8702fef0c297d4338e226845fe067f6341ad9fa0cef" -dependencies = [ - "cfg-if", - "crossbeam-epoch", - "crossbeam-utils", -] - -[[package]] -name = "crossbeam-epoch" -version = "0.9.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae211234986c545741a7dc064309f67ee1e5ad243d0e48335adc0484d960bcc7" -dependencies = [ - "autocfg", - "cfg-if", - "crossbeam-utils", - "memoffset", - "scopeguard", -] - -[[package]] -name = "crossbeam-utils" -version = "0.8.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a22b2d63d4d1dc0b7f1b6b2747dd0088008a9be28b6ddf0b1e7d335e3037294" -dependencies = [ - "cfg-if", -] - [[package]] name = "crunchy" version = "0.2.2" @@ -410,9 +240,9 @@ version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ef2b4b23cddf68b89b8f8069890e8c270d54e2d5fe1b143820234805e4cb17ef" dependencies = [ - "generic-array 0.14.6", + "generic-array", "rand_core 0.6.4", - "subtle 2.4.1", + "subtle", "zeroize", ] @@ -422,40 +252,10 @@ version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" dependencies = [ - "generic-array 0.14.6", - "rand_core 0.6.4", + "generic-array", "typenum", ] -[[package]] -name = "crypto-mac" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4434400df11d95d556bac068ddfedd482915eb18fe8bea89bc80b6e4b1c179e5" -dependencies = [ - "generic-array 0.12.4", - "subtle 1.0.0", -] - -[[package]] -name = "crypto-mac" -version = "0.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1d1a86f49236c215f271d40892d5fc950490551400b02ef360692c29815c714" -dependencies = [ - "generic-array 0.14.6", - "subtle 2.4.1", -] - -[[package]] -name = "ctr" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "049bb91fb4aaf0e3c7efa6cd5ef877dbbbd15b39dad06d9948de4ec8a75761ea" -dependencies = [ - "cipher 0.3.0", -] - [[package]] name = "curve25519-dalek" version = "3.2.0" @@ -465,7 +265,7 @@ dependencies = [ "byteorder", "digest 0.9.0", "rand_core 0.5.1", - "subtle 2.4.1", + "subtle", "zeroize", ] @@ -676,22 +476,13 @@ dependencies = [ "syn 1.0.109", ] -[[package]] -name = "digest" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3d0c8c8752312f9713efd397ff63acb9f85585afbf179282e720e7704954dd5" -dependencies = [ - "generic-array 0.12.4", -] - [[package]] name = "digest" version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" dependencies = [ - "generic-array 0.14.6", + "generic-array", ] [[package]] @@ -702,7 +493,7 @@ checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ "block-buffer 0.10.4", "crypto-common", - "subtle 2.4.1", + "subtle", ] [[package]] @@ -778,12 +569,12 @@ dependencies = [ "der", "digest 0.10.7", "ff", - "generic-array 0.14.6", + "generic-array", "group", "pkcs8", "rand_core 0.6.4", "sec1", - "subtle 2.4.1", + "subtle", "zeroize", ] @@ -844,7 +635,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d013fc25338cc558c5c2cfbad646908fb23591e2404481826742b651c9af7160" dependencies = [ "rand_core 0.6.4", - "subtle 2.4.1", + "subtle", ] [[package]] @@ -963,15 +754,6 @@ dependencies = [ "slab", ] -[[package]] -name = "generic-array" -version = "0.12.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffdf9f34f1447443d37393cc6c2b8313aebddcd96906caf34e54c68d8e57d7bd" -dependencies = [ - "typenum", -] - [[package]] name = "generic-array" version = "0.14.6" @@ -1002,10 +784,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "be4136b2a15dd319360be1c07d9933517ccf0be8f16bf62a3bee4f0d618df427" dependencies = [ "cfg-if", - "js-sys", "libc", "wasi 0.11.0+wasi-snapshot-preview1", - "wasm-bindgen", ] [[package]] @@ -1041,7 +821,7 @@ checksum = "5dfbfb3a6cfbd390d5c9564ab283a0349b9b9fcd46a706c1eb10e0db70bfbac7" dependencies = [ "ff", "rand_core 0.6.4", - "subtle 2.4.1", + "subtle", ] [[package]] @@ -1065,26 +845,6 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" -[[package]] -name = "hkdf" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01706d578d5c281058480e673ae4086a9f4710d8df1ad80a5b03e39ece5f886b" -dependencies = [ - "digest 0.9.0", - "hmac 0.11.0", -] - -[[package]] -name = "hmac" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a2a2320eb7ec0ebe8da8f744d7812d9fc4cb4d09344ac01898dbcb6a20ae69b" -dependencies = [ - "crypto-mac 0.11.1", - "digest 0.9.0", -] - [[package]] name = "hmac" version = "0.12.1" @@ -1120,15 +880,6 @@ dependencies = [ "unicode-normalization", ] -[[package]] -name = "inout" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5" -dependencies = [ - "generic-array 0.14.6", -] - [[package]] name = "instant" version = "0.1.12" @@ -1194,12 +945,6 @@ dependencies = [ "sha2 0.10.6", ] -[[package]] -name = "keystream" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c33070833c9ee02266356de0c43f723152bd38bd96ddf52c82b3af10c9138b28" - [[package]] name = "lazy_static" version = "1.4.0" @@ -1224,12 +969,6 @@ dependencies = [ "pkg-config", ] -[[package]] -name = "libm" -version = "0.2.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "348108ab3fba42ec82ff6e9564fc4ca0247bdccdc68dd8af9764bbc79c3c8ffb" - [[package]] name = "libz-sys" version = "1.1.8" @@ -1248,18 +987,6 @@ version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ece97ea872ece730aed82664c424eb4c8291e1ff2480247ccf7409044bc6479f" -[[package]] -name = "lioness" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ae926706ba42c425c9457121178330d75e273df2e82e28b758faf3de3a9acb9" -dependencies = [ - "arrayref", - "blake2", - "chacha", - "keystream", -] - [[package]] name = "log" version = "0.4.17" @@ -1275,15 +1002,6 @@ version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" -[[package]] -name = "memoffset" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a634b1c61a95585bd15607c6ab0c4e5b226e695ff2800ba0cdccddf208c406c" -dependencies = [ - "autocfg", -] - [[package]] name = "mixnet-vesting-integration-tests" version = "0.1.0" @@ -1300,26 +1018,6 @@ dependencies = [ "rand_chacha 0.2.2", ] -[[package]] -name = "num-traits" -version = "0.2.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" -dependencies = [ - "autocfg", - "libm", -] - -[[package]] -name = "num_cpus" -version = "1.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" -dependencies = [ - "hermit-abi", - "libc", -] - [[package]] name = "nym-coconut-bandwidth" version = "0.1.0" @@ -1507,23 +1205,6 @@ dependencies = [ "thiserror", ] -[[package]] -name = "nym-outfox" -version = "0.1.0" -dependencies = [ - "blake3", - "chacha20", - "chacha20poly1305", - "curve25519-dalek", - "getrandom 0.2.10", - "log", - "rand 0.7.3", - "rayon", - "sphinx-packet", - "thiserror", - "zeroize", -] - [[package]] name = "nym-pemstore" version = "0.3.0" @@ -1571,8 +1252,6 @@ dependencies = [ name = "nym-sphinx-types" version = "0.2.0" dependencies = [ - "nym-outfox", - "sphinx-packet", "thiserror", ] @@ -1616,12 +1295,6 @@ version = "1.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3" -[[package]] -name = "opaque-debug" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2839e79665f131bdb5782e51f2c6c9599c133c6098982a54c794358bf432529c" - [[package]] name = "opaque-debug" version = "0.3.0" @@ -1673,17 +1346,6 @@ version = "0.3.26" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ac9a59f73473f1b8d852421e59e64809f025994837ef743615c6d0c5b305160" -[[package]] -name = "poly1305" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8159bd90725d2df49889a078b54f4f79e87f1f8a8444194cdca81d38f5393abf" -dependencies = [ - "cpufeatures", - "opaque-debug 0.3.0", - "universal-hash", -] - [[package]] name = "ppv-lite86" version = "0.2.17" @@ -1823,16 +1485,6 @@ dependencies = [ "getrandom 0.2.10", ] -[[package]] -name = "rand_distr" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9e9532ada3929fb8b2e9dbe28d1e06c9b2cc65813f074fcb6bd5fbefeff9d56" -dependencies = [ - "num-traits", - "rand 0.7.3", -] - [[package]] name = "rand_hc" version = "0.2.0" @@ -1842,28 +1494,6 @@ dependencies = [ "rand_core 0.5.1", ] -[[package]] -name = "rayon" -version = "1.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d2df5196e37bcc87abebc0053e20787d73847bb33134a69841207dd0a47f03b" -dependencies = [ - "either", - "rayon-core", -] - -[[package]] -name = "rayon-core" -version = "1.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b8f95bd6966f5c87776639160a66bd8ab9895d9d4ab01ddba9fc60661aebe8d" -dependencies = [ - "crossbeam-channel", - "crossbeam-deque", - "crossbeam-utils", - "num_cpus", -] - [[package]] name = "redox_syscall" version = "0.3.5" @@ -1895,7 +1525,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7743f17af12fa0b03b803ba12cd6a8d9483a587e89c69445e3909655c0b9fabb" dependencies = [ "crypto-bigint", - "hmac 0.12.1", + "hmac", "zeroize", ] @@ -1996,12 +1626,6 @@ dependencies = [ "syn 1.0.109", ] -[[package]] -name = "scopeguard" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" - [[package]] name = "sec1" version = "0.3.0" @@ -2010,9 +1634,9 @@ checksum = "3be24c1842290c45df0a7bf069e0c268a747ad05a192f2fd7dcfdbc1cba40928" dependencies = [ "base16ct", "der", - "generic-array 0.14.6", + "generic-array", "pkcs8", - "subtle 2.4.1", + "subtle", "zeroize", ] @@ -2103,7 +1727,7 @@ dependencies = [ "cfg-if", "cpufeatures", "digest 0.9.0", - "opaque-debug 0.3.0", + "opaque-debug", ] [[package]] @@ -2136,30 +1760,6 @@ dependencies = [ "autocfg", ] -[[package]] -name = "sphinx-packet" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc43eda802856ee82a7555c7b75ceb9e07451741c7a2f5f23d036020e01189d4" -dependencies = [ - "aes", - "arrayref", - "blake2", - "bs58", - "byteorder", - "chacha", - "curve25519-dalek", - "digest 0.9.0", - "hkdf", - "hmac 0.11.0", - "lioness", - "log", - "rand 0.7.3", - "rand_distr", - "sha2 0.9.9", - "subtle 2.4.1", -] - [[package]] name = "spki" version = "0.6.0" @@ -2176,12 +1776,6 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" -[[package]] -name = "subtle" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d67a5a62ba6e01cb2192ff309324cb4875d0c451d55fe2319433abe7a05a8ee" - [[package]] name = "subtle" version = "2.4.1" @@ -2333,16 +1927,6 @@ dependencies = [ "tinyvec", ] -[[package]] -name = "universal-hash" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea" -dependencies = [ - "crypto-common", - "subtle 2.4.1", -] - [[package]] name = "url" version = "2.3.1" diff --git a/nym-connect/desktop/Cargo.lock b/nym-connect/desktop/Cargo.lock index 6287fadf38..c7bb0c20a6 100644 --- a/nym-connect/desktop/Cargo.lock +++ b/nym-connect/desktop/Cargo.lock @@ -4179,8 +4179,6 @@ dependencies = [ name = "nym-sphinx-types" version = "0.2.0" dependencies = [ - "nym-outfox", - "sphinx-packet", "thiserror", ] diff --git a/nym-wallet/Cargo.lock b/nym-wallet/Cargo.lock index 2da872c4fc..5768438108 100644 --- a/nym-wallet/Cargo.lock +++ b/nym-wallet/Cargo.lock @@ -120,12 +120,6 @@ version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a4c527152e37cf757a3f78aae5a06fbeefdb07ccc535c980a3208ee3060dd544" -[[package]] -name = "arrayvec" -version = "0.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711" - [[package]] name = "async-trait" version = "0.1.64" @@ -301,20 +295,6 @@ dependencies = [ "digest 0.10.7", ] -[[package]] -name = "blake3" -version = "1.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "199c42ab6972d92c9f8995f086273d25c42fc0f7b2a1fcefba465c1352d25ba5" -dependencies = [ - "arrayref", - "arrayvec", - "cc", - "cfg-if", - "constant_time_eq", - "digest 0.10.7", -] - [[package]] name = "block" version = "0.1.6" @@ -525,30 +505,6 @@ dependencies = [ "keystream", ] -[[package]] -name = "chacha20" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3613f74bd2eac03dad61bd53dbe620703d4371614fe0bc3b9f04dd36fe4e818" -dependencies = [ - "cfg-if", - "cipher 0.4.4", - "cpufeatures", -] - -[[package]] -name = "chacha20poly1305" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10cd79432192d1c0f4e1a0fef9527696cc039165d729fb41b3f4f4f354c2dc35" -dependencies = [ - "aead", - "chacha20", - "cipher 0.4.4", - "poly1305", - "zeroize", -] - [[package]] name = "cipher" version = "0.3.0" @@ -566,7 +522,6 @@ checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" dependencies = [ "crypto-common", "inout", - "zeroize", ] [[package]] @@ -700,12 +655,6 @@ version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "520fbf3c07483f94e3e3ca9d0cfd913d7718ef2483d2cfd91c0d9e91474ab913" -[[package]] -name = "constant_time_eq" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7144d30dcf0fafbce74250a3963025d8d52177934239851c917d29f1df280c2" - [[package]] name = "convert_case" version = "0.4.0" @@ -879,30 +828,6 @@ dependencies = [ "crossbeam-utils", ] -[[package]] -name = "crossbeam-deque" -version = "0.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce6fd6f855243022dcecf8702fef0c297d4338e226845fe067f6341ad9fa0cef" -dependencies = [ - "cfg-if", - "crossbeam-epoch", - "crossbeam-utils", -] - -[[package]] -name = "crossbeam-epoch" -version = "0.9.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae211234986c545741a7dc064309f67ee1e5ad243d0e48335adc0484d960bcc7" -dependencies = [ - "autocfg", - "cfg-if", - "crossbeam-utils", - "memoffset 0.9.0", - "scopeguard", -] - [[package]] name = "crossbeam-utils" version = "0.8.14" @@ -1640,7 +1565,7 @@ version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e1c54951450cbd39f3dbcf1005ac413b49487dabf18a720ad2383eccfeffb92" dependencies = [ - "memoffset 0.6.5", + "memoffset", "rustc_version 0.3.3", ] @@ -2915,15 +2840,6 @@ dependencies = [ "autocfg", ] -[[package]] -name = "memoffset" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a634b1c61a95585bd15607c6ab0c4e5b226e695ff2800ba0cdccddf208c406c" -dependencies = [ - "autocfg", -] - [[package]] name = "mime" version = "0.3.16" @@ -3328,23 +3244,6 @@ dependencies = [ "url", ] -[[package]] -name = "nym-outfox" -version = "0.1.0" -dependencies = [ - "blake3", - "chacha20", - "chacha20poly1305", - "curve25519-dalek", - "getrandom 0.2.10", - "log", - "rand 0.7.3", - "rayon", - "sphinx-packet", - "thiserror", - "zeroize", -] - [[package]] name = "nym-pemstore" version = "0.3.0" @@ -3369,7 +3268,6 @@ dependencies = [ name = "nym-sphinx-types" version = "0.2.0" dependencies = [ - "nym-outfox", "sphinx-packet", "thiserror", ] @@ -4074,17 +3972,6 @@ dependencies = [ "miniz_oxide", ] -[[package]] -name = "poly1305" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8159bd90725d2df49889a078b54f4f79e87f1f8a8444194cdca81d38f5393abf" -dependencies = [ - "cpufeatures", - "opaque-debug 0.3.0", - "universal-hash", -] - [[package]] name = "polyval" version = "0.6.0" @@ -4336,28 +4223,6 @@ dependencies = [ "cty", ] -[[package]] -name = "rayon" -version = "1.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d2df5196e37bcc87abebc0053e20787d73847bb33134a69841207dd0a47f03b" -dependencies = [ - "either", - "rayon-core", -] - -[[package]] -name = "rayon-core" -version = "1.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b8f95bd6966f5c87776639160a66bd8ab9895d9d4ab01ddba9fc60661aebe8d" -dependencies = [ - "crossbeam-channel", - "crossbeam-deque", - "crossbeam-utils", - "num_cpus", -] - [[package]] name = "redox_syscall" version = "0.2.16" From 67435e9cdf261f03fb9edb50fa06647117012624 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C4=99drzej=20Stuczy=C5=84ski?= Date: Thu, 27 Jul 2023 10:20:07 +0100 Subject: [PATCH 014/361] Feature/simplify cli parsing (#3699) * added a global flag to disable the printed out banner inside tty * added a 'build-info' command to our binaries * added binary name to BinaryBuildInformation * clippy --- clients/native/src/commands/build_info.rs | 16 +++ clients/native/src/commands/mod.rs | 22 +++-- clients/native/src/main.rs | 11 ++- clients/socks5/src/commands/build_info.rs | 16 +++ clients/socks5/src/commands/mod.rs | 22 +++-- clients/socks5/src/main.rs | 11 ++- .../bin-common/src/build_information/mod.rs | 99 +++++++++++++------ gateway/src/commands/build_info.rs | 16 +++ gateway/src/commands/mod.rs | 5 + gateway/src/main.rs | 17 ++-- mixnode/src/commands/build_info.rs | 16 +++ mixnode/src/commands/mod.rs | 5 + mixnode/src/main.rs | 22 +++-- nym-api/src/support/cli/mod.rs | 5 +- .../network-requester/src/cli/build_info.rs | 16 +++ .../network-requester/src/cli/mod.rs | 22 +++-- .../network-requester/src/core.rs | 4 +- .../network-requester/src/main.rs | 8 +- 18 files changed, 254 insertions(+), 79 deletions(-) create mode 100644 clients/native/src/commands/build_info.rs create mode 100644 clients/socks5/src/commands/build_info.rs create mode 100644 gateway/src/commands/build_info.rs create mode 100644 mixnode/src/commands/build_info.rs create mode 100644 service-providers/network-requester/src/cli/build_info.rs diff --git a/clients/native/src/commands/build_info.rs b/clients/native/src/commands/build_info.rs new file mode 100644 index 0000000000..e3385bb933 --- /dev/null +++ b/clients/native/src/commands/build_info.rs @@ -0,0 +1,16 @@ +// Copyright 2023 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use clap::Args; +use nym_bin_common::bin_info_owned; +use nym_bin_common::output_format::OutputFormat; + +#[derive(Args)] +pub(crate) struct BuildInfo { + #[clap(short, long, default_value_t = OutputFormat::default())] + output: OutputFormat, +} + +pub(crate) fn execute(args: BuildInfo) { + println!("{}", args.output.format(&bin_info_owned!())) +} diff --git a/clients/native/src/commands/mod.rs b/clients/native/src/commands/mod.rs index 08c4f9592d..6badc58748 100644 --- a/clients/native/src/commands/mod.rs +++ b/clients/native/src/commands/mod.rs @@ -10,7 +10,7 @@ use clap::CommandFactory; use clap::{Parser, Subcommand}; use lazy_static::lazy_static; use log::{error, info}; -use nym_bin_common::build_information::BinaryBuildInformation; +use nym_bin_common::bin_info; use nym_bin_common::completions::{fig_generate, ArgShell}; use nym_client_core::client::base_client::storage::gateway_details::{ OnDiskGatewayDetails, PersistedGatewayDetails, @@ -22,12 +22,12 @@ use nym_config::OptionalSet; use std::error::Error; use std::net::IpAddr; +pub(crate) mod build_info; pub(crate) mod init; pub(crate) mod run; lazy_static! { - pub static ref PRETTY_BUILD_INFORMATION: String = - BinaryBuildInformation::new(env!("CARGO_PKG_VERSION")).pretty_print(); + pub static ref PRETTY_BUILD_INFORMATION: String = bin_info!().pretty_print(); } // Helper for passing LONG_VERSION to clap @@ -42,6 +42,10 @@ pub(crate) struct Cli { #[clap(short, long)] pub(crate) config_env_file: Option, + /// Flag used for disabling the printed banner in tty. + #[clap(long)] + pub(crate) no_banner: bool, + #[clap(subcommand)] command: Commands, } @@ -54,6 +58,9 @@ pub(crate) enum Commands { /// Run the Nym client with provided configuration client optionally overriding set parameters Run(run::Run), + /// Show build information of this binary + BuildInfo(build_info::BuildInfo), + /// Generate shell completions Completions(ArgShell), @@ -73,12 +80,13 @@ pub(crate) struct OverrideConfig { enabled_credentials_mode: Option, } -pub(crate) async fn execute(args: &Cli) -> Result<(), Box> { +pub(crate) async fn execute(args: Cli) -> Result<(), Box> { let bin_name = "nym-native-client"; - match &args.command { - Commands::Init(m) => init::execute(m).await?, - Commands::Run(m) => run::execute(m).await?, + match args.command { + Commands::Init(m) => init::execute(&m).await?, + Commands::Run(m) => run::execute(&m).await?, + Commands::BuildInfo(m) => build_info::execute(m), Commands::Completions(s) => s.generate(&mut Cli::command(), bin_name), Commands::GenerateFigSpec => fig_generate(&mut Cli::command(), bin_name), } diff --git a/clients/native/src/main.rs b/clients/native/src/main.rs index cc862b2a88..cf3203b1e3 100644 --- a/clients/native/src/main.rs +++ b/clients/native/src/main.rs @@ -14,10 +14,13 @@ pub mod websocket; #[tokio::main] async fn main() -> Result<(), Box> { - setup_logging(); - maybe_print_banner(crate_name!(), crate_version!()); - let args = commands::Cli::parse(); setup_env(args.config_env_file.as_ref()); - commands::execute(&args).await + + if !args.no_banner { + maybe_print_banner(crate_name!(), crate_version!()); + } + setup_logging(); + + commands::execute(args).await } diff --git a/clients/socks5/src/commands/build_info.rs b/clients/socks5/src/commands/build_info.rs new file mode 100644 index 0000000000..e3385bb933 --- /dev/null +++ b/clients/socks5/src/commands/build_info.rs @@ -0,0 +1,16 @@ +// Copyright 2023 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use clap::Args; +use nym_bin_common::bin_info_owned; +use nym_bin_common::output_format::OutputFormat; + +#[derive(Args)] +pub(crate) struct BuildInfo { + #[clap(short, long, default_value_t = OutputFormat::default())] + output: OutputFormat, +} + +pub(crate) fn execute(args: BuildInfo) { + println!("{}", args.output.format(&bin_info_owned!())) +} diff --git a/clients/socks5/src/commands/mod.rs b/clients/socks5/src/commands/mod.rs index 2b5fe425b6..5ef887a3a2 100644 --- a/clients/socks5/src/commands/mod.rs +++ b/clients/socks5/src/commands/mod.rs @@ -10,7 +10,7 @@ use clap::CommandFactory; use clap::{Parser, Subcommand}; use lazy_static::lazy_static; use log::{error, info}; -use nym_bin_common::build_information::BinaryBuildInformation; +use nym_bin_common::bin_info; use nym_bin_common::completions::{fig_generate, ArgShell}; use nym_client_core::client::base_client::storage::gateway_details::{ OnDiskGatewayDetails, PersistedGatewayDetails, @@ -22,12 +22,12 @@ use nym_config::OptionalSet; use nym_sphinx::params::{PacketSize, PacketType}; use std::error::Error; +pub(crate) mod build_info; pub mod init; pub(crate) mod run; lazy_static! { - pub static ref PRETTY_BUILD_INFORMATION: String = - BinaryBuildInformation::new(env!("CARGO_PKG_VERSION")).pretty_print(); + pub static ref PRETTY_BUILD_INFORMATION: String = bin_info!().pretty_print(); } // Helper for passing LONG_VERSION to clap @@ -42,6 +42,10 @@ pub(crate) struct Cli { #[clap(short, long)] pub(crate) config_env_file: Option, + /// Flag used for disabling the printed banner in tty. + #[clap(long)] + pub(crate) no_banner: bool, + #[clap(subcommand)] command: Commands, } @@ -54,6 +58,9 @@ pub(crate) enum Commands { /// Run the Nym client with provided configuration client optionally overriding set parameters Run(run::Run), + /// Show build information of this binary + BuildInfo(build_info::BuildInfo), + /// Generate shell completions Completions(ArgShell), @@ -74,12 +81,13 @@ pub(crate) struct OverrideConfig { outfox: bool, } -pub(crate) async fn execute(args: &Cli) -> Result<(), Box> { +pub(crate) async fn execute(args: Cli) -> Result<(), Box> { let bin_name = "nym-socks5-client"; - match &args.command { - Commands::Init(m) => init::execute(m).await?, - Commands::Run(m) => run::execute(m).await?, + match args.command { + Commands::Init(m) => init::execute(&m).await?, + Commands::Run(m) => run::execute(&m).await?, + Commands::BuildInfo(m) => build_info::execute(m), Commands::Completions(s) => s.generate(&mut Cli::command(), bin_name), Commands::GenerateFigSpec => fig_generate(&mut Cli::command(), bin_name), } diff --git a/clients/socks5/src/main.rs b/clients/socks5/src/main.rs index b592c62817..8e6d899652 100644 --- a/clients/socks5/src/main.rs +++ b/clients/socks5/src/main.rs @@ -13,10 +13,13 @@ pub mod error; #[tokio::main] async fn main() -> Result<(), Box> { - setup_logging(); - maybe_print_banner(crate_name!(), crate_version!()); - let args = commands::Cli::parse(); setup_env(args.config_env_file.as_ref()); - commands::execute(&args).await + + if !args.no_banner { + maybe_print_banner(crate_name!(), crate_version!()); + } + setup_logging(); + + commands::execute(args).await } diff --git a/common/bin-common/src/build_information/mod.rs b/common/bin-common/src/build_information/mod.rs index d4fa7dd5c6..064f8a60fa 100644 --- a/common/bin-common/src/build_information/mod.rs +++ b/common/bin-common/src/build_information/mod.rs @@ -5,9 +5,13 @@ // and be used by our smart contracts use serde::{Deserialize, Serialize}; +use std::fmt::{Display, Formatter}; #[derive(Debug)] pub struct BinaryBuildInformation { + /// Provides the name of the binary, i.e. the content of `CARGO_PKG_NAME` environmental variable. + pub binary_name: &'static str, + // VERGEN_BUILD_TIMESTAMP /// Provides the build timestamp, for example `2021-02-23T20:14:46.558472672+00:00`. pub build_timestamp: &'static str, @@ -43,8 +47,9 @@ pub struct BinaryBuildInformation { impl BinaryBuildInformation { // explicitly require the build_version to be passed as it's binary specific - pub const fn new(build_version: &'static str) -> Self { + pub const fn new(binary_name: &'static str, build_version: &'static str) -> Self { BinaryBuildInformation { + binary_name, build_timestamp: env!("VERGEN_BUILD_TIMESTAMP"), build_version, commit_sha: env!("VERGEN_GIT_SHA"), @@ -58,6 +63,7 @@ impl BinaryBuildInformation { pub fn to_owned(&self) -> BinaryBuildInformationOwned { BinaryBuildInformationOwned { + binary_name: self.binary_name.to_owned(), build_timestamp: self.build_timestamp.to_owned(), build_version: self.build_version.to_owned(), commit_sha: self.commit_sha.to_owned(), @@ -70,39 +76,15 @@ impl BinaryBuildInformation { } pub fn pretty_print(&self) -> String { - format!( - r#" -{:<20}{} -{:<20}{} -{:<20}{} -{:<20}{} -{:<20}{} -{:<20}{} -{:<20}{} -{:<20}{} -"#, - "Build Timestamp:", - self.build_timestamp, - "Build Version:", - self.build_version, - "Commit SHA:", - self.commit_sha, - "Commit Date:", - self.commit_timestamp, - "Commit Branch:", - self.commit_branch, - "rustc Version:", - self.rustc_version, - "rustc Channel:", - self.rustc_channel, - "cargo Profile:", - self.cargo_profile, - ) + self.to_owned().to_string() } } #[derive(Clone, Debug, Serialize, Deserialize)] pub struct BinaryBuildInformationOwned { + /// Provides the name of the binary, i.e. the content of `CARGO_PKG_NAME` environmental variable. + pub binary_name: String, + // VERGEN_BUILD_TIMESTAMP /// Provides the build timestamp, for example `2021-02-23T20:14:46.558472672+00:00`. pub build_timestamp: String, @@ -135,3 +117,62 @@ pub struct BinaryBuildInformationOwned { /// Provides the cargo profile that was used for the build, for example `debug`. pub cargo_profile: String, } + +impl Display for BinaryBuildInformationOwned { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!( + f, + r#" +{:<20}{} +{:<20}{} +{:<20}{} +{:<20}{} +{:<20}{} +{:<20}{} +{:<20}{} +{:<20}{} +{:<20}{} +"#, + "Binary Name:", + self.binary_name, + "Build Timestamp:", + self.build_timestamp, + "Build Version:", + self.build_version, + "Commit SHA:", + self.commit_sha, + "Commit Date:", + self.commit_timestamp, + "Commit Branch:", + self.commit_branch, + "rustc Version:", + self.rustc_version, + "rustc Channel:", + self.rustc_channel, + "cargo Profile:", + self.cargo_profile, + ) + } +} + +// since this macro will get expanded at the callsite, it will pull in correct binary version +#[macro_export] +macro_rules! bin_info { + () => { + $crate::build_information::BinaryBuildInformation::new( + env!("CARGO_PKG_NAME"), + env!("CARGO_PKG_VERSION"), + ) + }; +} + +#[macro_export] +macro_rules! bin_info_owned { + () => { + $crate::build_information::BinaryBuildInformation::new( + env!("CARGO_PKG_NAME"), + env!("CARGO_PKG_VERSION"), + ) + .to_owned() + }; +} diff --git a/gateway/src/commands/build_info.rs b/gateway/src/commands/build_info.rs new file mode 100644 index 0000000000..e3385bb933 --- /dev/null +++ b/gateway/src/commands/build_info.rs @@ -0,0 +1,16 @@ +// Copyright 2023 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use clap::Args; +use nym_bin_common::bin_info_owned; +use nym_bin_common::output_format::OutputFormat; + +#[derive(Args)] +pub(crate) struct BuildInfo { + #[clap(short, long, default_value_t = OutputFormat::default())] + output: OutputFormat, +} + +pub(crate) fn execute(args: BuildInfo) { + println!("{}", args.output.format(&bin_info_owned!())) +} diff --git a/gateway/src/commands/mod.rs b/gateway/src/commands/mod.rs index 15775ec757..ed70c8b203 100644 --- a/gateway/src/commands/mod.rs +++ b/gateway/src/commands/mod.rs @@ -18,6 +18,7 @@ use std::error::Error; use std::net::IpAddr; use std::path::PathBuf; +pub(crate) mod build_info; pub(crate) mod init; pub(crate) mod node_details; pub(crate) mod run; @@ -37,6 +38,9 @@ pub(crate) enum Commands { /// Sign text to prove ownership of this mixnode Sign(sign::Sign), + /// Show build information of this binary + BuildInfo(build_info::BuildInfo), + /// Generate shell completions Completions(ArgShell), @@ -67,6 +71,7 @@ pub(crate) async fn execute(args: Cli) -> Result<(), Box node_details::execute(m).await?, Commands::Run(m) => run::execute(m).await?, Commands::Sign(m) => sign::execute(m)?, + Commands::BuildInfo(m) => build_info::execute(m), Commands::Completions(s) => s.generate(&mut crate::Cli::command(), bin_name), Commands::GenerateFigSpec => fig_generate(&mut crate::Cli::command(), bin_name), } diff --git a/gateway/src/main.rs b/gateway/src/main.rs index 9ea8eb679d..5290037266 100644 --- a/gateway/src/main.rs +++ b/gateway/src/main.rs @@ -5,7 +5,7 @@ use clap::{crate_name, crate_version, Parser}; use colored::Colorize; use lazy_static::lazy_static; use log::error; -use nym_bin_common::build_information::BinaryBuildInformation; +use nym_bin_common::bin_info; use nym_bin_common::logging::{maybe_print_banner, setup_logging}; use nym_bin_common::output_format::OutputFormat; use nym_network_defaults::setup_env; @@ -18,8 +18,7 @@ mod node; pub(crate) mod support; lazy_static! { - pub static ref PRETTY_BUILD_INFORMATION: String = - BinaryBuildInformation::new(env!("CARGO_PKG_VERSION")).pretty_print(); + pub static ref PRETTY_BUILD_INFORMATION: String = bin_info!().pretty_print(); } // Helper for passing LONG_VERSION to clap @@ -34,18 +33,24 @@ struct Cli { #[clap(short, long)] pub(crate) config_env_file: Option, + /// Flag used for disabling the printed banner in tty. + #[clap(long)] + pub(crate) no_banner: bool, + #[clap(subcommand)] command: commands::Commands, } #[tokio::main] async fn main() -> Result<(), Box> { - setup_logging(); - maybe_print_banner(crate_name!(), crate_version!()); - let args = Cli::parse(); setup_env(args.config_env_file.as_ref()); + if !args.no_banner { + maybe_print_banner(crate_name!(), crate_version!()); + } + setup_logging(); + commands::execute(args).await.map_err(|err| { if atty::is(atty::Stream::Stdout) { let error_message = format!("{err}").red(); diff --git a/mixnode/src/commands/build_info.rs b/mixnode/src/commands/build_info.rs new file mode 100644 index 0000000000..e3385bb933 --- /dev/null +++ b/mixnode/src/commands/build_info.rs @@ -0,0 +1,16 @@ +// Copyright 2023 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use clap::Args; +use nym_bin_common::bin_info_owned; +use nym_bin_common::output_format::OutputFormat; + +#[derive(Args)] +pub(crate) struct BuildInfo { + #[clap(short, long, default_value_t = OutputFormat::default())] + output: OutputFormat, +} + +pub(crate) fn execute(args: BuildInfo) { + println!("{}", args.output.format(&bin_info_owned!())) +} diff --git a/mixnode/src/commands/mod.rs b/mixnode/src/commands/mod.rs index b8c8f84894..e79df8e2ab 100644 --- a/mixnode/src/commands/mod.rs +++ b/mixnode/src/commands/mod.rs @@ -15,6 +15,7 @@ use nym_crypto::bech32_address_validation; use std::net::IpAddr; use std::process; +mod build_info; mod describe; mod init; mod node_details; @@ -38,6 +39,9 @@ pub(crate) enum Commands { /// Show details of this mixnode NodeDetails(node_details::NodeDetails), + /// Show build information of this binary + BuildInfo(build_info::BuildInfo), + /// Generate shell completions Completions(ArgShell), @@ -64,6 +68,7 @@ pub(crate) async fn execute(args: Cli) -> anyhow::Result<()> { Commands::Run(m) => run::execute(&m).await?, Commands::Sign(m) => sign::execute(&m)?, Commands::NodeDetails(m) => node_details::execute(&m)?, + Commands::BuildInfo(m) => build_info::execute(m), Commands::Completions(s) => s.generate(&mut crate::Cli::command(), bin_name), Commands::GenerateFigSpec => fig_generate(&mut crate::Cli::command(), bin_name), } diff --git a/mixnode/src/main.rs b/mixnode/src/main.rs index 5681e02cc7..bdda653edf 100644 --- a/mixnode/src/main.rs +++ b/mixnode/src/main.rs @@ -7,7 +7,8 @@ extern crate rocket; use ::nym_config::defaults::setup_env; use clap::{crate_name, crate_version, Parser}; use lazy_static::lazy_static; -use nym_bin_common::build_information::BinaryBuildInformation; +use nym_bin_common::bin_info; + #[allow(unused_imports)] use nym_bin_common::logging::{maybe_print_banner, setup_logging}; #[cfg(feature = "cpucycles")] @@ -16,13 +17,13 @@ use nym_bin_common::setup_tracing; use nym_mixnode_common::measure; #[cfg(feature = "cpucycles")] use tracing::instrument; + mod commands; mod config; mod node; lazy_static! { - pub static ref PRETTY_BUILD_INFORMATION: String = - BinaryBuildInformation::new(env!("CARGO_PKG_VERSION")).pretty_print(); + pub static ref PRETTY_BUILD_INFORMATION: String = bin_info!().pretty_print(); } // Helper for passing LONG_VERSION to clap @@ -37,6 +38,10 @@ struct Cli { #[clap(short, long)] pub(crate) config_env_file: Option, + /// Flag used for disabling the printed banner in tty. + #[clap(long)] + pub(crate) no_banner: bool, + #[clap(subcommand)] command: commands::Commands, } @@ -49,6 +54,13 @@ fn test_function() { #[tokio::main] async fn main() -> anyhow::Result<()> { + let args = Cli::parse(); + setup_env(args.config_env_file.as_ref()); + + if !args.no_banner { + maybe_print_banner(crate_name!(), crate_version!()); + } + cfg_if::cfg_if! { if #[cfg(feature = "cpucycles")] { setup_tracing!("mixnode"); @@ -59,10 +71,6 @@ async fn main() -> anyhow::Result<()> { } } - maybe_print_banner(crate_name!(), crate_version!()); - - let args = Cli::parse(); - setup_env(args.config_env_file.as_ref()); commands::execute(args).await?; cfg_if::cfg_if! { diff --git a/nym-api/src/support/cli/mod.rs b/nym-api/src/support/cli/mod.rs index 1bcc518ad4..e638d29c09 100644 --- a/nym-api/src/support/cli/mod.rs +++ b/nym-api/src/support/cli/mod.rs @@ -8,14 +8,13 @@ use ::nym_config::defaults::var_names::{MIXNET_CONTRACT_ADDRESS, VESTING_CONTRAC use anyhow::Result; use clap::Parser; use lazy_static::lazy_static; -use nym_bin_common::build_information::BinaryBuildInformation; +use nym_bin_common::bin_info; use nym_config::defaults::var_names::NYXD; use nym_config::OptionalSet; use nym_validator_client::nyxd; lazy_static! { - pub static ref PRETTY_BUILD_INFORMATION: String = - BinaryBuildInformation::new(env!("CARGO_PKG_VERSION")).pretty_print(); + pub static ref PRETTY_BUILD_INFORMATION: String = bin_info!().pretty_print(); } // Helper for passing LONG_VERSION to clap diff --git a/service-providers/network-requester/src/cli/build_info.rs b/service-providers/network-requester/src/cli/build_info.rs new file mode 100644 index 0000000000..e3385bb933 --- /dev/null +++ b/service-providers/network-requester/src/cli/build_info.rs @@ -0,0 +1,16 @@ +// Copyright 2023 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use clap::Args; +use nym_bin_common::bin_info_owned; +use nym_bin_common::output_format::OutputFormat; + +#[derive(Args)] +pub(crate) struct BuildInfo { + #[clap(short, long, default_value_t = OutputFormat::default())] + output: OutputFormat, +} + +pub(crate) fn execute(args: BuildInfo) { + println!("{}", args.output.format(&bin_info_owned!())) +} diff --git a/service-providers/network-requester/src/cli/mod.rs b/service-providers/network-requester/src/cli/mod.rs index e6a34432df..80dd4751a5 100644 --- a/service-providers/network-requester/src/cli/mod.rs +++ b/service-providers/network-requester/src/cli/mod.rs @@ -10,7 +10,7 @@ use crate::{ }; use clap::{CommandFactory, Parser, Subcommand}; use log::{error, info}; -use nym_bin_common::build_information::BinaryBuildInformation; +use nym_bin_common::bin_info; use nym_bin_common::completions::{fig_generate, ArgShell}; use nym_bin_common::version_checker; use nym_client_core::client::base_client::storage::gateway_details::{ @@ -21,13 +21,13 @@ use nym_client_core::config::GatewayEndpointConfig; use nym_client_core::error::ClientCoreError; use nym_sphinx::params::PacketSize; +mod build_info; mod init; mod run; mod sign; lazy_static::lazy_static! { - pub static ref PRETTY_BUILD_INFORMATION: String = - BinaryBuildInformation::new(env!("CARGO_PKG_VERSION")).pretty_print(); + pub static ref PRETTY_BUILD_INFORMATION: String = bin_info!().pretty_print(); } // Helper for passing LONG_VERSION to clap @@ -42,6 +42,10 @@ pub(crate) struct Cli { #[clap(short, long)] pub(crate) config_env_file: Option, + /// Flag used for disabling the printed banner in tty. + #[clap(long)] + pub(crate) no_banner: bool, + #[clap(subcommand)] command: Commands, } @@ -59,6 +63,9 @@ pub(crate) enum Commands { /// Sign to prove ownership of this network requester Sign(sign::Sign), + /// Show build information of this binary + BuildInfo(build_info::BuildInfo), + /// Generate shell completions Completions(ArgShell), @@ -119,10 +126,11 @@ pub(crate) fn override_config(config: Config, args: OverrideConfig) -> Config { pub(crate) async fn execute(args: Cli) -> Result<(), NetworkRequesterError> { let bin_name = "nym-network-requester"; - match &args.command { - Commands::Init(m) => init::execute(m).await?, - Commands::Run(m) => run::execute(m).await?, - Commands::Sign(m) => sign::execute(m).await?, + match args.command { + Commands::Init(m) => init::execute(&m).await?, + Commands::Run(m) => run::execute(&m).await?, + Commands::Sign(m) => sign::execute(&m).await?, + Commands::BuildInfo(m) => build_info::execute(m), Commands::Completions(s) => s.generate(&mut Cli::command(), bin_name), Commands::GenerateFigSpec => fig_generate(&mut Cli::command(), bin_name), } diff --git a/service-providers/network-requester/src/core.rs b/service-providers/network-requester/src/core.rs index b60bc30f41..92ab7af231 100644 --- a/service-providers/network-requester/src/core.rs +++ b/service-providers/network-requester/src/core.rs @@ -13,7 +13,7 @@ use crate::{reply, socks5}; use async_trait::async_trait; use futures::channel::mpsc; use log::warn; -use nym_bin_common::build_information::BinaryBuildInformation; +use nym_bin_common::bin_info_owned; use nym_client_core::config::disk_persistence::CommonClientPaths; use nym_network_defaults::NymNetworkDetails; use nym_service_providers_common::interface::{ @@ -101,7 +101,7 @@ impl ServiceProvider for NRServiceProvider { ) -> Result { Ok(BinaryInformation { binary_name: env!("CARGO_PKG_NAME").to_string(), - build_information: BinaryBuildInformation::new(env!("CARGO_PKG_VERSION")).to_owned(), + build_information: bin_info_owned!(), }) } diff --git a/service-providers/network-requester/src/main.rs b/service-providers/network-requester/src/main.rs index 696cb41c21..89ac717d37 100644 --- a/service-providers/network-requester/src/main.rs +++ b/service-providers/network-requester/src/main.rs @@ -18,11 +18,13 @@ mod statistics; #[tokio::main] async fn main() -> Result<(), NetworkRequesterError> { - setup_logging(); - maybe_print_banner(crate_name!(), crate_version!()); - let args = cli::Cli::parse(); setup_env(args.config_env_file.as_ref()); + if !args.no_banner { + maybe_print_banner(crate_name!(), crate_version!()); + } + setup_logging(); + cli::execute(args).await } From 0bfc1be1d594ecfb7970e0ad54571e1b1111a6a3 Mon Sep 17 00:00:00 2001 From: pierre Date: Thu, 27 Jul 2023 11:42:02 +0200 Subject: [PATCH 015/361] ci: fix release strapi actions --- .github/workflows/nym-connect-publish-macos.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/nym-connect-publish-macos.yml b/.github/workflows/nym-connect-publish-macos.yml index a4176eb285..827676f4d3 100644 --- a/.github/workflows/nym-connect-publish-macos.yml +++ b/.github/workflows/nym-connect-publish-macos.yml @@ -118,7 +118,7 @@ jobs: 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 "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: From 6e5f6bf0dfc0bce07e7f4e29ad1dea56d6ee677f Mon Sep 17 00:00:00 2001 From: pierre Date: Thu, 27 Jul 2023 11:42:02 +0200 Subject: [PATCH 016/361] ci: fix release strapi actions --- .github/workflows/nym-connect-publish-macos.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/nym-connect-publish-macos.yml b/.github/workflows/nym-connect-publish-macos.yml index a4176eb285..827676f4d3 100644 --- a/.github/workflows/nym-connect-publish-macos.yml +++ b/.github/workflows/nym-connect-publish-macos.yml @@ -118,7 +118,7 @@ jobs: 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 "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: From 19736b1204d78d0f877fc8d1729a1b9ea8432910 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bogdan-=C8=98tefan=20Neac=C5=9Fu?= Date: Thu, 27 Jul 2023 13:25:34 +0300 Subject: [PATCH 017/361] Fix develop after bad automerge (#3712) --- common/nymsphinx/params/src/packet_sizes.rs | 39 +-- nym-connect/desktop/Cargo.lock | 15 +- nym-wallet/Cargo.lock | 291 ++++---------------- 3 files changed, 56 insertions(+), 289 deletions(-) diff --git a/common/nymsphinx/params/src/packet_sizes.rs b/common/nymsphinx/params/src/packet_sizes.rs index 58d9681efc..ecae71993d 100644 --- a/common/nymsphinx/params/src/packet_sizes.rs +++ b/common/nymsphinx/params/src/packet_sizes.rs @@ -2,10 +2,10 @@ // SPDX-License-Identifier: Apache-2.0 use crate::PacketType; -#[cfg(feature = "sphinx")] -use nym_sphinx_types::{header::HEADER_SIZE, PAYLOAD_OVERHEAD_SIZE}; -#[cfg(feature = "outfox")] -use nym_sphinx_types::{MIN_PACKET_SIZE, MIX_PARAMS_LEN, OUTFOX_PACKET_OVERHEAD}; +use nym_sphinx_types::{ + header::HEADER_SIZE, MIN_PACKET_SIZE, MIX_PARAMS_LEN, OUTFOX_PACKET_OVERHEAD, + PAYLOAD_OVERHEAD_SIZE, +}; use serde::{Deserialize, Serialize}; use std::cmp::Ordering; use std::convert::TryFrom; @@ -14,7 +14,6 @@ use std::str::FromStr; use thiserror::Error; // each sphinx packet contains mandatory header and payload padding + markers -#[cfg(feature = "sphinx")] const SPHINX_PACKET_OVERHEAD: usize = HEADER_SIZE + PAYLOAD_OVERHEAD_SIZE; // it's up to the smart people to figure those values out : ) @@ -23,23 +22,16 @@ const SPHINX_PACKET_OVERHEAD: usize = HEADER_SIZE + PAYLOAD_OVERHEAD_SIZE; // TODO: I'm not entirely sure if we can easily extract `::NonceSize` // into a const usize before relevant stuff is stabilised in rust... -#[cfg(feature = "sphinx")] + const ACK_IV_SIZE: usize = 16; -#[cfg(feature = "sphinx")] const ACK_PACKET_SIZE: usize = ACK_IV_SIZE + crate::FRAG_ID_LEN + SPHINX_PACKET_OVERHEAD; -#[cfg(feature = "sphinx")] const REGULAR_PACKET_SIZE: usize = 2 * 1024 + SPHINX_PACKET_OVERHEAD; -#[cfg(feature = "sphinx")] const EXTENDED_PACKET_SIZE_8: usize = 8 * 1024 + SPHINX_PACKET_OVERHEAD; -#[cfg(feature = "sphinx")] const EXTENDED_PACKET_SIZE_16: usize = 16 * 1024 + SPHINX_PACKET_OVERHEAD; -#[cfg(feature = "sphinx")] const EXTENDED_PACKET_SIZE_32: usize = 32 * 1024 + SPHINX_PACKET_OVERHEAD; -#[cfg(feature = "outfox")] const OUTFOX_ACK_PACKET_SIZE: usize = MIN_PACKET_SIZE + OUTFOX_PACKET_OVERHEAD; -#[cfg(feature = "outfox")] const OUTFOX_REGULAR_PACKET_SIZE: usize = 2 * 1024 + OUTFOX_PACKET_OVERHEAD; #[derive(Debug, Error)] @@ -162,55 +154,39 @@ impl TryFrom for PacketSize { impl PacketSize { pub const fn size(self) -> usize { - #[allow(unreachable_patterns)] match self { - #[cfg(feature = "sphinx")] PacketSize::RegularPacket => REGULAR_PACKET_SIZE, - #[cfg(feature = "sphinx")] PacketSize::AckPacket => ACK_PACKET_SIZE, - #[cfg(feature = "sphinx")] PacketSize::ExtendedPacket8 => EXTENDED_PACKET_SIZE_8, - #[cfg(feature = "sphinx")] PacketSize::ExtendedPacket16 => EXTENDED_PACKET_SIZE_16, - #[cfg(feature = "sphinx")] PacketSize::ExtendedPacket32 => EXTENDED_PACKET_SIZE_32, - #[cfg(feature = "outfox")] PacketSize::OutfoxRegularPacket => OUTFOX_REGULAR_PACKET_SIZE, - #[cfg(feature = "outfox")] PacketSize::OutfoxAckPacket => OUTFOX_ACK_PACKET_SIZE, - _ => 0, } } pub const fn header_size(&self) -> usize { - #[allow(unreachable_patterns)] match self { - #[cfg(feature = "sphinx")] PacketSize::RegularPacket | PacketSize::AckPacket | PacketSize::ExtendedPacket8 | PacketSize::ExtendedPacket16 | PacketSize::ExtendedPacket32 => HEADER_SIZE, - #[cfg(feature = "outfox")] PacketSize::OutfoxRegularPacket | PacketSize::OutfoxAckPacket => MIX_PARAMS_LEN, - _ => 0, } } pub const fn payload_overhead(&self) -> usize { #[allow(unreachable_patterns)] match self { - #[cfg(feature = "sphinx")] PacketSize::RegularPacket | PacketSize::AckPacket | PacketSize::ExtendedPacket8 | PacketSize::ExtendedPacket16 | PacketSize::ExtendedPacket32 => PAYLOAD_OVERHEAD_SIZE, - #[cfg(feature = "outfox")] PacketSize::OutfoxRegularPacket | PacketSize::OutfoxAckPacket => { OUTFOX_PACKET_OVERHEAD - MIX_PARAMS_LEN // Mix params are calculated into the total overhead so we take them out here } - _ => 0, } } @@ -268,16 +244,11 @@ impl PacketSize { plaintext_size: usize, packet_type: PacketType, ) -> Result { - #[allow(unreachable_patterns)] let overhead = match packet_type { - #[cfg(feature = "sphinx")] PacketType::Mix => SPHINX_PACKET_OVERHEAD, #[allow(deprecated)] - #[cfg(feature = "sphinx")] PacketType::Vpn => SPHINX_PACKET_OVERHEAD, - #[cfg(feature = "outfox")] PacketType::Outfox => OUTFOX_PACKET_OVERHEAD, - _ => 0, }; let packet_size = plaintext_size + overhead; Self::get_type(packet_size) diff --git a/nym-connect/desktop/Cargo.lock b/nym-connect/desktop/Cargo.lock index c7bb0c20a6..1b9ca04b31 100644 --- a/nym-connect/desktop/Cargo.lock +++ b/nym-connect/desktop/Cargo.lock @@ -2596,17 +2596,6 @@ dependencies = [ "winapi", ] -[[package]] -name = "hostname" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c731c3e10504cc8ed35cfe2f1db4c9274c3d35fa486e3b31df46f068ef3e867" -dependencies = [ - "libc", - "match_cfg", - "winapi", -] - [[package]] name = "html5ever" version = "0.25.2" @@ -3669,7 +3658,7 @@ dependencies = [ [[package]] name = "nym-connect" -version = "1.1.13" +version = "1.1.15" dependencies = [ "anyhow", "bip39", @@ -4179,6 +4168,8 @@ dependencies = [ name = "nym-sphinx-types" version = "0.2.0" dependencies = [ + "nym-outfox", + "sphinx-packet", "thiserror", ] diff --git a/nym-wallet/Cargo.lock b/nym-wallet/Cargo.lock index 5768438108..050930055a 100644 --- a/nym-wallet/Cargo.lock +++ b/nym-wallet/Cargo.lock @@ -21,20 +21,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0" dependencies = [ "crypto-common", - "generic-array 0.14.7", -] - -[[package]] -name = "aes" -version = "0.7.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e8b47f52ea9bae42228d07ec09eb676433d7c4ed1ebdf0f1d1c29ed446f1ab8" -dependencies = [ - "cfg-if", - "cipher 0.3.0", - "cpufeatures", - "ctr 0.8.0", - "opaque-debug 0.3.0", + "generic-array", ] [[package]] @@ -44,7 +31,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "433cfd6710c9986c576a25ca913c39d66a6474107b406f34f91d4a8923395241" dependencies = [ "cfg-if", - "cipher 0.4.4", + "cipher", "cpufeatures", ] @@ -55,11 +42,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "82e1366e0c69c9f927b1fa5ce2c7bf9eafc8f9268c0b9800729e8b267612447c" dependencies = [ "aead", - "aes 0.8.2", - "cipher 0.4.4", - "ctr 0.9.2", + "aes", + "cipher", + "ctr", "ghash", - "subtle 2.4.1", + "subtle", ] [[package]] @@ -110,16 +97,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "95c2fcf79ad1932ac6269a738109997a83c227c09b75842ae564dc8ede6a861c" dependencies = [ "base64ct", - "blake2 0.10.6", + "blake2", "password-hash", ] -[[package]] -name = "arrayref" -version = "0.3.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4c527152e37cf757a3f78aae5a06fbeefdb07ccc535c980a3208ee3060dd544" - [[package]] name = "async-trait" version = "0.1.64" @@ -225,14 +206,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7e141fb0f8be1c7b45887af94c88b182472b57c96b56773250ae00cd6a14a164" dependencies = [ "bs58 0.5.0", - "hmac 0.12.1", + "hmac", "k256 0.13.1", "once_cell", "pbkdf2", "rand_core 0.6.4", "ripemd", "sha2 0.10.6", - "subtle 2.4.1", + "subtle", "zeroize", ] @@ -274,18 +255,6 @@ dependencies = [ "wyz", ] -[[package]] -name = "blake2" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94cb07b0da6a73955f8fb85d24c466778e70cda767a568229b104f0264089330" -dependencies = [ - "byte-tools", - "crypto-mac 0.7.0", - "digest 0.8.1", - "opaque-debug 0.2.3", -] - [[package]] name = "blake2" version = "0.10.6" @@ -307,7 +276,7 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" dependencies = [ - "generic-array 0.14.7", + "generic-array", ] [[package]] @@ -316,7 +285,7 @@ version = "0.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69cce20737498f97b993470a6e536b8523f0af7892a4f928cceb1ac5e52ebe7e" dependencies = [ - "generic-array 0.14.7", + "generic-array", ] [[package]] @@ -329,7 +298,7 @@ dependencies = [ "group 0.11.0", "pairing", "rand_core 0.6.4", - "subtle 2.4.1", + "subtle", "zeroize", ] @@ -385,12 +354,6 @@ version = "3.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0d261e256854913907f67ed06efbc3338dfe6179796deefc1ff763fc1aee5535" -[[package]] -name = "byte-tools" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3b5ca7a04898ad4bcd41c90c5285445ff5b791899bb1b0abdd2a2aa791211d7" - [[package]] name = "bytemuck" version = "1.13.0" @@ -495,25 +458,6 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" -[[package]] -name = "chacha" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ddf3c081b5fba1e5615640aae998e0fbd10c24cbd897ee39ed754a77601a4862" -dependencies = [ - "byteorder", - "keystream", -] - -[[package]] -name = "cipher" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ee52072ec15386f770805afd189a01c8841be8696bed250fa2f13c4c0d6dfb7" -dependencies = [ - "generic-array 0.14.7", -] - [[package]] name = "cipher" version = "0.4.4" @@ -849,9 +793,9 @@ version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ef2b4b23cddf68b89b8f8069890e8c270d54e2d5fe1b143820234805e4cb17ef" dependencies = [ - "generic-array 0.14.7", + "generic-array", "rand_core 0.6.4", - "subtle 2.4.1", + "subtle", "zeroize", ] @@ -861,9 +805,9 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf4c2f4e1afd912bc40bfd6fed5d9dc1f288e0ba01bfcc835cc5bc3eb13efe15" dependencies = [ - "generic-array 0.14.7", + "generic-array", "rand_core 0.6.4", - "subtle 2.4.1", + "subtle", "zeroize", ] @@ -873,31 +817,11 @@ version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" dependencies = [ - "generic-array 0.14.7", + "generic-array", "rand_core 0.6.4", "typenum", ] -[[package]] -name = "crypto-mac" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4434400df11d95d556bac068ddfedd482915eb18fe8bea89bc80b6e4b1c179e5" -dependencies = [ - "generic-array 0.12.4", - "subtle 1.0.0", -] - -[[package]] -name = "crypto-mac" -version = "0.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1d1a86f49236c215f271d40892d5fc950490551400b02ef360692c29815c714" -dependencies = [ - "generic-array 0.14.7", - "subtle 2.4.1", -] - [[package]] name = "cssparser" version = "0.27.2" @@ -944,22 +868,13 @@ dependencies = [ "syn 1.0.107", ] -[[package]] -name = "ctr" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "049bb91fb4aaf0e3c7efa6cd5ef877dbbbd15b39dad06d9948de4ec8a75761ea" -dependencies = [ - "cipher 0.3.0", -] - [[package]] name = "ctr" version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0369ee1ad671834580515889b80f2ea915f23b8be8d0daa4bbaf2ac5c7590835" dependencies = [ - "cipher 0.4.4", + "cipher", ] [[package]] @@ -977,7 +892,7 @@ dependencies = [ "byteorder", "digest 0.9.0", "rand_core 0.5.1", - "subtle 2.4.1", + "subtle", "zeroize", ] @@ -1168,22 +1083,13 @@ dependencies = [ "syn 1.0.107", ] -[[package]] -name = "digest" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3d0c8c8752312f9713efd397ff63acb9f85585afbf179282e720e7704954dd5" -dependencies = [ - "generic-array 0.12.4", -] - [[package]] name = "digest" version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" dependencies = [ - "generic-array 0.14.7", + "generic-array", ] [[package]] @@ -1195,7 +1101,7 @@ dependencies = [ "block-buffer 0.10.3", "const-oid", "crypto-common", - "subtle 2.4.1", + "subtle", ] [[package]] @@ -1403,12 +1309,12 @@ dependencies = [ "der 0.6.1", "digest 0.10.7", "ff 0.12.1", - "generic-array 0.14.7", + "generic-array", "group 0.12.1", "pkcs8 0.9.0", "rand_core 0.6.4", "sec1 0.3.0", - "subtle 2.4.1", + "subtle", "zeroize", ] @@ -1422,12 +1328,12 @@ dependencies = [ "crypto-bigint 0.5.2", "digest 0.10.7", "ff 0.13.0", - "generic-array 0.14.7", + "generic-array", "group 0.13.0", "pkcs8 0.10.2", "rand_core 0.6.4", "sec1 0.7.3", - "subtle 2.4.1", + "subtle", "zeroize", ] @@ -1536,7 +1442,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "131655483be284720a17d74ff97592b8e76576dc25563148601df2d7c9080924" dependencies = [ "rand_core 0.6.4", - "subtle 2.4.1", + "subtle", ] [[package]] @@ -1546,7 +1452,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d013fc25338cc558c5c2cfbad646908fb23591e2404481826742b651c9af7160" dependencies = [ "rand_core 0.6.4", - "subtle 2.4.1", + "subtle", ] [[package]] @@ -1556,7 +1462,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ded41244b729663b1e574f1b4fb731469f69f79c17667b5d776b16cda0479449" dependencies = [ "rand_core 0.6.4", - "subtle 2.4.1", + "subtle", ] [[package]] @@ -1836,15 +1742,6 @@ dependencies = [ "windows 0.39.0", ] -[[package]] -name = "generic-array" -version = "0.12.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffdf9f34f1447443d37393cc6c2b8313aebddcd96906caf34e54c68d8e57d7bd" -dependencies = [ - "typenum", -] - [[package]] name = "generic-array" version = "0.14.7" @@ -1900,7 +1797,7 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d930750de5717d2dd0b8c0d42c076c0e884c81a73e6cab859bbd2339c71e3e40" dependencies = [ - "opaque-debug 0.3.0", + "opaque-debug", "polyval", ] @@ -2031,7 +1928,7 @@ dependencies = [ "byteorder", "ff 0.11.1", "rand_core 0.6.4", - "subtle 2.4.1", + "subtle", ] [[package]] @@ -2042,7 +1939,7 @@ checksum = "5dfbfb3a6cfbd390d5c9564ab283a0349b9b9fcd46a706c1eb10e0db70bfbac7" dependencies = [ "ff 0.12.1", "rand_core 0.6.4", - "subtle 2.4.1", + "subtle", ] [[package]] @@ -2053,7 +1950,7 @@ checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" dependencies = [ "ff 0.13.0", "rand_core 0.6.4", - "subtle 2.4.1", + "subtle", ] [[package]] @@ -2229,26 +2126,6 @@ version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7ebdb29d2ea9ed0083cd8cece49bbd968021bd99b0849edb4a9a7ee0fdf6a4e0" -[[package]] -name = "hkdf" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01706d578d5c281058480e673ae4086a9f4710d8df1ad80a5b03e39ece5f886b" -dependencies = [ - "digest 0.9.0", - "hmac 0.11.0", -] - -[[package]] -name = "hmac" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a2a2320eb7ec0ebe8da8f744d7812d9fc4cb4d09344ac01898dbcb6a20ae69b" -dependencies = [ - "crypto-mac 0.11.1", - "digest 0.9.0", -] - [[package]] name = "hmac" version = "0.12.1" @@ -2500,7 +2377,7 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5" dependencies = [ - "generic-array 0.14.7", + "generic-array", ] [[package]] @@ -2659,12 +2536,6 @@ dependencies = [ "signature 2.1.0", ] -[[package]] -name = "keystream" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c33070833c9ee02266356de0c43f723152bd38bd96ddf52c82b3af10c9138b28" - [[package]] name = "kuchiki" version = "0.8.1" @@ -2701,12 +2572,6 @@ dependencies = [ "pkg-config", ] -[[package]] -name = "libm" -version = "0.2.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "348108ab3fba42ec82ff6e9564fc4ca0247bdccdc68dd8af9764bbc79c3c8ffb" - [[package]] name = "libz-sys" version = "1.1.8" @@ -2734,18 +2599,6 @@ version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f051f77a7c8e6957c0696eac88f26b0117e54f52d3fc682ab19397a8812846a4" -[[package]] -name = "lioness" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ae926706ba42c425c9457121178330d75e273df2e82e28b758faf3de3a9acb9" -dependencies = [ - "arrayref", - "blake2 0.8.1", - "chacha", - "keystream", -] - [[package]] name = "lock_api" version = "0.4.9" @@ -2989,7 +2842,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" dependencies = [ "autocfg", - "libm", ] [[package]] @@ -3268,7 +3120,6 @@ dependencies = [ name = "nym-sphinx-types" version = "0.2.0" dependencies = [ - "sphinx-packet", "thiserror", ] @@ -3278,7 +3129,7 @@ version = "0.1.0" dependencies = [ "aes-gcm", "argon2", - "generic-array 0.14.7", + "generic-array", "getrandom 0.2.10", "rand 0.8.5", "serde", @@ -3517,12 +3368,6 @@ version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6f61fba1741ea2b3d6a1e3178721804bb716a68a6aeba1149b5d52e3d464ea66" -[[package]] -name = "opaque-debug" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2839e79665f131bdb5782e51f2c6c9599c133c6098982a54c794358bf432529c" - [[package]] name = "opaque-debug" version = "0.3.0" @@ -3677,7 +3522,7 @@ checksum = "346f04948ba92c43e8469c1ee6736c7563d71012b17d40745260fe106aac2166" dependencies = [ "base64ct", "rand_core 0.6.4", - "subtle 2.4.1", + "subtle", ] [[package]] @@ -3699,7 +3544,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8ed6a7761f76e3b9f92dfb0a60a6a6477c61024b775147ff0973a02653abaf2" dependencies = [ "digest 0.10.7", - "hmac 0.12.1", + "hmac", ] [[package]] @@ -3980,7 +3825,7 @@ checksum = "7ef234e08c11dfcb2e56f79fd70f6f2eb7f025c0ce2333e82f4f0518ecad30c6" dependencies = [ "cfg-if", "cpufeatures", - "opaque-debug 0.3.0", + "opaque-debug", "universal-hash", ] @@ -4186,16 +4031,6 @@ dependencies = [ "getrandom 0.2.10", ] -[[package]] -name = "rand_distr" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9e9532ada3929fb8b2e9dbe28d1e06c9b2cc65813f074fcb6bd5fbefeff9d56" -dependencies = [ - "num-traits", - "rand 0.7.3", -] - [[package]] name = "rand_hc" version = "0.2.0" @@ -4322,7 +4157,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7743f17af12fa0b03b803ba12cd6a8d9483a587e89c69445e3909655c0b9fabb" dependencies = [ "crypto-bigint 0.4.9", - "hmac 0.12.1", + "hmac", "zeroize", ] @@ -4332,8 +4167,8 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2" dependencies = [ - "hmac 0.12.1", - "subtle 2.4.1", + "hmac", + "subtle", ] [[package]] @@ -4532,9 +4367,9 @@ checksum = "3be24c1842290c45df0a7bf069e0c268a747ad05a192f2fd7dcfdbc1cba40928" dependencies = [ "base16ct 0.1.1", "der 0.6.1", - "generic-array 0.14.7", + "generic-array", "pkcs8 0.9.0", - "subtle 2.4.1", + "subtle", "zeroize", ] @@ -4546,9 +4381,9 @@ checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc" dependencies = [ "base16ct 0.2.0", "der 0.7.7", - "generic-array 0.14.7", + "generic-array", "pkcs8 0.10.2", - "subtle 2.4.1", + "subtle", "zeroize", ] @@ -4798,7 +4633,7 @@ dependencies = [ "cfg-if", "cpufeatures", "digest 0.9.0", - "opaque-debug 0.3.0", + "opaque-debug", ] [[package]] @@ -4909,30 +4744,6 @@ dependencies = [ "system-deps 5.0.0", ] -[[package]] -name = "sphinx-packet" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc43eda802856ee82a7555c7b75ceb9e07451741c7a2f5f23d036020e01189d4" -dependencies = [ - "aes 0.7.5", - "arrayref", - "blake2 0.8.1", - "bs58 0.4.0", - "byteorder", - "chacha", - "curve25519-dalek", - "digest 0.9.0", - "hkdf", - "hmac 0.11.0", - "lioness", - "log", - "rand 0.7.3", - "rand_distr", - "sha2 0.9.9", - "subtle 2.4.1", -] - [[package]] name = "spin" version = "0.5.2" @@ -5034,12 +4845,6 @@ dependencies = [ "syn 1.0.107", ] -[[package]] -name = "subtle" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d67a5a62ba6e01cb2192ff309324cb4875d0c451d55fe2319433abe7a05a8ee" - [[package]] name = "subtle" version = "2.4.1" @@ -5399,7 +5204,7 @@ dependencies = [ "serde_repr", "sha2 0.10.6", "signature 2.1.0", - "subtle 2.4.1", + "subtle", "subtle-encoding", "tendermint-proto", "time", @@ -5459,7 +5264,7 @@ dependencies = [ "serde", "serde_bytes", "serde_json", - "subtle 2.4.1", + "subtle", "subtle-encoding", "tendermint", "tendermint-config", @@ -5869,7 +5674,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7d3160b73c9a19f7e2939a2fdad446c57c1bbbbf4d919d3213ff1267a580d8b5" dependencies = [ "crypto-common", - "subtle 2.4.1", + "subtle", ] [[package]] From 7ccadffdba81ca07dc531a5ea819673c58b0555c Mon Sep 17 00:00:00 2001 From: serinko <97586125+serinko@users.noreply.github.com> Date: Thu, 27 Jul 2023 12:41:22 +0200 Subject: [PATCH 018/361] operators-guide:troubleshooting.md done - content ready to go out --- documentation/operators/src/faq.md | 3 +- .../operators/src/nodes/maintenance.md | 41 ++- .../operators/src/nodes/mix-node-setup.md | 10 +- .../operators/src/nodes/troubleshooting.md | 325 +++++++++++++++++- 4 files changed, 365 insertions(+), 14 deletions(-) diff --git a/documentation/operators/src/faq.md b/documentation/operators/src/faq.md index e09a78c21c..88836f17f2 100644 --- a/documentation/operators/src/faq.md +++ b/documentation/operators/src/faq.md @@ -3,7 +3,8 @@ TODO - [ ] Explain active set and needed metrics - [ ] List friendly VPS providers - [ ] Explain difficulty running a node on a self-hosted machine -- [ ] Explain uNYM vs NYM. Why, ratio, etc +- [ ] Explain uNYM vs NYM/uNYX vs NYX. Why, ratio, etc +- [ ] Explain why validators set is whitelisted Some community questions: Hi, do you know if it's on the roadmap for network nodes (requesters, socks5 client, etc.) to reconnect to another gateway if the current gateway's connection drops or has issues? I have to reset my requester and socks5 clients to connect them to a new gateway almost every day...😕 having to delete the entire folders associated with them diff --git a/documentation/operators/src/nodes/maintenance.md b/documentation/operators/src/nodes/maintenance.md index d4884bad84..d809027150 100644 --- a/documentation/operators/src/nodes/maintenance.md +++ b/documentation/operators/src/nodes/maintenance.md @@ -3,6 +3,11 @@ ## Useful commands @@ -107,6 +112,32 @@ Running the command `df -H` will return the size of the various partitions of yo If the `/dev/sda` partition is almost full, try pruning some of the `.gz` syslog archives and restart your validator process. +## Moving a node + +In case of a need to move a node from one machine to another and avoiding to lose the delegation, here are few steps how to do it. + +The following examples transfers a mixnode (in case of other nodes, change the `mixnodes` in the command for the `` of your desire. + +* Pause your node process. + +Assuming both machines are remote VPS. + +* Make sure your `~/.ssh/.pub` is in both of the machines `~/.ssh/authorized_keys` file +* Create a `mixnodes` folder in the target VPS. Ssh in from your terminal and run: +``` +# in case none of the nym configs was created previously +mkdir ~/.nym + +#in case no nym mixnode was initialized previously +mkdir ~/.nym/mixnodes +``` +* Move the node data (keys) and config file to the new machine by opening a local terminal (as that one's ssh key is authorized in both of the machines) and running: +``` +scp -r @:~/.nym/mixnodes/ @:~/.nym/mixnodes/ +``` +* Re-run init (remember that init doesn't overwrite existing keys) to generate a config with the new listening address etc. +* Change the node smart contract info via the wallet interface. Otherwise the keys will point to the old IP address in the smart contract, and the node will not be able to be connected, and it will fail up-time checks. +* Re-run the node from the new location. ## VPS Setup and Automation ### Configure your firewall @@ -149,10 +180,18 @@ sudo ufw status For more information about your node's port configuration, check the [port reference table](https://nymtech.net/docs/nodes/gateway-setup.html#ports) below. -### Automating your node with tmux and systemd +### Automating your node with nohup, tmux and systemd Although it’s not totally necessary, it's useful to have the mix node automatically start at system boot time. +#### nohup + +`nohup` is a command with which your terminal is told to ignore the `HUP` or 'hangup' signal. This will stop the node process ending if you kill your session. + +``` +nohup ./ run --id # where `` is the id you set during the `init` command and depends on which node you starting +``` + #### tmux One way is to use `tmux` shell on top of your current VPS terminal. Tmux is a terminal multiplexer, it allows you to create several terminal windows and panes from a single terminal. Processes started in `tmux` keep running after closing the terminal as long as the given `tmux` window was not terminated. diff --git a/documentation/operators/src/nodes/mix-node-setup.md b/documentation/operators/src/nodes/mix-node-setup.md index 8797c13056..7e1cafa64c 100644 --- a/documentation/operators/src/nodes/mix-node-setup.md +++ b/documentation/operators/src/nodes/mix-node-setup.md @@ -8,13 +8,7 @@ TODO - [x] Add step 2/3 in the wallet bonding - [x] Better picture alignment. - [x] Add second console output to Initialising your mixnode manually as the automatized is not working -- [ ] Point users to understand the requirements for becoming a part of the active set. -- [ ] Include how to move a mix node from one VPS to another while keeping the delegation etc (ie init a new mix node and move `/home/nym/.nym/mixnodes/data/*` there ?) - - copy that dir over to the new vps - - re-run init - remember that init doesn't overwrite existing keys - to generate a config with the new listening address etc - - change the node info via the wallet - - Since otherwise their keys will point to the old IP address in the smart contract, and their node will (a) not be able to be connected to, and (b) fail uptime checks -- [ ] Explain more about self hosted nodes in the 6.1 not only as a troubleshooting +- [x] Explain more about self hosted nodes in the 6.1 not only as a troubleshooting - [x] Move the upgrade, automation, ulimit, vps, API and port parts to maintenance.md ---> @@ -327,7 +321,7 @@ There are also 2 community explorers which have been created by [Nodes Guru](htt For more details see [Troubleshooting FAQ](../nodes/troubleshooting.md) - + ## Maintenance diff --git a/documentation/operators/src/nodes/troubleshooting.md b/documentation/operators/src/nodes/troubleshooting.md index b27a417063..e5333bcfe4 100644 --- a/documentation/operators/src/nodes/troubleshooting.md +++ b/documentation/operators/src/nodes/troubleshooting.md @@ -1,13 +1,323 @@ - - # Troubleshooting +## Binary Build Problems + +### I am trying to build from the GitHub archive files and the build fails + +GitHub automatically includes .zip and tar.gz files of the Nym repository in its release. You cannot extract these and build - you'll see something like this: + +``` + process didn't exit successfully: `/build/nym/src/nym-0.12.1/target/release/build/nym-socks5-client-c1d0f76a8c7d7e9a/build-script-build` (exit status: 101) + --- stderr + thread 'main' panicked at 'failed to extract build metadata: could not find repository from '/build/nym/src/nym-0.12.1/clients/socks5'; class=Repository (6); code=NotFound (-3)', clients/socks5/build.rs:7:31 + note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace +warning: build failed, waiting for other jobs to finish... +error: build failed +``` + +Why does this happen? + +We have scripts which automatically include the Git commit hash and Git tag in the binary for easier debugging later. If you download a .zip and try building from that, it's not a Git repository and build will fail as above. + +What to do? + +* Open terminal in the directory where you want to have a git repository +* To get Nym repository for the first time, run: +``` +git clone https://github.com/nymtech/nym.git +``` +* Follow the instructions to build the platform +* To upgrade, pause your nodes, in the same terminal window run `git pull`, follow the upgrade instructions and re-start your nodes. + +## General Node Config + +### Where can I find my private and public keys and config? + +All config and keys files are stored in a directory named after your `id` which you chose during the `init` process, and can be found at the following PATH: `$HOME/.nym//` where `$HOME` is a home directory of the user (your current user in this case) that launched the node or client. + +The directory structure for each node will be roughly as follows: + +``` +bob@nym:~$ tree /home/nym/.nym/mixnodes/ +/home/nym/.nym/mixnodes/ +|-- nym010 +| |-- config +| | `-- config.toml +| `-- data +| |-- private_identity.pem +| |-- private_sphinx.pem +| |-- public_identity.pem +| `-- public_sphinx.pem +``` + +> If you `cat` the `public_sphinx.pem` key, the output will be different from the public key you will see on Nym [dashboard](https://sandbox-explorer.nymtech.net/). The reason for this is that `.pem` files are encoded in **base64**, however on the web they are in **base58**. Don't be confused if your keys look different. They are the same keys, just with different encoding :) + + +## Mix Nodes + +### How can I tell my node is up and running and mixing traffic? + +First of all check the 'Mixnodes' section of either the Nym Network Explorers: +* [Mainnet](https://explorer.nymtech.net/) +* [Sandbox testnet](https://sandbox-explorer.nymtech.net/) + +Enter your **identity key** to find your node. Check the contents of the `Mixnode stats` and `Routing score` sections. + +There are 2 community explorers currently, which have been created by [Nodes Guru](https://nodes.guru): +* [Mainnet](https://mixnet.explorers.guru/) +* [Sandbox testnet](https://sandbox.mixnet.explorers.guru/) + +If you want more information, or if your node isn't showing up on the explorer of your choice and you want to double-check, here are some examples on how to check if the node is configured properly. + +#### Check from your VPS + +Additional details can be obtained via various methods after you connect to your VPS: + +##### Socket statistics with `ss` + +``` +sudo ss -s -t | grep 1789 # if you have specified a different port in your mixnode config, change accordingly +``` + +This command should return a lot of data containing `ESTAB`. This command should work on every unix based system. + +##### List open files and reliant processes with `lsof` + +``` +# check if lsof is installed: +lsof -v +# install if not installed +sudo apt install lsof +# run against mixnode port +sudo lsof -i TCP:1789 # if you have specified a different port in your mixnode config, change accordingly +``` + +This command should return something like this: + +``` +nym-mixno 103349 root 53u IPv6 1333229972 0t0 TCP [2a03:b0c0:3:d0::ff3:f001]:57844->[2a01:4f9:c011:38ae::5]:1789 (ESTABLISHED) +nym-mixno 103349 root 54u IPv4 1333229973 0t0 TCP nym:57104->194.5.78.73:1789 (ESTABLISHED) +nym-mixno 103349 root 55u IPv4 1333229974 0t0 TCP nym:48130->static.236.109.119.168.clients.your-server.de:1789 (ESTABLISHED) +nym-mixno 103349 root 56u IPv4 1333229975 0t0 TCP nym:52548->vmi572614.contaboserver.net:1789 (ESTABLISHED) +nym-mixno 103349 root 57u IPv6 1333229976 0t0 TCP [2a03:b0c0:3:d0::ff3:f001]:43244->[2600:1f18:1031:2401:c04b:2f25:ca79:fef3]:1789 (ESTABLISHED) +``` + +##### Query `systemd` journal with `journalctl` + +``` +sudo journalctl -u nym-mixnode -o cat | grep "Since startup mixed" +``` + +If you have created `nym-mixnode.service` file (i.e. you are running your mixnode via `systemd`) then this command shows you how many packets have you mixed so far, and should return a list of messages like this: + +``` +2021-05-18T12:35:24.057Z INFO nym_mixnode::node::metrics > Since startup mixed 233639 packets! +2021-05-18T12:38:02.178Z INFO nym_mixnode::node::metrics > Since startup mixed 233739 packets! +2021-05-18T12:40:32.344Z INFO nym_mixnode::node::metrics > Since startup mixed 233837 packets! +2021-05-18T12:46:08.549Z INFO nym_mixnode::node::metrics > Since startup mixed 234081 packets! +2021-05-18T12:56:57.129Z INFO nym_mixnode::node::metrics > Since startup mixed 234491 packets! +``` + +You can add ` | tail` to the end of the command to watch for new entries in real time if needed. + +##### build-info + +A `build-info` command prints the build information like commit hash, rust version, binary version just like what command `--version` does. However, you can also specify an `--output=json` flag that will format the whole output as a json, making it an order of magnitude easier to parse. + +For example `./target/debug/nym-network-requester --no-banner build-info --output json` will return: + +``` +{"binary_name":"nym-network-requester","build_timestamp":"2023-07-24T15:38:37.00657Z","build_version":"1.1.23","commit_sha":"c70149400206dce24cf20babb1e64f22202672dd","commit_timestamp":"2023-07-24T14:45:45Z","commit_branch":"feature/simplify-cli-parsing","rustc_version":"1.71.0","rustc_channel":"stable","cargo_profile":"debug"} +``` + +#### Check from your local machine + +##### Scan ports with `nmap`: + +``` +nmap -p 1789 -Pn +``` + +If your mixnode is configured properly it should output something like this: + +``` +bob@desktop:~$ nmap -p 1789 95.296.134.220 -Pn + +Host is up (0.053s latency). + +PORT STATE SERVICE +1789/tcp open hello +``` + +##### Query online nodes: + +``` +curl --location --request GET 'https://validator.nymtech.net/api/v1/mixnodes/' +``` + +Will return a list all nodes currently online. + +You can query gateways by replacing `mixnodes` with `gateways` in the above command, and can query for the mixnodes and gateways on the Sandbox testnet by replacing `validator` with `sandbox-validator`. + + +#### Check with Network API + +We currently have an API set up returning our metrics tests of the network. There are two endpoints to ping for information about your mixnode, `report` and `history`. Find more information about this in the [Mixnodes metrics documentation](./maintenance.md#metrics--api-endpoints). + +### Why is my node not mixing any packets? + +If you are still unable to see your node on the dashboard, or your node is declaring it has not mixed any packets, there are several potential issues: + +- The firewall on your host machine is not configured properly. Checkout the [instructions](./maintenance.md#configure-your-firewall). +- You provided incorrect information when bonding your node. +- You are running your mixnode from a VPS without IPv6 support. +- You did not use the `--announce-host` flag while running the mixnode from your local machine behind NAT. +- You did not configure your router firewall while running the mixnode from your local machine behind NAT, or you are lacking IPv6 support. +- Your mixnode is not running at all, it has either exited / panicked or you closed the session without making the node persistent. Check out the [instructions](./maintenance.md#automating-your-node-with-tmux-and-systemd). + +```admonish caution +Your mixnode **must speak both IPv4 and IPv6** in order to cooperate with other nodes and route traffic. This is a common reason behind many errors we are seeing among node operators, so check with your provider that your VPS is able to do this! +``` + +#### Incorrect bonding information + +Check that you have provided the correct information when bonding your mixnode in the web wallet [interface](https://sandbox-wallet.nymtech.net/). When in doubt, un-bond and then re-bond your node! + +> All delegated stake will be lost when un-bonding! However the mixnode must be operational in the first place for the delegation to have any effect. + +#### Missing `announce-host` flag + +On certain cloud providers such as AWS and Google Cloud, you need to do some additional configuration of your firewall and use `--host` with your **local ip** and `--announce-host` with the **public ip** of your mixnode host. + +If the difference between the two is unclear, contact the help desk of your VPS provider. + +#### No IPv6 connectivity + +Make sure that your VPS has IPv6 connectivity available with whatever provider you are using. + +To get all ip addresses of your host, try following commands: + +``` +hostname -i +``` + +Will return your **local ip** address. + +``` +hostname -I +``` + +Will return all of the ip addresses of your host. This output should look something like this: + +``` +bob@nym:~$ hostname -I +88.36.11.23 172.18.0.1 2a01:28:ca:102::1:641 +``` + +- The first **ipv4** is the public ip you need to use for the `--announce-host` flag. +- The second **ipv4** is the local ip you need to use for the `--host` flag. +- The 3rd output should confirm if your machine has ipv6 available. + +### Running on a local machine behind NAT with no fixed IP address + +Your ISP has to be IPv6 ready if you want to run a mixnode on your local machine. Sadly, in 2020, most of them are not and you won't get an IPv6 address by default from your ISP. Usually it is a extra paid service or they simply don't offer it. + +Before you begin, check if you have IPv6 [here](https://test-ipv6.cz/) or by running command explained in the [section above](./troubleshooting.md#no-ipv6-connectivity). If not, then don't waste your time to run a node which won't ever be able to mix any packet due to this limitation. Call your ISP and ask for IPv6, there is a plenty of it for everyone! + +If all goes well and you have IPv6 available, then you will need to `init` the mixnode with an extra flag, `--announce-host`. You will also need to edit your `config.toml` file each time your IPv4 address changes, that could be a few days or a few weeks. Check the your IPv4 in the [section above](./troubleshooting.md#no-ipv6-connectivity). + +Additional configuration on your router might also be needed to allow traffic in and out to port 1789 and IPv6 support. + +Here is a sample of the `init` command example to create the mixnode config. + +``` +./nym-mixnode init --id --host 0.0.0.0 --announce-host 85.160.12.13 +``` + +- `--host 0.0.0.0` should work every time even if your local machine IPv4 address changes. For example on Monday your router gives your machine an address `192.168.0.13` and on Wednesday, the DHCP lease will end and you will be asigned `192.168.0.14`. Using `0.0.0.0` should avoid this without having to set any static ip in your router`s configuration. + +- you can get your current IPv4 address by either using `curl ipinfo.io` if you're on MacOS or Linux or visiting [whatsmyip site](https://www.whatsmyip.org/). Simply copy it and use it as `--anounce-host` address. + +Make sure you check if your node is really mixing. We are aiming to improve the setup for operators running locally, however you may need a bit of patience to set this up from your home behind NAT. + +### Accidentally killing your node process on exiting session + +When you close your current terminal session, you need to make sure you don't kill the mixnode process! There are multiple ways on how to make it persistent even after exiting your ssh session, the easiest solution is to use `tmux` or `nohup`, and the more elegant solution is to run the node with `systemd`. Read the automation manual [here](./maintenance.md#automating-your-node-with-tmux-and-systemd). + +### Common errors and warnings + +Most of the `ERROR` and `WARN` messages in your node logs are benign - as long as your node outputs `since startup mixed X packets!` (`X` bust be > 0) in your logs (and this number increases over time), your node is mixing packets. If you want to be sure, check the Nym [dashboard](https://sandbox-explorer.nymtech.net/) or see other ways on how to check if your node is mixing properly as outlined in the section [**How can I tell my node is up and running and mixing traffic?**](./troubleshooting.md#how-can-i-tell-my-node-is-up-and-running-and-mixing-traffic?) above. + +More specific errors and warnings are covered below. + +#### `tokio runtime worker` error + +If you are running into issues with an error including the following: + +``` +thread 'tokio-runtime-worker' panicked at 'Failed to create TCP listener: Os { code: 99, kind: AddrNotAvailable, message: "Cannot assign requested address" }' +``` + +Then you need to `--announce-host ` and `--host ` on startup. This issue is addressed [above](./troubleshooting.md#missing-`announce-host`-flag) + + +### Can I use a port other than 1789 ? + +Yes! Here is what you will need to do: + +Assuming you would like to use port `1337` for your mixnode, you need to open the new port (and close the old one): + +``` +sudo ufw allow 1337 +sudo ufw deny 1789 +``` + +And then edit the mixnode's config. + +> If you want to change the port for an already running node, you need to stop the process before editing your config file. + +The config file is located at `~/.nym/mixnodes//config/config.toml`. + +For example, assuming `` was chosen to be `alice-node`: + +``` +nano ~/.nym/mixnodes/alice-node/config/config.toml +``` + +You will need to edit two parts of the file. `announce_address` and `listening_address` in the config.toml file. Simply replace `:1789` (the default port) with `:1337` (your new port) after your IP address. + +Finally, restart your node. You should see if the mixnode is using the port you have changed in the config.toml file right after you run the node. + +### What is `verloc` and do I have to configure my mixnode to implement it? + +`verloc` is short for _verifiable location_. Mixnodes and gateways now measure speed-of-light distances to each other, in an attempt to verify how far apart they are. In later releases, this will allow us to algorithmically verify node locations in a non-fakeable and trustworthy manner. + +You don't have to do any additional configuration for your node to implement this, it is a passive process that runs in the background of the mixnet from version `0.10.1` onward. + +## Validators + ### Common reasons for your validator being jailed The most common reason for your validator being jailed is that your validator is out of memory because of bloated syslogs. @@ -16,3 +326,10 @@ Running the command `df -H` will return the size of the various partitions of yo If the `/dev/sda` partition is almost full, try pruning some of the `.gz` syslog archives and restart your validator process. + +## Where can I get more help? + +The fastest way to reach one of us or get a help from the community, visit our [Telegram Node Setup Help Chat](https://t.me/nymchan_help_chat) or head to our [Discord](https://Discord.gg/nym). + +For more tech heavy question join our [Matrix core community channel](https://matrix.to/#/#general:nymtech.chat), where you can meet other builders and Nym core team members. + From 3aa9ad01972cd8088368853a386f2964c0630169 Mon Sep 17 00:00:00 2001 From: serinko <97586125+serinko@users.noreply.github.com> Date: Thu, 27 Jul 2023 13:01:09 +0200 Subject: [PATCH 019/361] corrected scp -3 option --- documentation/operators/src/nodes/maintenance.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/documentation/operators/src/nodes/maintenance.md b/documentation/operators/src/nodes/maintenance.md index d809027150..26d5375a7d 100644 --- a/documentation/operators/src/nodes/maintenance.md +++ b/documentation/operators/src/nodes/maintenance.md @@ -133,7 +133,7 @@ mkdir ~/.nym/mixnodes ``` * Move the node data (keys) and config file to the new machine by opening a local terminal (as that one's ssh key is authorized in both of the machines) and running: ``` -scp -r @:~/.nym/mixnodes/ @:~/.nym/mixnodes/ +scp -r -3 @:~/.nym/mixnodes/ @:~/.nym/mixnodes/ ``` * Re-run init (remember that init doesn't overwrite existing keys) to generate a config with the new listening address etc. * Change the node smart contract info via the wallet interface. Otherwise the keys will point to the old IP address in the smart contract, and the node will not be able to be connected, and it will fail up-time checks. From ac8afe133fae9362636c2af0ddfdc6b2ca78c929 Mon Sep 17 00:00:00 2001 From: pierre Date: Thu, 27 Jul 2023 15:53:48 +0200 Subject: [PATCH 020/361] fix(nc-desktop): typo --- .../desktop/src/pages/menu/settings/PrivacyLevelSettings.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/nym-connect/desktop/src/pages/menu/settings/PrivacyLevelSettings.tsx b/nym-connect/desktop/src/pages/menu/settings/PrivacyLevelSettings.tsx index 5c2241b4d0..a97e7fdfdf 100644 --- a/nym-connect/desktop/src/pages/menu/settings/PrivacyLevelSettings.tsx +++ b/nym-connect/desktop/src/pages/menu/settings/PrivacyLevelSettings.tsx @@ -37,8 +37,8 @@ export const PrivacyLevelSettings = () => { label="Enable" /> - By activating this option, the connection speed will be relatively faster in exchange of relaxing some - protections + By activating this option, the connection speed will be relatively faster in exchange for relaxing some + privacy protections From 5964f104c5ba699bda1a8e753741c01bbf7ea05c Mon Sep 17 00:00:00 2001 From: pierre Date: Thu, 27 Jul 2023 15:53:48 +0200 Subject: [PATCH 021/361] fix(nc-desktop): typo --- .../desktop/src/pages/menu/settings/PrivacyLevelSettings.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/nym-connect/desktop/src/pages/menu/settings/PrivacyLevelSettings.tsx b/nym-connect/desktop/src/pages/menu/settings/PrivacyLevelSettings.tsx index 5c2241b4d0..a97e7fdfdf 100644 --- a/nym-connect/desktop/src/pages/menu/settings/PrivacyLevelSettings.tsx +++ b/nym-connect/desktop/src/pages/menu/settings/PrivacyLevelSettings.tsx @@ -37,8 +37,8 @@ export const PrivacyLevelSettings = () => { label="Enable" /> - By activating this option, the connection speed will be relatively faster in exchange of relaxing some - protections + By activating this option, the connection speed will be relatively faster in exchange for relaxing some + privacy protections From 5fbccc34066bc9437779f613245e93d8859a54c0 Mon Sep 17 00:00:00 2001 From: pierre Date: Thu, 27 Jul 2023 19:11:33 +0200 Subject: [PATCH 022/361] build(nc-android): moving build config to kotlin --- nym-connect/native/android/app/build.gradle | 127 ----------------- .../native/android/app/build.gradle.kts | 131 ++++++++++++++++++ nym-connect/native/android/build.gradle | 6 - nym-connect/native/android/build.gradle.kts | 6 + .../{settings.gradle => settings.gradle.kts} | 4 +- 5 files changed, 140 insertions(+), 134 deletions(-) delete mode 100644 nym-connect/native/android/app/build.gradle create mode 100644 nym-connect/native/android/app/build.gradle.kts delete mode 100644 nym-connect/native/android/build.gradle create mode 100644 nym-connect/native/android/build.gradle.kts rename nym-connect/native/android/{settings.gradle => settings.gradle.kts} (94%) diff --git a/nym-connect/native/android/app/build.gradle b/nym-connect/native/android/app/build.gradle deleted file mode 100644 index 72ba890287..0000000000 --- a/nym-connect/native/android/app/build.gradle +++ /dev/null @@ -1,127 +0,0 @@ -plugins { - id 'com.android.application' - id 'org.jetbrains.kotlin.android' - id 'org.jetbrains.kotlin.plugin.serialization' version '1.8.21' - id 'io.sentry.android.gradle' version '3.11.0' -} - -android { - namespace 'net.nymtech.nyms5' - compileSdk 33 - - defaultConfig { - applicationId "net.nymtech.nyms5" - minSdk 24 - targetSdk 33 - versionCode 1 - versionName "1.0" - - testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" - vectorDrawables { - useSupportLibrary true - } - } - - buildTypes { - release { - minifyEnabled true - proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' - } - } - - flavorDimensions "abi" - productFlavors { - universal { - dimension "abi" - ndk { - abiFilters "arm64-v8a", "armeabi-v7a", "x86_64", "x86" - } - } - arch64 { - dimension "abi" - ndk { - abiFilters "arm64-v8a", "x86_64" - } - } - arm64 { - dimension "abi" - ndk { - abiFilters "arm64-v8a" - } - } - arm { - dimension "abi" - ndk { - abiFilters "armeabi-v7a" - } - } - x86_64 { - dimension "abi" - ndk { - abiFilters "x86_64" - } - } - x86 { - dimension "abi" - ndk { - abiFilters "x86" - } - } - } - -// splits { -// abi { -// enable true -// reset() -// include "x86_64", "arm64-v8a" -// universalApk true -// } -// } - - compileOptions { - sourceCompatibility JavaVersion.VERSION_1_8 - targetCompatibility JavaVersion.VERSION_1_8 - } - kotlinOptions { - jvmTarget = '1.8' - } - buildFeatures { - compose true - } - composeOptions { - kotlinCompilerExtensionVersion '1.4.6' - } - packagingOptions { - resources { - excludes += '/META-INF/{AL2.0,LGPL2.1}' - } - } -} - -dependencies { - implementation 'androidx.core:core-ktx:1.10.1' - implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.6.1' - implementation 'androidx.activity:activity-compose:1.7.2' - implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.4' - implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.4' - implementation 'org.jetbrains.kotlinx:kotlinx-serialization-json:1.5.0' - implementation platform('androidx.compose:compose-bom:2022.10.00') - implementation 'androidx.lifecycle:lifecycle-viewmodel-compose:2.6.1' - implementation 'androidx.navigation:navigation-compose:2.6.0' - implementation 'androidx.compose.runtime:runtime-livedata' - implementation 'androidx.compose.ui:ui' - implementation 'androidx.compose.ui:ui-graphics' - implementation 'androidx.compose.ui:ui-tooling-preview' - implementation 'androidx.compose.material3:material3' - implementation 'androidx.work:work-runtime-ktx:2.8.1' - implementation 'androidx.datastore:datastore-preferences:1.0.0' - testImplementation 'junit:junit:4.13.2' - androidTestImplementation 'androidx.test.ext:junit:1.1.3' - androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0' - androidTestImplementation platform('androidx.compose:compose-bom:2022.10.00') - androidTestImplementation 'androidx.compose.ui:ui-test-junit4' - debugImplementation 'androidx.compose.ui:ui-tooling' - debugImplementation 'androidx.compose.ui:ui-test-manifest' - implementation 'com.github.kittinunf.fuel:fuel:2.3.1' - implementation 'io.sentry:sentry-android:6.24.0' -} diff --git a/nym-connect/native/android/app/build.gradle.kts b/nym-connect/native/android/app/build.gradle.kts new file mode 100644 index 0000000000..b33dd6d715 --- /dev/null +++ b/nym-connect/native/android/app/build.gradle.kts @@ -0,0 +1,131 @@ +plugins { + id("com.android.application") + id("org.jetbrains.kotlin.android") + id("org.jetbrains.kotlin.plugin.serialization") version "1.8.21" + id("io.sentry.android.gradle") version "3.11.0" +} + +android { + namespace = "net.nymtech.nyms5" + compileSdk = 33 + + defaultConfig { + applicationId = "net.nymtech.nyms5" + minSdk = 24 + targetSdk = 33 + versionCode = 1 + versionName = "1.0" + + testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" + vectorDrawables { + useSupportLibrary = true + } + } + + buildTypes { + release { + isMinifyEnabled = true + proguardFiles( + getDefaultProguardFile("proguard-android-optimize.txt"), + "proguard-rules.pro" + ) + } + } + + flavorDimensions += "abi" + productFlavors { + create("universal") { + dimension = "abi" + ndk { + abiFilters += listOf("arm64-v8a", "armeabi-v7a", "x86_64", "x86") + } + } + create("arch64") { + dimension = "abi" + ndk { + abiFilters += listOf("arm64-v8a", "x86_64") + } + } + create("arm64") { + dimension = "abi" + ndk { + abiFilters += "arm64-v8a" + } + } + create("arm") { + dimension = "abi" + ndk { + abiFilters += "armeabi-v7a" + } + } + create("x86_64") { + dimension = "abi" + ndk { + abiFilters += "x86_64" + } + } + create("x86") { + dimension = "abi" + ndk { + abiFilters += "x86" + } + } + } + +// splits { +// abi { +// enable true +// reset() +// include "x86_64", "arm64-v8a" +// universalApk true +// } +// } + + compileOptions { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 + } + kotlinOptions { + jvmTarget = "1.8" + } + buildFeatures { + compose = true + } + composeOptions { + kotlinCompilerExtensionVersion = "1.4.6" + } + packaging { + resources { + excludes += "/META-INF/{AL2.0,LGPL2.1}" + } + } +} + +dependencies { + + implementation("androidx.core:core-ktx:1.10.1") + implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.6.1") + implementation("androidx.activity:activity-compose:1.7.2") + implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.4") + implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.4") + implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.5.0") + implementation(platform("androidx.compose:compose-bom:2022.10.00")) + implementation("androidx.lifecycle:lifecycle-viewmodel-compose:2.6.1") + implementation("androidx.navigation:navigation-compose:2.6.0") + implementation("androidx.compose.runtime:runtime-livedata") + implementation("androidx.compose.ui:ui") + implementation("androidx.compose.ui:ui-graphics") + implementation("androidx.compose.ui:ui-tooling-preview") + implementation("androidx.compose.material3:material3") + implementation("androidx.work:work-runtime-ktx:2.8.1") + implementation("androidx.datastore:datastore-preferences:1.0.0") + testImplementation("junit:junit:4.13.2") + androidTestImplementation("androidx.test.ext:junit:1.1.3") + androidTestImplementation("androidx.test.espresso:espresso-core:3.4.0") + androidTestImplementation(platform("androidx.compose:compose-bom:2022.10.00")) + androidTestImplementation("androidx.compose.ui:ui-test-junit4") + debugImplementation("androidx.compose.ui:ui-tooling") + debugImplementation("androidx.compose.ui:ui-test-manifest") + implementation("com.github.kittinunf.fuel:fuel:2.3.1") + implementation("io.sentry:sentry-android:6.24.0") +} diff --git a/nym-connect/native/android/build.gradle b/nym-connect/native/android/build.gradle deleted file mode 100644 index fd3d478923..0000000000 --- a/nym-connect/native/android/build.gradle +++ /dev/null @@ -1,6 +0,0 @@ -// Top-level build file where you can add configuration options common to all sub-projects/modules. -plugins { - id 'com.android.application' version '8.0.2' apply false - id 'com.android.library' version '8.0.2' apply false - id 'org.jetbrains.kotlin.android' version '1.8.20' apply false -} \ No newline at end of file diff --git a/nym-connect/native/android/build.gradle.kts b/nym-connect/native/android/build.gradle.kts new file mode 100644 index 0000000000..6cb6871c9e --- /dev/null +++ b/nym-connect/native/android/build.gradle.kts @@ -0,0 +1,6 @@ +// Top-level build file where you can add configuration options common to all sub-projects/modules. +plugins { + id("com.android.application") version "8.1.0" apply false + id("com.android.library") version "8.1.0" apply false + id("org.jetbrains.kotlin.android") version "1.8.20" apply false +} \ No newline at end of file diff --git a/nym-connect/native/android/settings.gradle b/nym-connect/native/android/settings.gradle.kts similarity index 94% rename from nym-connect/native/android/settings.gradle rename to nym-connect/native/android/settings.gradle.kts index 0bf8eaecd3..267005beeb 100644 --- a/nym-connect/native/android/settings.gradle +++ b/nym-connect/native/android/settings.gradle.kts @@ -12,5 +12,7 @@ dependencyResolutionManagement { mavenCentral() } } + rootProject.name = "nyms5" -include ':app' +include(":app") + \ No newline at end of file From 02f5fa6816d4713e04507a74a1708682508fdeea Mon Sep 17 00:00:00 2001 From: serinko <97586125+serinko@users.noreply.github.com> Date: Fri, 28 Jul 2023 13:22:40 +0200 Subject: [PATCH 023/361] faq.md: initial questions and answers - in progress --- documentation/operators/src/faq.md | 67 ++- .../operators/src/nodes/validator-setup.md | 558 ++++++++++++++++++ 2 files changed, 620 insertions(+), 5 deletions(-) create mode 100644 documentation/operators/src/nodes/validator-setup.md diff --git a/documentation/operators/src/faq.md b/documentation/operators/src/faq.md index 88836f17f2..4fbc17694a 100644 --- a/documentation/operators/src/faq.md +++ b/documentation/operators/src/faq.md @@ -1,10 +1,10 @@ +---> +# Frequently Asked Questions + +## Mixnet nodes + +### What determines the rewards when running a mixnode? + +The stake required for a mix node to achieve maximum rewards is called to the mix node saturation point os simply stake saturation point. This is calculated from the staking supply (all circulating supply + part of unlocked tokens). The target level of staking is to have 50% of the staking supply locked in mix nodes. + +The node stake saturation point, which we denote by Nsat, is given by the stake supply, target level of staking divided by the number of rewarded (active) nodes. + +This design ensures the nodes aim to have a same size of stake (reputation) which can be done by delegation staking, as well as it ensures that there is a decentralization of staking as any higher level of staked tokens per node results in worse rewards. On the contrary, the more mix nodes are active, the lower is Nsat. The equilibrium is reached when the staked tokens are delegated equally across the active mix-nodes and that's our basis for this incentive system. + +For more detailed calculation, read our blog post [Nym Token Economics update](https://blog.nymtech.net/nym-token-economics-update-fedff0ed5267). More info on staking can be found [here](https://blog.nymtech.net/staking-in-nym-introducing-mainnet-mixmining-f9bb1cbc7c36). And [here](https://blog.nymtech.net/want-to-stake-in-nym-here-is-how-to-choose-a-mix-node-to-delegate-nym-to-c3b862add165) is more info on how to choose a mix node for delegation. And finally an [update](https://blog.nymtech.net/quarterly-token-economic-parameter-update-b2862948710f) on token economics from July 2023. + +### Which VPS providers would you recommend? + +Consider in which jurisdiction they reside and where they want to run a mix node. Do you want to pay by crypto or not and what are the other important particularities for your case? We always recommend operators to try to chose smaller and decentralized VPS providers over the most known ones controlling a majority of the internet. We recieve some good feedback on these: Hertzner, DigitalOcean, Linode, Ghandi and Exoscale. Do your own research and share with the community. + +### Why is a mixnode setup on a self-hosted machine so tricky? + +### What's the Sphinx packet size? + +The sizes are shown in the configs [here](https://github.com/nymtech/nym/blob/1ba6444e722e7757f1175a296bed6e31e25b8db8/common/nymsphinx/params/src/packet_sizes.rs#L12) (default is the one clients use, the others are for research purposes, not to be used in production as this would fragment the anonymity set). More info can be found [here](https://github.com/nymtech/nym/blob/4844ac953a12b29fa27688609ec193f1d560c996/common/nymsphinx/anonymous-replies/src/reply_surb.rs#L80). + +### Why a mix node and a gateway cannot be bond to the same wallet? + +Becauase of the way the smart contract works we keep it one-node one-address at the moment. + +### Which nodes are the most needed to be setup to strengthen Nym infrastructure and which ones bring rewards? + +Right now only mix nodes are rewarded. We're working on gateway and service payments. Gateways are the weak link right now due mostly to lack of incentivisation. Services like Network Requesters are obviously the most necessary for people to start using the platform, and we're working on smart contracts to allow for people to start advertising them the same way they do mix nodes. + +### Are mixnodes whitelisted? + +Nope, anyone can run a mixnode. Purely reliant on the node's reputation (self stake + delegations) & routing score. + +## Validators and tokens + +### What's the difference between NYM and uNYM? + +1 NYM = 1 000 000 uNYM + + + +### Can I run a validator? + +We are currently working towards building up a closed set of reputable validators. You can ask us for coins to get in, but please don't be offended if we say no - validators are part of our system's core security and we are starting out with people we already know or who have a solid reputation. + +### Why is validator set entry whitelisted? + +We understand that the early days of the Nyx blockchain will face possible vulnerabilities in terms of size - easy to disrupt or halt the chain if a malicious party entered with a large portion of stake. Besides that, there are some legal issues we need to address before we can distribute the validator set in a fully permissions fashion. + +### Why does Nym do many airdrops? + +It is part of ensuring decentralisation - we need to avoid a handful of people having too much control over the token and market. Of course ideally people will stake the tokens and contribute to the project at this stage. We run surveys to better understand what people are doing with their tokens and what usability issues there are for staking. Any feedback is appreciated as it helps us improve all aspects of using the token and participating in the ecosystem. diff --git a/documentation/operators/src/nodes/validator-setup.md b/documentation/operators/src/nodes/validator-setup.md new file mode 100644 index 0000000000..6eafb49106 --- /dev/null +++ b/documentation/operators/src/nodes/validator-setup.md @@ -0,0 +1,558 @@ + +# Validators + +> Nym has two main codebases: +> - the [Nym platform](https://github.com/nymtech/nym), written in Rust. This contains all of our code except for the validators. +> - the [Nym validators](https://github.com/nymtech/nyxd), written in Go. + +The validator is built using [Cosmos SDK](https://cosmos.network) and [Tendermint](https://tendermint.com), with a [CosmWasm](https://cosmwasm.com) smart contract controlling the directory service, node bonding, and delegated mixnet staking. + +> We are currently working towards building up a closed set of reputable validators. You can ask us for coins to get in, but please don't be offended if we say no - validators are part of our system's core security and we are starting out with people we already know or who have a solid reputation. + +## Building your validator + +> Any syntax in `<>` brackets is a user's unique variable. Exchange it with a corresponding name without the `<>` brackets. + +### Prerequisites +#### `git`, `gcc`, `jq` + +* Debian-based systems: +``` +apt install git build-essential jq + +# optional additional manual pages can be installed with: +apt-get install manpages-dev +``` + +* Arch-based systems: +Install `git`, `gcc` and `jq` with the following: +``` +pacman -S git gcc jq +``` + +#### `Go` +`Go` can be installed via the following commands (taken from the [Go Download and install page](https://go.dev/doc/install)): + +``` +# First remove any existing old Go installation and extract the archive you just downloaded into /usr/local: +# You may need to run the command as root or through sudo +rm -rf /usr/local/go && tar -C /usr/local -xzf go1.20.6.linux-amd64.tar.gz + +# Add /usr/local/go/bin to the PATH environment variable +export PATH=$PATH:/usr/local/go/bin +source $HOME/.profile +``` + + +Verify `Go` is installed with: + +``` +go version +# Should return something like: +go version go1.20.4 linux/amd64 +``` + +### Download a precompiled validator binary +You can find pre-compiled binaries for Ubuntu `22.04` and `20.04` [here](https://github.com/nymtech/nyxd/releases). + +```admonish caution title="" +There are seperate releases for Mainnet and the Sandbox testnet - make sure to download the correct binary to avoid `bech32Prefix` mismatches. +``` + +### Manually compiling your validator binary +The codebase for the Nyx validators can be found [here](https://github.com/nymtech/nyxd). + +The validator binary can be compiled by running the following commands: +``` +git clone https://github.com/nymtech/nyxd.git +cd nyxd +git checkout release/ + +# Mainnet +make build + +# Sandbox testnet +BECH32_PREFIX=nymt make build +``` + +At this point, you will have a copy of the `nyxd` binary in your `build/` directory. Test that it's compiled properly by running: + +``` +./build/nyxd +``` + +You should see a similar help menu printed to you: + +~~~admonish example collapsible=true title="Console output" +``` +Wasm Daemon (server) + +Usage: + nyxd [command] + +Available Commands: + add-genesis-account Add a genesis account to genesis.json + add-wasm-genesis-message Wasm genesis subcommands + collect-gentxs Collect genesis txs and output a genesis.json file + config Create or query an application CLI configuration file + debug Tool for helping with debugging your application + export Export state to JSON + gentx Generate a genesis tx carrying a self delegation + help Help about any command + init Initialize private validator, p2p, genesis, and application configuration files + keys Manage your application's keys + query Querying subcommands + rollback rollback cosmos-sdk and tendermint state by one height + start Run the full node + status Query remote node for status + tendermint Tendermint subcommands + tx Transactions subcommands + validate-genesis validates the genesis file at the default location or at the location passed as an arg + version Print the application binary version information + +Flags: + -h, --help help for nyxd + --home string directory for config and data (default "/home/willow/.nyxd") + --log_format string The logging format (json|plain) (default "plain") + --log_level string The logging level (trace|debug|info|warn|error|fatal|panic) (default "info") + --trace print out full stack trace on errors + +Use "nyxd [command] --help" for more information about a command. + +``` +~~~ + +### Linking `nyxd` to `libwasmvm.so` + +`libwasmvm.so` is the wasm virtual machine which is needed to execute smart contracts in `v0.26.1`. This file is renamed in `libwasmvm.x86_64.so` in `v0.31.1`. + +If you downloaded your `nyxd` binary from Github, you will have seen this file when un-`tar`-ing the `.tar.gz` file from the releases page. + +If you are seeing an error concerning this file when trying to run `nyxd`, then you need to move the `libwasmvm.so` file to correct location. + +Simply `cp` or `mv` that file to `/lib/x86_64-linux-gnu/` and re-run `nyxd`. + +### Adding `nyxd` to your `$PATH` +You'll need to set `LD_LIBRARY_PATH` in your user's `~/.bashrc` or `~/.zshrc` file (depends on the terminal you use), and add that to our path. Replace `/home///binaries` in the command below to the locations of `nyxd` and `libwasmvm.so` and run it. If you have compiled these on the server, they will be in the `build/` folder: + +``` +NYX_BINARIES=/home/// + +# if you are using another shell like zsh replace '.bashrc' with the relevant config file +echo 'export LD_LIBRARY_PATH=${LD_LIBRARY_PATH}:'NYX_BINARIES >> ~/.bashrc +echo 'export PATH=$PATH:'${NYX_BINARIES} >> ~/.bashrc +source ~/.bashrc +``` + +Test everything worked: + +``` +nyxd +``` + +This should return the regular help menu: + +~~~admonish example collapsible=true title="Console output" +``` +Wasm Daemon (server) + +Usage: + nyxd [command] + +Available Commands: + add-genesis-account Add a genesis account to genesis.json + add-wasm-genesis-message Wasm genesis subcommands + collect-gentxs Collect genesis txs and output a genesis.json file + config Create or query an application CLI configuration file + debug Tool for helping with debugging your application + export Export state to JSON + gentx Generate a genesis tx carrying a self delegation + help Help about any command + init Initialize private validator, p2p, genesis, and application configuration files + keys Manage your application's keys + query Querying subcommands + rollback rollback cosmos-sdk and tendermint state by one height + start Run the full node + status Query remote node for status + tendermint Tendermint subcommands + tx Transactions subcommands + validate-genesis validates the genesis file at the default location or at the location passed as an arg + version Print the application binary version information + +Flags: + -h, --help help for nyxd + --home string directory for config and data (default "/home/willow/.nyxd") + --log_format string The logging format (json|plain) (default "plain") + --log_level string The logging level (trace|debug|info|warn|error|fatal|panic) (default "info") + --trace print out full stack trace on errors + +Use "nyxd [command] --help" for more information about a command. + +``` +~~~ + +## Initialising your validator +### Prerequisites: + +- FQDN Domain name +- IPv4 and IPv6 connectivity + +Choose a name for your validator and use it in place of `` in the following command: + +``` +# Mainnet +nyxd init --chain-id=nyx + +# Sandbox testnet +nyxd init --chain-id=sandbox +``` + +```admonish caution title="" +`init` generates `priv_validator_key.json` and `node_key.json`. + +If you have already set up a validator on a network, **make sure to back up the key located at** +`~/.nyxd/config/priv_validator_key.json`. + +If you don't save the validator key, then it can't sign blocks and will be jailed all the time, and +there is no way to deterministically (re)generate this key. +``` + +At this point, you have a new validator, with its own genesis file located at `$HOME/.nyxd/config/genesis.json`. You will need to replace the contents of that file that with either the Nyx Mainnet or Sandbox Testnet genesis file. + +You can use the following command to download them for the correct network: + +``` +# Mainnet +wget -O $HOME/.nyxd/config/genesis.json https://nymtech.net/genesis/genesis.json + +# Sandbox testnet +wget -O $HOME/.nyxd/config/genesis.json https://sandbox-validator1.nymtech.net/snapshots/genesis.json +``` + +### `config.toml` configuration + +Edit the following config options in `$HOME/.nyxd/config/config.toml` to match the information below for your network: + +``` +# Mainnet +persistent_peers = "ee03a6777fb76a2efd0106c3769daaa064a3fcb5@51.79.21.187:26656" +create_empty_blocks = false +laddr = "tcp://0.0.0.0:26656" +``` + +``` +# Sandbox testnet +cors_allowed_origins = ["*"] +persistent_peers = "8421c0a3d90d490e27e8061f2abcb1276c8358b6@sandbox-validator1.nymtech.net:26666" +create_empty_blocks = false +laddr = "tcp://0.0.0.0:26656" +``` + +These affect the following: +* `persistent_peers = "@.nymtech.net:26666"` allows your validator to start pulling blocks from other validators. **The main sandbox validator listens on `26666` instead of the default `26656` for debugging**. It is recommended you do not change your port from `26656`. +* `create_empty_blocks = false` will save space +* `laddr = "tcp://0.0.0.0:26656"` is in your p2p configuration options + +Optionally, if you want to enable [Prometheus](https://prometheus.io/) metrics then the following must also match in the `config.toml`: + +- `prometheus = true` +- `prometheus_listen_addr = ":26660"` + +> Remember to enable metrics in the 'Configuring Prometheus metrics' section below as well. + +And if you wish to add a human-readable moniker to your node: + +- `moniker = ""` + +Finally, if you plan on using [Cockpit](https://cockpit-project.org/documentation.html) on your server, change the `grpc` port from `9090` as this is the port used by Cockpit. + +### `app.toml` configuration +In the file `$HOME/nyxd/config/app.toml`, set the following values: + +``` +# Mainnet +minimum-gas-prices = "0.025unym,0.025unyx" +enable = true in the `[api]` section to get the API server running +``` +``` +# Sandbox Testnet +minimum-gas-prices = "0.025unymt,0.025unyxt" +enable = true` in the `[api]` section to get the API server running +``` + +### Setting up your validator's admin user +You'll need an admin account to be in charge of your validator. Set that up with: + +``` +nyxd keys add nyxd-admin +``` + +This will add keys for your administrator account to your system's keychain and log your name, address, public key, and mnemonic. As the instructions say, remember to **write down your mnemonic**. + +You can get the admin account's address with: + +``` +nyxd keys show nyxd-admin -a +``` + +Type in your keychain **password**, not the mnemonic, when asked. + +## Starting your validator + +```admonish caution title="" +If you are running a Sandbox testnet validator, please skip the `validate-genesis` command: it will fail due to the size of the genesis file as this is a fork of an existing chain state. +``` + +Everything should now be ready to go. You've got the validator set up, all changes made in `config.toml` and `app.toml`, the Nym genesis file copied into place (replacing the initial auto-generated one). Now let's validate the whole setup: + +``` +nyxd validate-genesis +``` + +If this check passes, you should receive the following output: + +``` +File at /path/to/genesis.json is a valid genesis file +``` + +> If this test did not pass, check that you have replaced the contents of `//.nymd/config/genesis.json` with that of the correct genesis file. + +### Open firewall ports + +Before starting the validator, we will need to open the firewall ports: + +``` +# if ufw is not already installed: +sudo apt install ufw +sudo ufw enable +sudo ufw allow 1317,26656,26660,22,80,443/tcp +# to check everything worked +sudo ufw status +``` + +Ports `22`, `80`, and `443` are for ssh, http, and https connections respectively. The rest of the ports are documented [here](https://docs.cosmos.network/main/core/grpc_rest). + +For more information about your validator's port configuration, check the [validator port reference table](https://nymtech.net/operators/binaries/maintenance.html#ports) below. + +> If you are planning to use [Cockpit](https://cockpit-project.org/) on your validator server then you will have defined a different `grpc` port in your `config.toml` above: remember to open this port as well. + +Start the validator: + +``` +nyxd start +``` + +Once your validator starts, it will start requesting blocks from other validators. This may take several hours. Once it's up to date, you can issue a request to join the validator set with the command below. + +### Syncing from a snapshot +If you wish to sync from a snapshot on **mainnet** use Polkachu's [mainnet](https://polkachu.com/networks/nym) resources. + +If you wish to sync from a snapshot on **Sandbox testnet** use the below commands, which are a modified version of Polkachu's excellent resources. These commands assume you are running an OS with `apt` as the package manager: + +``` +# install lz4 if necessary +sudo apt install snapd -y +sudo snap install lz4 + +# download the snapshot +wget -O nyxd-sandbox-snapshot-data.tar.lz4 https://sandbox-validator1.nymtech.net/snapshots/nyxd-sandbox-snapshot-data.tar.lz4 + +# reset your validator state +nyxd tendermint unsafe-reset-all + +# unpack the snapshot +lz4 -c -d nyxd-sandbox-snapshot-data.tar.lz4 | tar -x -C $HOME/.nyxd +``` + +You can then restart `nyxd` - it should start syncing from a block > 2000000. + +### Joining Consensus +```admonish caution title="" +When joining consensus, make sure that you do not disrupt (or worse - halt) the network by coming in with a disproportionately large amount of staked tokens. + +Please initially stake a small amount of tokens compared to existing validators, then delegate to yourself in tranches over time. +``` + +Once your validator has synced and you have received tokens, you can join consensus and produce blocks. + +``` +# Mainnet +nyxd tx staking create-validator + --amount=10000000unyx + --fees=0unyx + --pubkey=$(/home///nyxd/binaries/nyxd tendermint show-validator) + --moniker="" + --chain-id=nyx + --commission-rate="0.10" + --commission-max-rate="0.20" + --commission-max-change-rate="0.01" + --min-self-delegation="1" + --gas="auto" + --gas-adjustment=1.15 + --from="KEYRING_NAME" + --node https://rpc-1.nyx.nodes.guru:443 +``` +``` +# Sandbox Testnet +nyxd tx staking create-validator + --amount=10000000unyxt + --fees=5000unyxt + --pubkey=$(/home///nym/binaries/nyxd tendermint show-validator) + --moniker="" + --chain-id=sandbox + --commission-rate="0.10" + --commission-max-rate="0.20" + --commission-max-change-rate="0.01" + --min-self-delegation="1" + --gas="auto" + --gas-adjustment=1.15 + --from="KEYRING_NAME" + --node https://sandbox-validator1.nymtech.net:443 +``` + +You'll need either `unyxt` tokens on Sandbox, or `unyx` tokens on mainnet to perform this command. + +> We are currently working towards building up a closed set of reputable validators. You can ask us for coins to get in, but please don't be offended if we say no - validators are part of our system's core security and we are starting out with people we already know or who have a solid reputation. + +If you want to edit some details for your node you will use a command like this: + +``` +# Mainnet +nyxd tx staking edit-validator + --chain-id=nyx + --moniker="" + --details="Nyx validator" + --security-contact="" + --identity="" + --gas="auto" + --gas-adjustment=1.15 + --from="KEYRING_NAME" + --fees 2000unyx +``` +``` +# Sandbox testnet +nyxd tx staking edit-validator + --chain-id=sandbox + --moniker="" + --details="Sandbox testnet validator" + --security-contact="your email" + --identity="" + --gas="auto" + --gas-adjustment=1.15 + --from="KEYRING_NAME" + --fees 2000unyxt +``` + +With above command you can specify the `gpg` key last numbers (as used in `keybase`) as well as validator details and your email for security contact. + +### Automating your validator with systemd +You will most likely want to automate your validator restarting if your server reboots. Checkout the [maintenance page](https://nymtech.net/operators/binaries/maintenance.html#systemd) with a quick tutorial. + +### Installing and configuring nginx for HTTPS + +If you want to set up a reverse proxying on the validator server to improve security and performance, using [nginx](https://www.nginx.com/resources/glossary/nginx/#:~:text=NGINX%20is%20open%20source%20software,%2C%20media%20streaming%2C%20and%20more.&text=In%20addition%20to%20its%20HTTP,%2C%20TCP%2C%20and%20UDP%20servers.), follow the manual on the [maintenance page](https://nymtech.net/operators/binaries/maintenance.html#setup). + +### Setting the ulimit + +Linux machines limit how many open files a user is allowed to have. This is called a `ulimit`. We need to set it to a higher value than the default 1024. Follow the instructions in the [maintenance page](https://nymtech.net/operators/binaries/maintenance.html#Setting-the-ulimit) to change the `ulimit` value for validators. + +## Using your validator +### Unjailing your validator +If your validator gets jailed, you can fix it with the following command: + +``` +# Mainnet +nyxd tx slashing unjail + --broadcast-mode=block + --from="KEYRING_NAME" + --chain-id=nyx + --gas=auto + --gas-adjustment=1.4 + --fees=7000unyx +``` +``` +# Sandbox Testnet +nyxd tx slashing unjail + --broadcast-mode=block + --from="KEYRING_NAME" + --chain-id=sandbox + --gas=auto + --gas-adjustment=1.4 + --fees=7000unyxt +``` + +### Upgrading your validator + +To upgrade your validator, follow the steps on the [maintenance page](https://nymtech.net/operators/binaries/maintenance.html#Setting-the-ulimit). + +#### Common reasons for your validator being jailed + +The most common reason for your validator being jailed is that your validator is out of memory because of bloated syslogs. + +Running the command `df -H` will return the size of the various partitions of your VPS. + +If the `/dev/sda` partition is almost full, try pruning some of the `.gz` syslog archives and restart your validator process. + +### Day 2 operations with your validator + +You can check your current balances with: + +``` +nymd query bank balances ${ADDRESS} +``` + +For example, on the Sanbox testnet this would return: + +```yaml +balances: +- amount: "919376" +denom: unymt +pagination: +next_key: null +total: "0" +``` + +You can, of course, stake back the available balance to your validator with the following command. + +> Remember to save some tokens for gas costs! + +``` +# Mainnet +nyxd tx staking delegate VALOPERADDRESS AMOUNTunym + --from="KEYRING_NAME" + --keyring-backend=os + --chain-id=nyx + --gas="auto" + --gas-adjustment=1.15 + --fees 5000unyx +``` +``` +# Sandbox Testnet +nyxd tx staking delegate VALOPERADDRESS AMOUNTunymt + --from="KEYRING_NAME" + --keyring-backend=os + --chain-id=sandbox + --gas="auto" + --gas-adjustment=1.15 + --fees 5000unyxt +``` + From d8925fe234f8d24a6352a14340dda6e993369e08 Mon Sep 17 00:00:00 2001 From: mfahampshire Date: Fri, 28 Jul 2023 14:30:01 +0200 Subject: [PATCH 024/361] * started on cosmos tutorial * edited summary accordingly * edited links in other pages for new ts tutorial structure * removed ipfs coming soon page --- documentation/dev-portal/src/SUMMARY.md | 7 +- .../src/infrastructure/node-types.md | 7 +- .../src/integrations/mixnet-integration.md | 197 +++++++++--------- .../src/tutorials/cosmos-service/intro.md | 4 + .../src/tutorials/cosmos-service/overview.md | 57 +++++ .../tutorials/cosmos-service/preparing-env.md | 21 ++ .../src/tutorials/ipfs-service-provider.md | 1 - .../simple-service-provider.md | 0 8 files changed, 188 insertions(+), 106 deletions(-) create mode 100644 documentation/dev-portal/src/tutorials/cosmos-service/intro.md create mode 100644 documentation/dev-portal/src/tutorials/cosmos-service/overview.md create mode 100644 documentation/dev-portal/src/tutorials/cosmos-service/preparing-env.md delete mode 100644 documentation/dev-portal/src/tutorials/ipfs-service-provider.md rename documentation/dev-portal/src/tutorials/{ => simple-service-provider}/simple-service-provider.md (100%) diff --git a/documentation/dev-portal/src/SUMMARY.md b/documentation/dev-portal/src/SUMMARY.md index aeae804662..77fbee86b0 100644 --- a/documentation/dev-portal/src/SUMMARY.md +++ b/documentation/dev-portal/src/SUMMARY.md @@ -28,14 +28,17 @@ # Tutorials -- [Simple Service Provider](tutorials/simple-service-provider.md) +- [(Rust SDK) Cosmos Blockchain Service](tutorials/cosmos-service/intro.md) + - [Tutorial Overview](tutorials/cosmos-service/overview.md) + - [Preparing Your Environment](tutorials/cosmos-service/preparing-env.md) +- [(Typescript) Simple Service Provider](tutorials/simple-service-provider/simple-service-provider.md) - [Tutorial Overview](tutorials/simple-service-provider/overview.md) - [Preparing Your User Client Environment](tutorials/simple-service-provider/preparating-env.md) - [Building Your User Client](tutorials/simple-service-provider/user-client.md) - [Preparing Your Service Provider Environment](tutorials/simple-service-provider/preparating-env2.md) - [Building Your Service Provider](tutorials/simple-service-provider/service-provider.md) - [Sending a Message Through the Mixnet](tutorials/simple-service-provider/sending-message.md) -- [IPFS Service Provider (coming soon)](tutorials/ipfs-service-provider.md) + # Community Resources diff --git a/documentation/dev-portal/src/infrastructure/node-types.md b/documentation/dev-portal/src/infrastructure/node-types.md index 464dedb80f..589b9ca576 100644 --- a/documentation/dev-portal/src/infrastructure/node-types.md +++ b/documentation/dev-portal/src/infrastructure/node-types.md @@ -36,12 +36,11 @@ For example, a Service Provider could receive a request to check a mail server a Maybe you would like to concentrate on building a application that uses the mixnet: -* Explore the Tutorials section of the Developer Portal. Our in-depth tutorial on [Building a Simple Service Provider](../tutorials/simple-service-provider.md) give a good understanding of building User Clients and Service Providers in TypeScript, and how to configure Nym Websocket Clients for seamless communication with the mixnet. +* Explore the Tutorials section of the Developer Portal. Our in-depth tutorial on [Building a Simple Service Provider](../tutorials/simple-service-provider/simple-service-provider.md) give a good understanding of building User Clients and Service Providers in TypeScript, and how to configure Nym Websocket Clients for seamless communication with the mixnet. * Get started with using the Nym Mixnet quickly and easily by exploring the [Quickstart](../quickstart/overview.md) options, such a NymConnect, proxying traffic through the Nym Socks5 client, or dive into integrating Nym into your existing application with the [Integrations](../integrations/integration-options.md) section. -Or perhaps you a developer that would like to run a infrastructure node such as a Gateway, Mix node or Network Requestor: -* Check out the [Network Overview](https://nymtech.net/docs/architecture/network-overview.html) docs page. +Or perhaps you a developer that would like to run a infrastructure node such as a Gateway, Mix node or Network Requestor: +* Check out the [Network Overview](https://nymtech.net/docs/architecture/network-overview.html) docs page. * Take a look at our [Node Setup Guide](https://nymtech.net/docs/nodes/setup-guides.html) with our Nym Docs, containing setup guides for setting up you own infrastructure node. - diff --git a/documentation/dev-portal/src/integrations/mixnet-integration.md b/documentation/dev-portal/src/integrations/mixnet-integration.md index e4a328bf24..9df20518bd 100644 --- a/documentation/dev-portal/src/integrations/mixnet-integration.md +++ b/documentation/dev-portal/src/integrations/mixnet-integration.md @@ -1,19 +1,19 @@ # Integrating with Nym for network privacy -If you are wanting to integrate Nym by using the Mixnet as a transport layer for application traffic, you will have to run one of the three Nym clients in order to connect to the Mixnet. +If you are wanting to integrate Nym by using the Mixnet as a transport layer for application traffic, you will have to run one of the three Nym clients in order to connect to the Mixnet. ## Connecting applications to the mixnet -### SDK support +### SDK support If your app is written in Typescript or Rust, then you can use the [Typescript](https://nymtech.net/docs/sdk/typescript.html) or [Rust](https://nymtech.net/docs/sdk/rust.html) SDKs. These SDKs abstract away much of the messaging logic from your app, and allow you to run a Nym client as part of your application process, instead of having to run them seperately. ### Choosing a client -In order to connect your application to the mixnet, you need to select one of three clients to use. These clients do the majority of the heavy-lifting with regards to cryptographic operations and routing under the hood, and all do basically the same thing: create a connection to a gateway, encrypt and decrypt packets sent to and received from the mixnet, and send cover traffic to hide the flow of actual app traffic from observers. +In order to connect your application to the mixnet, you need to select one of three clients to use. These clients do the majority of the heavy-lifting with regards to cryptographic operations and routing under the hood, and all do basically the same thing: create a connection to a gateway, encrypt and decrypt packets sent to and received from the mixnet, and send cover traffic to hide the flow of actual app traffic from observers. -As outlined in the [clients overview documentation](https://nymtech.net/docs/clients/overview.html) there are three clients availiable to developers to use when connecting applications to the mixnet: +As outlined in the [clients overview documentation](https://nymtech.net/docs/clients/overview.html) there are three clients availiable to developers to use when connecting applications to the mixnet: #### Websocket client -Your first option is the native websocket client. This is a compiled program that can run on Linux, Mac OS X, and Windows machines. It runs as a persistent process on a desktop or server machine. You can connect to it with any language that supports websockets. +Your first option is the native websocket client. This is a compiled program that can run on Linux, Mac OS X, and Windows machines. It runs as a persistent process on a desktop or server machine. You can connect to it with any language that supports websockets. -You can see an example of how to connect to and manage interactions with this client in the [Simple Service Provider tutorial](../tutorials/simple-service-provider.md). +You can see an example of how to connect to and manage interactions with this client in the [Simple Service Provider tutorial](../tutorials/simple-service-provider/simple-service-provider.md). #### Webassembly client If you’re working in JavaScript or Typescript in the browser, or building an edge computing app, you’ll likely want to choose the webassembly client. @@ -27,16 +27,16 @@ You can find example code in the [examples section](https://github.com/nymtech/n #### SOCKS client This client is useful for allowing existing applications to use the Nym mixnet without any code changes. All that’s necessary is that they can use one of the SOCKS5, SOCKS4a, or SOCKS4 proxy protocols (which many applications can - crypto wallets, browsers, chat applications etc). -It’s less flexible as a way of writing custom applications than the other clients, but able to be used to proxy application traffic through the mixnet without having to make any code changes. +It’s less flexible as a way of writing custom applications than the other clients, but able to be used to proxy application traffic through the mixnet without having to make any code changes. -You can find examples of how to utilise this client in the [Quickstart](../quickstart/socks-proxy.md) section, and the [SOCKS5 documentation](https://nymtech.net/docs/clients/socks5-client.html). +You can find examples of how to utilise this client in the [Quickstart](../quickstart/socks-proxy.md) section, and the [SOCKS5 documentation](https://nymtech.net/docs/clients/socks5-client.html). -## Recommended infrastructure setup -In order to ensure uptime and reliability, it is recommended that you run some pieces of mixnet infrastructure. What infrastructure is necessary to run depends on the architecture of your application, and the endpoints that it needs to hit! +## Recommended infrastructure setup +In order to ensure uptime and reliability, it is recommended that you run some pieces of mixnet infrastructure. What infrastructure is necessary to run depends on the architecture of your application, and the endpoints that it needs to hit! -* If you're running a purely P2P application, then just integrating clients and having some method of sharing addresses should be enough to route your traffic through the mixnet. -* If you're wanting to place the mixnet between your users' application instances and a server-based backend, you can use the [network requester](https://nymtech.net/docs/nodes/network-requester-setup.html) service provider binary to proxy these requests to your application backend, with the mixnet 'between' the user and your service, in order to prevent metadata leakage being broadcast to the internet. -* If you're wanting to route RPC requests through the mixnet to a blockchain, you will need to look into setting up some sort of service that does the transaction broadcasting for you. You can find examples of such projects on the [community applications](../community-resources/community-applications-and-guides.md) page. +* If you're running a purely P2P application, then just integrating clients and having some method of sharing addresses should be enough to route your traffic through the mixnet. +* If you're wanting to place the mixnet between your users' application instances and a server-based backend, you can use the [network requester](https://nymtech.net/docs/nodes/network-requester-setup.html) service provider binary to proxy these requests to your application backend, with the mixnet 'between' the user and your service, in order to prevent metadata leakage being broadcast to the internet. +* If you're wanting to route RPC requests through the mixnet to a blockchain, you will need to look into setting up some sort of service that does the transaction broadcasting for you. You can find examples of such projects on the [community applications](../community-resources/community-applications-and-guides.md) page. ## Example application traffic flow ### Initialization @@ -44,36 +44,36 @@ First, we need to initalise an app and connect it to Nym. ``` - +-----------+ - | Gateway | +-----------+ - ^ - | - | - | - | - | - | - +-------------------+ - | +---------------+ | - | | Nym client | | - | +---------------+ | - | ^ | - | | | - | | | - | | | - | v | - | +---------------+ | - | | Your app code | | - | +---------------+ | - +-------------------+ - Your Local Machine + | Gateway | + +-----------+ + ^ + | + | + | + | + | + | + +-------------------+ + | +---------------+ | + | | Nym client | | + | +---------------+ | + | ^ | + | | | + | | | + | | | + | v | + | +---------------+ | + | | Your app code | | + | +---------------+ | + +-------------------+ + Your Local Machine ``` At the bottom we have an app. It consists of two parts: -* your application specific logic -* your Nym client - either running as a standalone process, or as part of the process of your app code if you're using an SDK +* your application specific logic +* your Nym client - either running as a standalone process, or as part of the process of your app code if you're using an SDK Nym apps have a stable, potentially long-lasting relation to a gateway node. A client will register itself with a gateway, and get back an authentication token that it can then use to retrieve messages from the gateway later on. @@ -89,40 +89,40 @@ The Nym client part of the app accepts messages from your code and automatically The app has now connected to the Gateway, but we haven't sent a message to ourselves yet. Let's do that now. ``` - - +----------+ +----------+ +----------+ - | Mix Node |<-----------> | Mix Node |<----------->| Mix Node | - | Layer 1 | | Layer 2 | | Layer 3 | - +----------+ +----------+ +----------+ - ^ ^ - | | + + +----------+ +----------+ +----------+ + | Mix Node |<-----------> | Mix Node |<----------->| Mix Node | + | Layer 1 | | Layer 2 | | Layer 3 | + +----------+ +----------+ +----------+ + ^ ^ + | | |<--------------------------------------------------+ - | - v - +--------------+ - | Your gateway | - +--------------+ - ^ - | - | - v - +-------------------+ - | +---------------+ | - | | Nym client | | - | +---------------+ | - | ^ | - | | | - | | | - | v | - | +---------------+ | - | | Your app code | | - | +---------------+ | - +-------------------+ - Your Local Machine** + | + v + +--------------+ + | Your gateway | + +--------------+ + ^ + | + | + v + +-------------------+ + | +---------------+ | + | | Nym client | | + | +---------------+ | + | ^ | + | | | + | | | + | v | + | +---------------+ | + | | Your app code | | + | +---------------+ | + +-------------------+ + Your Local Machine** ** note that depending on the technical setup, the Nym client running on this machine may -be either a seperate process or embedded in the same process as the app code via one of our SDKs. +be either a seperate process or embedded in the same process as the app code via one of our SDKs. ``` Let's say your code code pokes a message `hello world` into the Nym client. The Nym client automatically wraps that message up into a layer encrypted Sphinx packet, adds some routing information and encryption, and sends it to its own gateway. The gateway strips the first layer of encryption, ending up with the address of the first mixnode it should forward to, and a Sphinx packet. @@ -139,38 +139,37 @@ Messages are end-to-end encrypted. Although the gateway knows our app's IP when The process for sending messages to other apps is exactly the same, you simply specify a different recipient address. Address discovery happens outside the Nym system: in the case of a Service Provider app, the service provider has presumably advertised its own address. If you're sending to a friend of yours, you'll need to get a hold of their address out of band, maybe through a private messaging app such as Signal. ``` - - +----------+ +----------+ +----------+ - | Mix Node |<-----------> | Mix Node |<----------->| Mix Node | - | Layer 1 | | Layer 2 | | Layer 3 | - +----------+ +----------+ +----------+ - ^ ^ - | | - | | - v v - +--------------+ +-----------------+ - | Your gateway | | Service gateway | - +--------------+ +-----------------+ - ^ ^ - | | - | | - v v - +-------------------+ +-------------------+ - | +---------------+ | | +---------------+ | - | | Nym client | | | | Nym Client | | - | +---------------+ | | +---------------+ | - | ^ | | ^ | - | | | | | | - | | | | | | - | v | | v | - | +---------------+ | | +---------------+ | - | | Your app code | | | | Service Code | | - | +---------------+ | | +---------------+ | - +-------------------+ +-------------------+ - Your Local Machine** Service Provider Machine** + + +----------+ +----------+ +----------+ + | Mix Node |<-----------> | Mix Node |<----------->| Mix Node | + | Layer 1 | | Layer 2 | | Layer 3 | + +----------+ +----------+ +----------+ + ^ ^ + | | + | | + v v + +--------------+ +-----------------+ + | Your gateway | | Service gateway | + +--------------+ +-----------------+ + ^ ^ + | | + | | + v v + +-------------------+ +-------------------+ + | +---------------+ | | +---------------+ | + | | Nym client | | | | Nym Client | | + | +---------------+ | | +---------------+ | + | ^ | | ^ | + | | | | | | + | | | | | | + | v | | v | + | +---------------+ | | +---------------+ | + | | Your app code | | | | Service Code | | + | +---------------+ | | +---------------+ | + +-------------------+ +-------------------+ + Your Local Machine** Service Provider Machine** ** note that depending on the technical setup, the Nym client running on these machines may -be either a seperate process or embedded in the same process as the app code via one of our SDKs. +be either a seperate process or embedded in the same process as the app code via one of our SDKs. ``` - diff --git a/documentation/dev-portal/src/tutorials/cosmos-service/intro.md b/documentation/dev-portal/src/tutorials/cosmos-service/intro.md new file mode 100644 index 0000000000..1b9e40096a --- /dev/null +++ b/documentation/dev-portal/src/tutorials/cosmos-service/intro.md @@ -0,0 +1,4 @@ +# Building a Cosmos Blockchain Service with the Rust SDK + +This tutorial is for Rust developers wanting to interact with the Rust SDK and take a first step at building a service with which to interact with a blockchain. +The key here is to think of the service as a proxy: it interacts with the blockchain _on the client's behalf_, shielding it from the Validator it interacts with, whilst also being shielded from the client by the mixnet! diff --git a/documentation/dev-portal/src/tutorials/cosmos-service/overview.md b/documentation/dev-portal/src/tutorials/cosmos-service/overview.md new file mode 100644 index 0000000000..a24a381662 --- /dev/null +++ b/documentation/dev-portal/src/tutorials/cosmos-service/overview.md @@ -0,0 +1,57 @@ +# Tutorial Overview + +This tutorial involves writing two pieces of code in Rust: + +- A client side binary used to construct a blockchain query and send this query to a service, which will interact with a Cosmos SDK blockchain on our behalf. +- A service which will listen out for requests from the mixnet, act on those requests, and anonymously reply to the client sending the requests. + +You will learn how to do the following with the Rust SDK: +- Create clients with manual storage settings. +- Parse incoming traffic from the mixnet and reply anonymously using [SURBs](). + +> Services usually run on remote servers to assure reliable uptime and to unlink sender and receiver metadata. For demonstration purposes however, you will run both components on your local machine, looping messages through the mixnet to yourself. + + +``` + +----------+ +----------+ +----------+ + | Mix Node |<-----------> | Mix Node |<----------->| Mix Node | + | Layer 1 | | Layer 2 | | Layer 3 | + +----------+ +----------+ +----------+ + ^ ^ + | | + |<--------------------------------------------------+ + | + v + +--------------+ + | Your gateway | + +--------------+ + ^ + | + | + v ++-------------------------------------------+ +| | +| +-------------+ +--------------+ | +| | Client code | | Service code | | +| +-------------+ +--------------+ | +| | ++-------------------------------------------+ + Your Local Machine +``` + +You can find the code for these components [here](). You can use it as a reference while building or simply download it and follow along as you progress through the tutorial. + +Notice that this tutorial attempts to use very few external libraries. This tutorial is not showing you how to build production-grade code, but **to understand how to connect and send messages to, as well as recieve messages from, the mixnet.** + +```admonish note title="Sidenote: What is a Service / Service Provider?" +'Service' or 'Service Provider' are catchall names used to refer to any type of app that can communicate with the mixnet via a Nym client - in this case, one embedded in your app process via the Rust SDK. + +The first SP to have been released is the [Network Requester](https://nymtech.net/docs/nodes/network-requester-setup.html) - a binary that receives a network request from the mixnet, performs that request (e.g. authenticating with a message server and receiving new messages for a user) and then passes the response back to the user who requested it anonymously, shielding their metadata from the message server. + +The SP you will build in this tutorial is far more simple than this, showing you how to approach building something that can: +* connect to the mixnet, +* listen for messages, and +* perform some action with them - in this case, query a Cosmos SDK blockchain. + +However, once you see how easy it is to integrate with the mixnet for traffic transport, you will be able to build apps with real-world uses easily. +``` diff --git a/documentation/dev-portal/src/tutorials/cosmos-service/preparing-env.md b/documentation/dev-portal/src/tutorials/cosmos-service/preparing-env.md new file mode 100644 index 0000000000..a763be73f3 --- /dev/null +++ b/documentation/dev-portal/src/tutorials/cosmos-service/preparing-env.md @@ -0,0 +1,21 @@ +# Preparing Your Environment + +## Prerequisites +* `Rust` & `cargo` + +## Creating your Project Structure + +* Make a new cargo project: +``` + +``` + +* Create the following directory structure and files: +``` + +``` + +* Finally add the following dependencies - you can just copy and paste this into your `Cargo.toml` file: +``` +TODO pull validator client code etc from the monorepo instead of workspace loading +``` diff --git a/documentation/dev-portal/src/tutorials/ipfs-service-provider.md b/documentation/dev-portal/src/tutorials/ipfs-service-provider.md deleted file mode 100644 index b51f035be4..0000000000 --- a/documentation/dev-portal/src/tutorials/ipfs-service-provider.md +++ /dev/null @@ -1 +0,0 @@ -# Building a Image Upload Service Provider with IPFS (coming soon) diff --git a/documentation/dev-portal/src/tutorials/simple-service-provider.md b/documentation/dev-portal/src/tutorials/simple-service-provider/simple-service-provider.md similarity index 100% rename from documentation/dev-portal/src/tutorials/simple-service-provider.md rename to documentation/dev-portal/src/tutorials/simple-service-provider/simple-service-provider.md From 7b71775e08ad30fb13454493135026b3f70db629 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jon=20H=C3=A4ggblad?= Date: Fri, 28 Jul 2023 14:48:33 +0200 Subject: [PATCH 025/361] Add geo-aware mixnet topology provider (#3713) * WIP: initial work * wupwup * WIP: experiments * Move topology provider and requests to own crate * Make sure we use the new crate everywhere * Sort Cargo.toml * Extract out some functions in geo_aware_provider * rustfmt * Add CountryGroup type * Assign unknown as well * wipwip * Add command line flag to socks5-client * Use geo-aware mixnode selection in nym-connect when in medium mode * rustfmt * clippy * Fix nym-connect build * wasm fix * Spelling --- Cargo.lock | 20 ++ Cargo.toml | 13 +- clients/socks5/src/commands/init.rs | 1 + clients/socks5/src/commands/mod.rs | 15 +- clients/socks5/src/commands/run.rs | 13 + clients/webassembly/Cargo.lock | 13 + clients/webassembly/src/client/config.rs | 1 + common/client-core/Cargo.toml | 10 +- .../client-core/src/client/base_client/mod.rs | 15 +- .../topology_control/geo_aware_provider.rs | 319 ++++++++++++++++++ .../src/client/topology_control/mod.rs | 1 + common/client-core/src/config/mod.rs | 22 +- .../src/config/old_config_v1_1_20_2.rs | 1 + .../mixnet-contract/src/mixnode.rs | 4 + common/socks5-client-core/Cargo.toml | 13 +- common/socks5-client-core/src/lib.rs | 1 + explorer-api/Cargo.toml | 17 +- explorer-api/explorer-api-requests/Cargo.toml | 12 + explorer-api/explorer-api-requests/src/lib.rs | 44 +++ .../src/country_statistics/geolocate.rs | 4 +- explorer-api/src/geo_ip/location.rs | 12 + explorer-api/src/mix_node/http.rs | 3 +- explorer-api/src/mix_node/mod.rs | 2 +- explorer-api/src/mix_node/models.rs | 38 +-- explorer-api/src/mix_nodes/http.rs | 2 +- explorer-api/src/mix_nodes/location.rs | 23 +- explorer-api/src/mix_nodes/models.rs | 4 +- nym-connect/desktop/Cargo.lock | 39 ++- nym-connect/desktop/src-tauri/src/tasks.rs | 26 +- .../src/allowed_hosts/hosts.rs | 1 - 30 files changed, 574 insertions(+), 115 deletions(-) create mode 100644 common/client-core/src/client/topology_control/geo_aware_provider.rs create mode 100644 explorer-api/explorer-api-requests/Cargo.toml create mode 100644 explorer-api/explorer-api-requests/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index d576e05c0c..f7ba594a47 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1907,6 +1907,7 @@ dependencies = [ "maxminddb", "nym-bin-common", "nym-contracts-common", + "nym-explorer-api-requests", "nym-mixnet-contract-common", "nym-network-defaults", "nym-task", @@ -3783,6 +3784,7 @@ dependencies = [ "nym-config", "nym-credential-storage", "nym-crypto", + "nym-explorer-api-requests", "nym-gateway-client", "nym-gateway-requests", "nym-network-defaults", @@ -3793,6 +3795,7 @@ dependencies = [ "nym-topology", "nym-validator-client", "rand 0.7.3", + "reqwest", "serde", "serde_json", "sha2 0.10.6", @@ -4005,6 +4008,18 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "nym-explorer-api-requests" +version = "0.1.0" +dependencies = [ + "nym-contracts-common", + "nym-mixnet-contract-common", + "nym-validator-client", + "schemars", + "serde", + "ts-rs", +] + [[package]] name = "nym-gateway" version = "1.1.23" @@ -4520,7 +4535,9 @@ dependencies = [ "nym-bandwidth-controller", "nym-client-core", "nym-config", + "nym-contracts-common", "nym-credential-storage", + "nym-mixnet-contract-common", "nym-network-defaults", "nym-service-providers-common", "nym-socks5-proxy-helpers", @@ -4530,10 +4547,13 @@ dependencies = [ "nym-validator-client", "pin-project", "rand 0.7.3", + "reqwest", + "schemars", "serde", "tap", "thiserror", "tokio", + "url", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index fd137c6bdf..2b2fad2698 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -74,6 +74,7 @@ members = [ "common/types", "common/wasm-utils", "explorer-api", + "explorer-api/explorer-api-requests", "gateway", "gateway/gateway-requests", "integrations/bity", @@ -117,31 +118,31 @@ anyhow = "1.0.71" async-trait = "0.1.64" bip39 = { version = "2.0.0", features = ["zeroize"] } cfg-if = "1.0.0" +cosmrs = "=0.14.0" cosmwasm-derive = "=1.2.5" cosmwasm-schema = "=1.2.5" cosmwasm-std = "=1.2.5" cosmwasm-storage = "=1.2.5" -cosmrs = "=0.14.0" -# same version as used by cosmrs -tendermint-rpc = "=0.32" -cw-utils = "=1.0.1" +cw-controllers = { version = "=1.0.1" } cw-storage-plus = "=1.0.1" +cw-utils = "=1.0.1" cw2 = { version = "=1.0.1" } cw3 = { version = "=1.0.1" } cw3-fixed-multisig = { version = "=1.0.1" } cw4 = { version = "=1.0.1" } -cw-controllers = { version = "=1.0.1" } dotenvy = "0.15.6" generic-array = "0.14.7" -k256 = "0.13" getrandom = "0.2.10" +k256 = "0.13" lazy_static = "1.4.0" log = "0.4" once_cell = "1.7.2" rand = "0.8.5" +reqwest = "0.11.18" serde = "1.0.152" serde_json = "1.0.91" tap = "1.0.1" +tendermint-rpc = "=0.32" # same version as used by cosmrs thiserror = "1.0.38" tokio = "1.24.1" url = "2.2" diff --git a/clients/socks5/src/commands/init.rs b/clients/socks5/src/commands/init.rs index 76337b84ac..ef370c54b8 100644 --- a/clients/socks5/src/commands/init.rs +++ b/clients/socks5/src/commands/init.rs @@ -94,6 +94,7 @@ impl From for OverrideConfig { use_anonymous_replies: init_config.use_reply_surbs, fastmode: init_config.fastmode, no_cover: init_config.no_cover, + geo_routing: None, medium_toggle: false, nyxd_urls: init_config.nyxd_urls, enabled_credentials_mode: init_config.enabled_credentials_mode, diff --git a/clients/socks5/src/commands/mod.rs b/clients/socks5/src/commands/mod.rs index 2b5fe425b6..12b4523330 100644 --- a/clients/socks5/src/commands/mod.rs +++ b/clients/socks5/src/commands/mod.rs @@ -16,7 +16,8 @@ use nym_client_core::client::base_client::storage::gateway_details::{ OnDiskGatewayDetails, PersistedGatewayDetails, }; use nym_client_core::client::key_manager::persistence::OnDiskKeys; -use nym_client_core::config::GatewayEndpointConfig; +use nym_client_core::client::topology_control::geo_aware_provider::CountryGroup; +use nym_client_core::config::{GatewayEndpointConfig, TopologyStructure}; use nym_client_core::error::ClientCoreError; use nym_config::OptionalSet; use nym_sphinx::params::{PacketSize, PacketType}; @@ -68,6 +69,7 @@ pub(crate) struct OverrideConfig { use_anonymous_replies: Option, fastmode: bool, no_cover: bool, + geo_routing: Option, medium_toggle: bool, nyxd_urls: Option>, enabled_credentials_mode: Option, @@ -91,6 +93,13 @@ 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)) + } else { + TopologyStructure::default() + }; + let packet_type = if args.outfox { PacketType::Outfox } else { @@ -114,6 +123,10 @@ pub(crate) fn override_config(config: Config, args: OverrideConfig) -> Config { // NOTE: see comment above about the order of the other disble cover traffic config .with_base(BaseClientConfig::with_disabled_cover_traffic, args.no_cover) .with_base(BaseClientConfig::with_packet_type, packet_type) + .with_base( + BaseClientConfig::with_topology_structure, + topology_structure, + ) .with_optional(Config::with_anonymous_replies, args.use_anonymous_replies) .with_optional(Config::with_port, args.port) .with_optional_base_custom_env( diff --git a/clients/socks5/src/commands/run.rs b/clients/socks5/src/commands/run.rs index 5d54038c06..8dd289e2bf 100644 --- a/clients/socks5/src/commands/run.rs +++ b/clients/socks5/src/commands/run.rs @@ -11,6 +11,7 @@ use clap::Args; use log::*; use nym_bin_common::version_checker::is_minor_version_compatible; use nym_client_core::client::base_client::storage::OnDiskPersistent; +use nym_client_core::client::topology_control::geo_aware_provider::CountryGroup; use nym_crypto::asymmetric::identity; use nym_socks5_client_core::NymClient; use nym_sphinx::addressing::clients::Recipient; @@ -60,6 +61,10 @@ pub(crate) struct Run { #[clap(long, hide = true)] no_cover: bool, + /// Set geo-aware mixnode selection when sending mixnet traffic, for experiments only. + #[clap(long, hide = true, value_parser = validate_country_group)] + geo_routing: Option, + /// Enable medium mixnet traffic, for experiments only. /// This includes things like disabling cover traffic, no per hop delays, etc. #[clap(long, hide = true)] @@ -82,6 +87,7 @@ impl From for OverrideConfig { use_anonymous_replies: run_config.use_anonymous_replies, fastmode: run_config.fastmode, no_cover: run_config.no_cover, + geo_routing: run_config.geo_routing, medium_toggle: run_config.medium_toggle, nyxd_urls: run_config.nyxd_urls, enabled_credentials_mode: run_config.enabled_credentials_mode, @@ -90,6 +96,13 @@ impl From for OverrideConfig { } } +fn validate_country_group(s: &str) -> Result { + match s.parse() { + Ok(cg) => Ok(cg), + Err(_) => Err(format!("failed to parse country group: {}", s)), + } +} + // this only checks compatibility between config the binary. It does not take into consideration // network version. It might do so in the future. fn version_check(cfg: &Config) -> bool { diff --git a/clients/webassembly/Cargo.lock b/clients/webassembly/Cargo.lock index f0733eac1b..c6410d881c 100644 --- a/clients/webassembly/Cargo.lock +++ b/clients/webassembly/Cargo.lock @@ -2477,6 +2477,7 @@ dependencies = [ "nym-config", "nym-credential-storage", "nym-crypto", + "nym-explorer-api-requests", "nym-gateway-client", "nym-gateway-requests", "nym-network-defaults", @@ -2487,6 +2488,7 @@ dependencies = [ "nym-topology", "nym-validator-client", "rand 0.7.3", + "reqwest", "serde", "serde_json", "sha2 0.10.6", @@ -2692,6 +2694,17 @@ dependencies = [ "zeroize", ] +[[package]] +name = "nym-explorer-api-requests" +version = "0.1.0" +dependencies = [ + "nym-contracts-common", + "nym-mixnet-contract-common", + "nym-validator-client", + "schemars", + "serde", +] + [[package]] name = "nym-gateway-client" version = "0.1.0" diff --git a/clients/webassembly/src/client/config.rs b/clients/webassembly/src/client/config.rs index 07d9ab5f5f..f63d1456ac 100644 --- a/clients/webassembly/src/client/config.rs +++ b/clients/webassembly/src/client/config.rs @@ -246,6 +246,7 @@ impl From for ConfigTopology { topology.topology_resolution_timeout_ms, ), disable_refreshing: topology.disable_refreshing, + topology_structure: Default::default(), } } } diff --git a/common/client-core/Cargo.toml b/common/client-core/Cargo.toml index d5ae32dbd2..e63d6e0ff7 100644 --- a/common/client-core/Cargo.toml +++ b/common/client-core/Cargo.toml @@ -10,27 +10,29 @@ rust-version = "1.66" [dependencies] async-trait = { workspace = true } base64 = "0.21.2" -dirs = "4.0" dashmap = "5.4.0" +dirs = "4.0" futures = "0.3" humantime-serde = "1.0" log = { workspace = true } rand = { version = "0.7.3", features = ["wasm-bindgen"] } +reqwest = { workspace = true } serde = { workspace = true, features = ["derive"] } serde_json = { workspace = true } sha2 = "0.10.6" tap = "1.0.1" thiserror = "1.0.34" -url = { version ="2.2", features = ["serde"] } -tungstenite = { version = "0.13.0", default-features = false } -tokio = { version = "1.24.1", features = ["macros"]} time = "0.3.17" +tokio = { version = "1.24.1", features = ["macros"]} +tungstenite = { version = "0.13.0", default-features = false } +url = { version ="2.2", features = ["serde"] } zeroize = { workspace = true } # internal 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-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" } diff --git a/common/client-core/src/client/base_client/mod.rs b/common/client-core/src/client/base_client/mod.rs index aecb7eb5c1..a934793953 100644 --- a/common/client-core/src/client/base_client/mod.rs +++ b/common/client-core/src/client/base_client/mod.rs @@ -2,6 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 use super::received_buffer::ReceivedBufferMessage; +use super::topology_control::geo_aware_provider::GeoAwareTopologyProvider; use crate::client::base_client::storage::MixnetClientStorage; use crate::client::cover_traffic_stream::LoopCoverTrafficStream; use crate::client::inbound_messages::{InputMessage, InputMessageReceiver, InputMessageSender}; @@ -339,14 +340,20 @@ where fn setup_topology_provider( custom_provider: Option>, + provider_from_config: config::TopologyStructure, nym_api_urls: Vec, ) -> Box { // if no custom provider was ... provided ..., create one using nym-api - custom_provider.unwrap_or_else(|| { - Box::new(NymApiTopologyProvider::new( + custom_provider.unwrap_or_else(|| match provider_from_config { + config::TopologyStructure::NymApi => Box::new(NymApiTopologyProvider::new( 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, + )), }) } @@ -521,8 +528,10 @@ where let topology_provider = Self::setup_topology_provider( self.custom_topology_provider.take(), + self.config.debug.topology.topology_structure, self.config.get_nym_api_endpoints(), ); + Self::start_topology_refresher( topology_provider, self.config.debug.topology, diff --git a/common/client-core/src/client/topology_control/geo_aware_provider.rs b/common/client-core/src/client/topology_control/geo_aware_provider.rs new file mode 100644 index 0000000000..268f6612e0 --- /dev/null +++ b/common/client-core/src/client/topology_control/geo_aware_provider.rs @@ -0,0 +1,319 @@ +use std::{collections::HashMap, fmt}; + +use log::{debug, error, info}; +use nym_explorer_api_requests::PrettyDetailedMixNodeBond; +use nym_topology::{ + nym_topology_from_detailed, + provider_trait::{async_trait, TopologyProvider}, + NymTopology, +}; +use nym_validator_client::client::MixId; +use rand::{prelude::SliceRandom, thread_rng}; +use serde::{Deserialize, Serialize}; +use url::Url; + +const MIN_NODES_PER_LAYER: usize = 1; +const EXPLORER_API_MIXNODES_URL: &str = "https://explorer.nymtech.net/api/v1/mix-nodes"; + +// TODO: create a explorer-api-client +async fn fetch_mixnodes_from_explorer_api() -> Option> { + reqwest::get(EXPLORER_API_MIXNODES_URL) + .await + .ok()? + .json::>() + .await + .ok() +} + +#[derive(Copy, Clone, Hash, PartialEq, Eq, Serialize, Deserialize, Debug)] +pub enum CountryGroup { + Europe, + NorthAmerica, + SouthAmerica, + Oceania, + Asia, + Africa, + Unknown, +} + +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. + fn new(country_code: &str) -> Self { + let country_code = country_code.to_uppercase(); + use CountryGroup::*; + match country_code.as_ref() { + // Europe + "AT" => Europe, + "BG" => Europe, + "CH" => Europe, + "CY" => Europe, + "CZ" => Europe, + "DE" => Europe, + "DK" => Europe, + "ES" => Europe, + "FI" => Europe, + "FR" => Europe, + "GB" => Europe, + "GR" => Europe, + "IE" => Europe, + "IT" => Europe, + "LT" => Europe, + "LU" => Europe, + "LV" => Europe, + "MD" => Europe, + "MT" => Europe, + "NL" => Europe, + "NO" => Europe, + "PL" => Europe, + "RO" => Europe, + "SE" => Europe, + "SK" => Europe, + "TR" => Europe, + "UA" => Europe, + + // North America + "CA" => NorthAmerica, + "MX" => NorthAmerica, + "US" => NorthAmerica, + + // South America + "AR" => SouthAmerica, + "BR" => SouthAmerica, + "CL" => SouthAmerica, + "CO" => SouthAmerica, + "CR" => SouthAmerica, + "GT" => SouthAmerica, + + // Oceania + "AU" => Oceania, + + // Asia + "AM" => Asia, + "BH" => Asia, + "CN" => Asia, + "GE" => Asia, + "HK" => Asia, + "ID" => Asia, + "IL" => Asia, + "IN" => Asia, + "JP" => Asia, + "KH" => Asia, + "KR" => Asia, + "KZ" => Asia, + "MY" => Asia, + "RU" => Asia, + "SG" => Asia, + "TH" => Asia, + "VN" => Asia, + + // Africa + "SC" => Africa, + "UG" => Africa, + "ZA" => Africa, + + // And group level codes work too + "EU" => Europe, + "NA" => NorthAmerica, + "SA" => SouthAmerica, + "OC" => Oceania, + "AS" => Asia, + "AF" => Africa, + + // And some aliases + "EUROPE" => Europe, + "NORTHAMERICA" => NorthAmerica, + "SOUTHAMERICA" => SouthAmerica, + "OCEANIA" => Oceania, + "ASIA" => Asia, + "AFRICA" => Africa, + + _ => { + info!("Unknown country code: {}", country_code); + Unknown + } + } + } +} + +impl fmt::Display for CountryGroup { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + use CountryGroup::*; + match self { + Europe => write!(f, "EU"), + NorthAmerica => write!(f, "NA"), + SouthAmerica => write!(f, "SA"), + Oceania => write!(f, "OC"), + Asia => write!(f, "AS"), + Africa => write!(f, "AF"), + Unknown => write!(f, "Unknown"), + } + } +} + +impl std::str::FromStr for CountryGroup { + type Err = (); + + fn from_str(s: &str) -> Result { + let group = CountryGroup::new(s); + if group == CountryGroup::Unknown { + Err(()) + } else { + Ok(group) + } + } +} + +impl CountryGroup { + #[allow(unused)] + fn known(self) -> Option { + use CountryGroup::*; + match self { + Europe | NorthAmerica | SouthAmerica | Oceania | Asia | Africa => Some(self), + Unknown => None, + } + } +} + +fn group_mixnodes_by_country_code( + mixnodes: Vec, +) -> HashMap> { + mixnodes + .into_iter() + .fold(HashMap::>::new(), |mut acc, m| { + if let Some(ref location) = m.location { + let country_code = location.two_letter_iso_country_code.clone(); + let group_code = CountryGroup::new(country_code.as_str()); + let mixnodes = acc.entry(group_code).or_insert_with(Vec::new); + mixnodes.push(m.mix_id); + } + acc + }) +} + +fn log_mixnode_distribution(mixnodes: &HashMap>) { + let mixnode_distribution = mixnodes + .iter() + .map(|(k, v)| format!("{}: {}", k, v.len())) + .collect::>() + .join(", "); + debug!("Mixnode distribution - {}", mixnode_distribution); +} + +fn check_layer_integrity(topology: NymTopology) -> Result<(), ()> { + let mixes = topology.mixes(); + if mixes.keys().len() < 3 { + error!("Layer is missing in topology!"); + return Err(()); + } + for (layer, mixnodes) in mixes { + debug!("Layer {:?} has {} mixnodes", layer, mixnodes.len()); + if mixnodes.len() < MIN_NODES_PER_LAYER { + error!( + "There are only {} mixnodes in layer {:?}", + mixnodes.len(), + layer + ); + return Err(()); + } + } + Ok(()) +} + +pub struct GeoAwareTopologyProvider { + validator_client: nym_validator_client::client::NymApiClient, + filter_on: CountryGroup, + client_version: String, +} + +impl GeoAwareTopologyProvider { + pub fn new( + mut nym_api_urls: Vec, + client_version: String, + filter_on: CountryGroup, + ) -> GeoAwareTopologyProvider { + log::info!( + "Creating geo-aware topology provider with filter on {:?}", + filter_on + ); + nym_api_urls.shuffle(&mut thread_rng()); + + GeoAwareTopologyProvider { + validator_client: nym_validator_client::client::NymApiClient::new( + nym_api_urls[0].clone(), + ), + filter_on, + client_version, + } + } + + async fn get_topology(&self) -> Option { + let mixnodes = match self.validator_client.get_cached_active_mixnodes().await { + Err(err) => { + error!("failed to get network mixnodes - {err}"); + return None; + } + Ok(mixes) => mixes, + }; + + let gateways = match self.validator_client.get_cached_gateways().await { + Err(err) => { + error!("failed to get network gateways - {err}"); + return None; + } + Ok(gateways) => gateways, + }; + + // 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 { + error!("failed to get mixnodes from explorer-api"); + return None; + }; + + // 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. + // The reason we this instead of a straight filter is that this opens up the possibility to + // complement a small grouping with mixnodes from adjecent countries. + 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); + return None; + }; + + let mixnodes = mixnodes + .into_iter() + .filter(|m| filtered_mixnode_ids.contains(&m.mix_id())) + .collect::>(); + + let topology = nym_topology_from_detailed(mixnodes, gateways) + .filter_system_version(&self.client_version); + + // TODO: return real error type + check_layer_integrity(topology.clone()).ok()?; + + Some(topology) + } +} + +#[cfg(not(target_arch = "wasm32"))] +#[async_trait] +impl TopologyProvider for GeoAwareTopologyProvider { + // this will be manually refreshed on a timer specified inside mixnet client config + async fn get_new_topology(&mut self) -> Option { + self.get_topology().await + } +} + +#[cfg(target_arch = "wasm32")] +#[async_trait(?Send)] +impl TopologyProvider for GeoAwareTopologyProvider { + // this will be manually refreshed on a timer specified inside mixnet client config + async fn get_new_topology(&mut self) -> Option { + self.get_topology().await + } +} diff --git a/common/client-core/src/client/topology_control/mod.rs b/common/client-core/src/client/topology_control/mod.rs index 0fef523b1e..ed426dde50 100644 --- a/common/client-core/src/client/topology_control/mod.rs +++ b/common/client-core/src/client/topology_control/mod.rs @@ -10,6 +10,7 @@ use nym_topology::NymTopologyError; use std::time::Duration; mod accessor; +pub mod geo_aware_provider; pub(crate) mod nym_api_provider; // TODO: move it to config later diff --git a/common/client-core/src/config/mod.rs b/common/client-core/src/config/mod.rs index e8f08adfaa..465c227e19 100644 --- a/common/client-core/src/config/mod.rs +++ b/common/client-core/src/config/mod.rs @@ -8,7 +8,7 @@ use serde::{Deserialize, Serialize}; use std::time::Duration; use url::Url; -use crate::error::ClientCoreError; +use crate::{client::topology_control::geo_aware_provider::CountryGroup, error::ClientCoreError}; #[cfg(target_arch = "wasm32")] use wasm_bindgen::prelude::*; @@ -158,6 +158,15 @@ impl Config { self } + pub fn with_topology_structure(mut self, topology_structure: TopologyStructure) -> Self { + self.set_topology_structure(topology_structure); + self + } + + pub fn set_topology_structure(&mut self, topology_structure: TopologyStructure) { + self.debug.topology.topology_structure = topology_structure; + } + pub fn with_no_per_hop_delays(mut self, no_per_hop_delays: bool) -> Self { if no_per_hop_delays { self.set_no_per_hop_delays() @@ -466,6 +475,16 @@ pub struct Topology { /// the first valid instance. /// Supersedes `topology_refresh_rate_ms`. pub disable_refreshing: bool, + + /// Specifies the mixnode topology to be used for sending packets. + pub topology_structure: TopologyStructure, +} + +#[derive(Default, Copy, Clone, Debug, PartialEq, Serialize, Deserialize)] +pub enum TopologyStructure { + #[default] + NymApi, + GeoAware(CountryGroup), } impl Default for Topology { @@ -474,6 +493,7 @@ impl Default for Topology { topology_refresh_rate: DEFAULT_TOPOLOGY_REFRESH_RATE, topology_resolution_timeout: DEFAULT_TOPOLOGY_RESOLUTION_TIMEOUT, disable_refreshing: false, + topology_structure: TopologyStructure::default(), } } } diff --git a/common/client-core/src/config/old_config_v1_1_20_2.rs b/common/client-core/src/config/old_config_v1_1_20_2.rs index ed0cd0356a..a097b55f2a 100644 --- a/common/client-core/src/config/old_config_v1_1_20_2.rs +++ b/common/client-core/src/config/old_config_v1_1_20_2.rs @@ -267,6 +267,7 @@ impl From for Topology { topology_refresh_rate: value.topology_refresh_rate, topology_resolution_timeout: value.topology_resolution_timeout, disable_refreshing: value.disable_refreshing, + topology_structure: Default::default(), } } } diff --git a/common/cosmwasm-smart-contracts/mixnet-contract/src/mixnode.rs b/common/cosmwasm-smart-contracts/mixnet-contract/src/mixnode.rs index 69f1a89209..c29077c3b6 100644 --- a/common/cosmwasm-smart-contracts/mixnet-contract/src/mixnode.rs +++ b/common/cosmwasm-smart-contracts/mixnet-contract/src/mixnode.rs @@ -58,6 +58,10 @@ impl MixNodeDetails { self.bond_information.mix_id } + pub fn layer(&self) -> Layer { + self.bond_information.layer + } + pub fn is_unbonding(&self) -> bool { self.bond_information.is_unbonding } diff --git a/common/socks5-client-core/Cargo.toml b/common/socks5-client-core/Cargo.toml index 9ca792f8b1..2e1aa52b32 100644 --- a/common/socks5-client-core/Cargo.toml +++ b/common/socks5-client-core/Cargo.toml @@ -7,22 +7,27 @@ edition = "2021" [dependencies] dirs = "4.0" +futures = "0.3" log = { workspace = true } pin-project = "1.0" rand = { version = "0.7.3", features = ["wasm-bindgen"] } +reqwest = "0.11.4" +schemars = { version = "0.8", features = ["preserve_order"] } serde = { workspace = true, features = ["derive"] } # for config serialization/deserialization -thiserror = "1.0.34" tap = "1.0.1" +thiserror = "1.0.34" tokio = { version = "1.24.1", features = ["rt-multi-thread", "net", "signal"] } -futures = "0.3" +url = "2.2" -nym-client-core = { path = "../client-core", features = ["fs-surb-storage"] } nym-bandwidth-controller = { path = "../../common/bandwidth-controller" } +nym-client-core = { path = "../client-core", features = ["fs-surb-storage"] } nym-config = { path = "../config" } +nym-contracts-common = { path = "../cosmwasm-smart-contracts/contracts-common" } nym-credential-storage = { path = "../credential-storage" } +nym-mixnet-contract-common = { path = "../cosmwasm-smart-contracts/mixnet-contract" } nym-network-defaults = { path = "../network-defaults" } -nym-socks5-proxy-helpers = { path = "../socks5/proxy-helpers" } nym-service-providers-common = { path = "../../service-providers/common" } +nym-socks5-proxy-helpers = { path = "../socks5/proxy-helpers" } nym-socks5-requests = { path = "../socks5/requests" } nym-sphinx = { path = "../nymsphinx" } nym-task = { path = "../task" } diff --git a/common/socks5-client-core/src/lib.rs b/common/socks5-client-core/src/lib.rs index 2070eeaf71..74d27b69ed 100644 --- a/common/socks5-client-core/src/lib.rs +++ b/common/socks5-client-core/src/lib.rs @@ -24,6 +24,7 @@ use nym_credential_storage::storage::Storage as CredentialStorage; use nym_sphinx::addressing::clients::Recipient; use nym_sphinx::params::PacketType; use nym_task::{TaskClient, TaskManager}; + use std::error::Error; pub mod config; diff --git a/explorer-api/Cargo.toml b/explorer-api/Cargo.toml index 5e1dec9f48..c4c7b345bc 100644 --- a/explorer-api/Cargo.toml +++ b/explorer-api/Cargo.toml @@ -8,12 +8,17 @@ edition = "2021" [dependencies] chrono = { version = "0.4.19", features = ["serde"] } clap = { version = "4.0", features = ["cargo", "derive"] } +dotenvy = "0.15.6" humantime-serde = "1.0" isocountry = "0.3.2" itertools = "0.10.3" log = { workspace = true } +maxminddb = "0.23.0" okapi = { version = "0.7.0-rc.1", features = ["impl_json_schema"] } pretty_env_logger = "0.4.0" +rand = "0.8.5" +rand_pcg = "0.3.1" +rand_seeder = "0.2.3" reqwest = "0.11.4" rocket = { version = "0.5.0-rc.2", features = ["json"] } rocket_cors = { git="https://github.com/lawliet89/rocket_cors", rev="dfd3662c49e2f6fc37df35091cb94d82f7fb5915" } @@ -23,15 +28,11 @@ serde = "1.0.126" serde_json = "1.0.66" thiserror = "1.0.29" tokio = {version = "1.21.2", features = ["full"] } -maxminddb = "0.23.0" -dotenvy = "0.15.6" -rand = "0.8.5" -rand_seeder = "0.2.3" -rand_pcg = "0.3.1" -nym-mixnet-contract-common = { path = "../common/cosmwasm-smart-contracts/mixnet-contract" } -nym-contracts-common = { path = "../common/cosmwasm-smart-contracts/contracts-common" } -nym-network-defaults = { path = "../common/network-defaults" } nym-bin-common = { path = "../common/bin-common"} +nym-contracts-common = { path = "../common/cosmwasm-smart-contracts/contracts-common" } +nym-explorer-api-requests = { path = "explorer-api-requests" } +nym-mixnet-contract-common = { path = "../common/cosmwasm-smart-contracts/mixnet-contract" } +nym-network-defaults = { path = "../common/network-defaults" } nym-task = { path = "../common/task" } nym-validator-client = { path = "../common/client-libs/validator-client", features=["http-client"] } diff --git a/explorer-api/explorer-api-requests/Cargo.toml b/explorer-api/explorer-api-requests/Cargo.toml new file mode 100644 index 0000000000..93c8faaada --- /dev/null +++ b/explorer-api/explorer-api-requests/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "nym-explorer-api-requests" +version = "0.1.0" +edition = "2021" + +[dependencies] +nym-contracts-common = { path = "../../common/cosmwasm-smart-contracts/contracts-common" } +nym-mixnet-contract-common = { path = "../../common/cosmwasm-smart-contracts/mixnet-contract" } +nym-validator-client = { path = "../../common/client-libs/validator-client" } +schemars = { version = "0.8", features = ["preserve_order"] } +serde = { version = "1.0", features = ["derive"] } +ts-rs = { version = "6.1.2", optional = true } diff --git a/explorer-api/explorer-api-requests/src/lib.rs b/explorer-api/explorer-api-requests/src/lib.rs new file mode 100644 index 0000000000..bfcafa2860 --- /dev/null +++ b/explorer-api/explorer-api-requests/src/lib.rs @@ -0,0 +1,44 @@ +use nym_contracts_common::Percent; +use nym_mixnet_contract_common::{Addr, Coin, Layer, MixId, MixNode}; +use nym_validator_client::models::NodePerformance; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq)] +#[serde(rename_all = "snake_case")] +pub enum MixnodeStatus { + Active, // in both the active set and the rewarded set + Standby, // only in the rewarded set + Inactive, // in neither the rewarded set nor the active set +} + +#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)] +pub struct PrettyDetailedMixNodeBond { + pub mix_id: MixId, + pub location: Option, + pub status: MixnodeStatus, + pub pledge_amount: Coin, + pub total_delegation: Coin, + pub owner: Addr, + pub layer: Layer, + pub mix_node: MixNode, + pub stake_saturation: f32, + pub uncapped_saturation: f32, + pub avg_uptime: u8, + pub node_performance: NodePerformance, + pub estimated_operator_apy: f64, + pub estimated_delegators_apy: f64, + pub operating_cost: Coin, + pub profit_margin_percent: Percent, + pub family_id: Option, + pub blacklisted: bool, +} + +#[derive(Clone, Debug, JsonSchema, Serialize, Deserialize)] +pub struct Location { + pub two_letter_iso_country_code: String, + pub three_letter_iso_country_code: String, + pub country_name: String, + pub latitude: Option, + pub longitude: Option, +} diff --git a/explorer-api/src/country_statistics/geolocate.rs b/explorer-api/src/country_statistics/geolocate.rs index 8158c6e65e..a80ebc28e4 100644 --- a/explorer-api/src/country_statistics/geolocate.rs +++ b/explorer-api/src/country_statistics/geolocate.rs @@ -1,9 +1,9 @@ // Copyright 2022 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use crate::mix_nodes::location::Location; use crate::state::ExplorerApiStateContext; use log::{info, warn}; +use nym_explorer_api_requests::Location; use nym_task::TaskClient; pub(crate) struct GeoLocateTask { @@ -64,7 +64,7 @@ impl GeoLocateTask { ) { Ok(opt) => match opt { Some(location) => { - let location = Location::new(location); + let location: Location = location.into(); trace!( "{} mix nodes already located. Ip {} is located in {:#?}", diff --git a/explorer-api/src/geo_ip/location.rs b/explorer-api/src/geo_ip/location.rs index adbdb6cef3..fdb8584938 100644 --- a/explorer-api/src/geo_ip/location.rs +++ b/explorer-api/src/geo_ip/location.rs @@ -42,6 +42,18 @@ pub(crate) struct Location { pub(crate) longitude: Option, } +impl From for nym_explorer_api_requests::Location { + fn from(location: Location) -> Self { + nym_explorer_api_requests::Location { + country_name: location.name, + two_letter_iso_country_code: location.iso_alpha2, + three_letter_iso_country_code: location.iso_alpha3, + latitude: location.latitude, + longitude: location.longitude, + } + } +} + impl GeoIp { pub fn new() -> Self { let db_path = std::env::var("GEOIP_DB_PATH").unwrap_or_else(|e| { diff --git a/explorer-api/src/mix_node/http.rs b/explorer-api/src/mix_node/http.rs index 137c6250ed..e00ed8ff82 100644 --- a/explorer-api/src/mix_node/http.rs +++ b/explorer-api/src/mix_node/http.rs @@ -6,9 +6,10 @@ use crate::mix_node::delegations::{ }; use crate::mix_node::econ_stats::retrieve_mixnode_econ_stats; use crate::mix_node::models::{ - EconomicDynamicsStats, NodeDescription, NodeStats, PrettyDetailedMixNodeBond, SummedDelegations, + EconomicDynamicsStats, NodeDescription, NodeStats, SummedDelegations, }; use crate::state::ExplorerApiStateContext; +use nym_explorer_api_requests::PrettyDetailedMixNodeBond; use nym_mixnet_contract_common::{Delegation, MixId}; use reqwest::Error as ReqwestError; use rocket::response::status::NotFound; diff --git a/explorer-api/src/mix_node/mod.rs b/explorer-api/src/mix_node/mod.rs index 88c3b1cd71..dac365c71d 100644 --- a/explorer-api/src/mix_node/mod.rs +++ b/explorer-api/src/mix_node/mod.rs @@ -1,4 +1,4 @@ pub(crate) mod delegations; pub(crate) mod econ_stats; pub(crate) mod http; -pub(crate) mod models; +pub mod models; diff --git a/explorer-api/src/mix_node/models.rs b/explorer-api/src/mix_node/models.rs index 94ae6558d0..4adfa6dd86 100644 --- a/explorer-api/src/mix_node/models.rs +++ b/explorer-api/src/mix_node/models.rs @@ -2,49 +2,15 @@ // SPDX-License-Identifier: Apache-2.0 use crate::cache::Cache; -use crate::mix_nodes::location::Location; -use nym_contracts_common::Percent; use nym_mixnet_contract_common::Delegation; -use nym_mixnet_contract_common::{Addr, Coin, Layer, MixId, MixNode}; -use nym_validator_client::models::{NodePerformance, SelectionChance}; +use nym_mixnet_contract_common::{Addr, Coin, MixId}; +use nym_validator_client::models::SelectionChance; use serde::Deserialize; use serde::Serialize; use std::sync::Arc; use std::time::SystemTime; use tokio::sync::RwLock; -#[derive(Clone, Debug, Serialize, JsonSchema, PartialEq)] -#[serde(rename_all = "snake_case")] -pub(crate) enum MixnodeStatus { - Active, // in both the active set and the rewarded set - Standby, // only in the rewarded set - Inactive, // in neither the rewarded set nor the active set -} - -#[derive(Clone, Debug, Serialize, JsonSchema)] -pub(crate) struct PrettyDetailedMixNodeBond { - // I leave this to @MS to refactor this type as a lot of things here are redundant thanks to - // the existence of `MixNodeDetails` - pub mix_id: MixId, - pub location: Option, - pub status: MixnodeStatus, - pub pledge_amount: Coin, - pub total_delegation: Coin, - pub owner: Addr, - pub layer: Layer, - pub mix_node: MixNode, - pub stake_saturation: f32, - pub uncapped_saturation: f32, - pub avg_uptime: u8, - pub node_performance: NodePerformance, - pub estimated_operator_apy: f64, - pub estimated_delegators_apy: f64, - pub operating_cost: Coin, - pub profit_margin_percent: Percent, - pub family_id: Option, - pub blacklisted: bool, -} - #[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq, JsonSchema)] pub struct SummedDelegations { pub owner: Addr, diff --git a/explorer-api/src/mix_nodes/http.rs b/explorer-api/src/mix_nodes/http.rs index ef5570e455..a36843b66f 100644 --- a/explorer-api/src/mix_nodes/http.rs +++ b/explorer-api/src/mix_nodes/http.rs @@ -1,6 +1,6 @@ -use crate::mix_node::models::{MixnodeStatus, PrettyDetailedMixNodeBond}; use crate::mix_nodes::models::{MixNodeActiveSetSummary, MixNodeSummary}; use crate::state::ExplorerApiStateContext; +use nym_explorer_api_requests::{MixnodeStatus, PrettyDetailedMixNodeBond}; use rocket::serde::json::Json; use rocket::{Route, State}; use rocket_okapi::okapi::openapi3::OpenApi; diff --git a/explorer-api/src/mix_nodes/location.rs b/explorer-api/src/mix_nodes/location.rs index b876cdc792..4908564697 100644 --- a/explorer-api/src/mix_nodes/location.rs +++ b/explorer-api/src/mix_nodes/location.rs @@ -1,7 +1,7 @@ // Copyright 2021 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use crate::geo_ip::location; +use nym_explorer_api_requests::Location; use nym_mixnet_contract_common::MixId; use serde::{Deserialize, Serialize}; use std::collections::HashMap; @@ -31,27 +31,6 @@ pub(crate) struct LocationCacheItem { pub(crate) valid_until: SystemTime, } -#[derive(Clone, Debug, JsonSchema, Serialize, Deserialize)] -pub(crate) struct Location { - pub(crate) two_letter_iso_country_code: String, - pub(crate) three_letter_iso_country_code: String, - pub(crate) country_name: String, - pub(crate) latitude: Option, - pub(crate) longitude: Option, -} - -impl Location { - pub(crate) fn new(location: location::Location) -> Self { - Location { - country_name: location.name, - two_letter_iso_country_code: location.iso_alpha2, - three_letter_iso_country_code: location.iso_alpha3, - latitude: location.latitude, - longitude: location.longitude, - } - } -} - impl LocationCacheItem { pub(crate) fn new_from_location(location: Option) -> Self { LocationCacheItem { diff --git a/explorer-api/src/mix_nodes/models.rs b/explorer-api/src/mix_nodes/models.rs index bfead42c8c..b107bb2168 100644 --- a/explorer-api/src/mix_nodes/models.rs +++ b/explorer-api/src/mix_nodes/models.rs @@ -5,6 +5,7 @@ use std::collections::{HashMap, HashSet}; use std::sync::Arc; use std::time::{Duration, SystemTime}; +use nym_explorer_api_requests::{Location, MixnodeStatus, PrettyDetailedMixNodeBond}; use nym_mixnet_contract_common::rewarding::helpers::truncate_reward; use nym_mixnet_contract_common::MixId; use serde::Serialize; @@ -14,8 +15,7 @@ use crate::helpers::best_effort_small_dec_to_f64; use nym_validator_client::models::MixNodeBondAnnotated; use super::utils::family_numerical_id; -use crate::mix_node::models::{MixnodeStatus, PrettyDetailedMixNodeBond}; -use crate::mix_nodes::location::{Location, LocationCache, LocationCacheItem}; +use crate::mix_nodes::location::{LocationCache, LocationCacheItem}; use crate::mix_nodes::CACHE_ENTRY_TTL; #[derive(Clone, Debug, Serialize, JsonSchema)] diff --git a/nym-connect/desktop/Cargo.lock b/nym-connect/desktop/Cargo.lock index 6287fadf38..3f657d5852 100644 --- a/nym-connect/desktop/Cargo.lock +++ b/nym-connect/desktop/Cargo.lock @@ -284,8 +284,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "93f2635620bf0b9d4576eb7bb9a38a55df78bd1205d26fa994b25911a69f212f" dependencies = [ "bitcoin_hashes", - "rand 0.7.3", - "rand_core 0.5.1", + "rand 0.8.5", + "rand_core 0.6.4", "serde", "unicode-normalization", "zeroize", @@ -2596,17 +2596,6 @@ dependencies = [ "winapi", ] -[[package]] -name = "hostname" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c731c3e10504cc8ed35cfe2f1db4c9274c3d35fa486e3b31df46f068ef3e867" -dependencies = [ - "libc", - "match_cfg", - "winapi", -] - [[package]] name = "html5ever" version = "0.25.2" @@ -3572,6 +3561,7 @@ dependencies = [ "nym-config", "nym-credential-storage", "nym-crypto", + "nym-explorer-api-requests", "nym-gateway-client", "nym-gateway-requests", "nym-network-defaults", @@ -3582,6 +3572,7 @@ dependencies = [ "nym-topology", "nym-validator-client", "rand 0.7.3", + "reqwest", "serde", "serde_json", "sha2 0.10.6", @@ -3669,7 +3660,7 @@ dependencies = [ [[package]] name = "nym-connect" -version = "1.1.13" +version = "1.1.15" dependencies = [ "anyhow", "bip39", @@ -3794,6 +3785,17 @@ dependencies = [ "zeroize", ] +[[package]] +name = "nym-explorer-api-requests" +version = "0.1.0" +dependencies = [ + "nym-contracts-common", + "nym-mixnet-contract-common", + "nym-validator-client", + "schemars", + "serde", +] + [[package]] name = "nym-gateway-client" version = "0.1.0" @@ -3995,7 +3997,9 @@ dependencies = [ "nym-bandwidth-controller", "nym-client-core", "nym-config", + "nym-contracts-common", "nym-credential-storage", + "nym-mixnet-contract-common", "nym-network-defaults", "nym-service-providers-common", "nym-socks5-proxy-helpers", @@ -4005,10 +4009,13 @@ dependencies = [ "nym-validator-client", "pin-project", "rand 0.7.3", + "reqwest", + "schemars", "serde", "tap", "thiserror", "tokio", + "url", ] [[package]] @@ -5155,9 +5162,9 @@ checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" [[package]] name = "reqwest" -version = "0.11.15" +version = "0.11.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ba30cc2c0cd02af1222ed216ba659cdb2f879dfe3181852fe7c50b1d0005949" +checksum = "cde824a14b7c14f85caff81225f411faacc04a2013f41670f41443742b1c1c55" dependencies = [ "base64 0.21.2", "bytes", diff --git a/nym-connect/desktop/src-tauri/src/tasks.rs b/nym-connect/desktop/src-tauri/src/tasks.rs index 8c69b1be7e..06b0809c2f 100644 --- a/nym-connect/desktop/src-tauri/src/tasks.rs +++ b/nym-connect/desktop/src-tauri/src/tasks.rs @@ -1,17 +1,23 @@ use futures::{channel::mpsc, StreamExt}; -use nym_client_core::client::base_client::storage::gateway_details::GatewayDetailsStore; -use nym_client_core::client::base_client::storage::{MixnetClientStorage, OnDiskPersistent}; -use nym_client_core::{config::GatewayEndpointConfig, error::ClientCoreStatusMessage}; -use nym_socks5_client_core::NymClient as Socks5NymClient; -use nym_socks5_client_core::Socks5ControlMessageSender; +use nym_client_core::{ + client::{ + base_client::storage::{ + gateway_details::GatewayDetailsStore, MixnetClientStorage, OnDiskPersistent, + }, + topology_control::geo_aware_provider::CountryGroup, + }, + config::{GatewayEndpointConfig, TopologyStructure}, + error::ClientCoreStatusMessage, +}; +use nym_socks5_client_core::{NymClient as Socks5NymClient, Socks5ControlMessageSender}; use nym_sphinx::params::PacketSize; use nym_task::manager::TaskStatus; use std::sync::Arc; use tap::TapFallible; use tokio::sync::RwLock; -use crate::config::{Config, PrivacyLevel}; use crate::{ + config::{Config, PrivacyLevel}, error::Result, events::{self, emit_event, emit_status_event}, models::{ConnectionStatusKind, ConnectivityTestResult}, @@ -46,6 +52,14 @@ fn override_config_from_env(config: &mut Config, privacy_level: &PrivacyLevel) { log::warn!("Disabling per-hop delay"); config.core.base.set_no_per_hop_delays(); + + // TODO: selectable in the UI + let default_country_group = CountryGroup::Europe; + log::warn!("Using geo-aware mixnode selection: {default_country_group}"); + config + .core + .base + .set_topology_structure(TopologyStructure::GeoAware(default_country_group)); } } diff --git a/service-providers/network-requester/src/allowed_hosts/hosts.rs b/service-providers/network-requester/src/allowed_hosts/hosts.rs index 5c48369d0d..11aa3dd276 100644 --- a/service-providers/network-requester/src/allowed_hosts/hosts.rs +++ b/service-providers/network-requester/src/allowed_hosts/hosts.rs @@ -136,7 +136,6 @@ impl HostsStore { }) .map(Host::from) .collect(); - dbg!(&hosts); Ok(hosts) } } From 6db396e877523896d636a3ddf2850de61f7faffa Mon Sep 17 00:00:00 2001 From: mfahampshire Date: Fri, 28 Jul 2023 16:31:08 +0200 Subject: [PATCH 026/361] cont. with first pass at tutorial --- documentation/dev-portal/src/SUMMARY.md | 3 +++ .../src/tutorials/cosmos-service/client.md | 1 + .../dev-portal/src/tutorials/cosmos-service/lib.md | 13 +++++++++++++ .../src/tutorials/cosmos-service/preparing-env.md | 13 ++++++++++++- .../src/tutorials/cosmos-service/service.md | 1 + 5 files changed, 30 insertions(+), 1 deletion(-) create mode 100644 documentation/dev-portal/src/tutorials/cosmos-service/client.md create mode 100644 documentation/dev-portal/src/tutorials/cosmos-service/lib.md create mode 100644 documentation/dev-portal/src/tutorials/cosmos-service/service.md diff --git a/documentation/dev-portal/src/SUMMARY.md b/documentation/dev-portal/src/SUMMARY.md index 77fbee86b0..3fd7145ff3 100644 --- a/documentation/dev-portal/src/SUMMARY.md +++ b/documentation/dev-portal/src/SUMMARY.md @@ -31,6 +31,9 @@ - [(Rust SDK) Cosmos Blockchain Service](tutorials/cosmos-service/intro.md) - [Tutorial Overview](tutorials/cosmos-service/overview.md) - [Preparing Your Environment](tutorials/cosmos-service/preparing-env.md) + - [Preparing Your Lib](tutorials/cosmos-service/lib.md) + - [Preparing Your Client](tutorials/cosmos-service/client.md) + - [Preparing Your Service](tutorials/cosmos-service/service.md) - [(Typescript) Simple Service Provider](tutorials/simple-service-provider/simple-service-provider.md) - [Tutorial Overview](tutorials/simple-service-provider/overview.md) - [Preparing Your User Client Environment](tutorials/simple-service-provider/preparating-env.md) diff --git a/documentation/dev-portal/src/tutorials/cosmos-service/client.md b/documentation/dev-portal/src/tutorials/cosmos-service/client.md new file mode 100644 index 0000000000..c6db01fb6a --- /dev/null +++ b/documentation/dev-portal/src/tutorials/cosmos-service/client.md @@ -0,0 +1 @@ +# Preparing Your Client diff --git a/documentation/dev-portal/src/tutorials/cosmos-service/lib.md b/documentation/dev-portal/src/tutorials/cosmos-service/lib.md new file mode 100644 index 0000000000..9988384581 --- /dev/null +++ b/documentation/dev-portal/src/tutorials/cosmos-service/lib.md @@ -0,0 +1,13 @@ +# Preparing Your Lib + +We'll now move on to preparing shared data structures and functions in `src/lib.rs`. + +These include the Request and Response types we'll be passing between the Client and the Service through the mixnet, as well as shared functions such as client creation, and parsing incoming messages. + +## Data Structures + +## Shared Functions + +### Client Creation + +### Parsing Incoming diff --git a/documentation/dev-portal/src/tutorials/cosmos-service/preparing-env.md b/documentation/dev-portal/src/tutorials/cosmos-service/preparing-env.md index a763be73f3..5a98938837 100644 --- a/documentation/dev-portal/src/tutorials/cosmos-service/preparing-env.md +++ b/documentation/dev-portal/src/tutorials/cosmos-service/preparing-env.md @@ -7,12 +7,23 @@ * Make a new cargo project: ``` - +cargo new nym-cosmos-service ``` * Create the following directory structure and files: ``` +. +├── Cargo.toml +├── bin +│   ├── client.rs +│   └── service.rs +└── src + ├── client.rs + ├── lib.rs + ├── main.rs + └── service.rs +3 directories, 7 files ``` * Finally add the following dependencies - you can just copy and paste this into your `Cargo.toml` file: diff --git a/documentation/dev-portal/src/tutorials/cosmos-service/service.md b/documentation/dev-portal/src/tutorials/cosmos-service/service.md new file mode 100644 index 0000000000..1090443702 --- /dev/null +++ b/documentation/dev-portal/src/tutorials/cosmos-service/service.md @@ -0,0 +1 @@ +# Preparing Your Service From db407847e310b59abaace3055ee9cdfeb574f203 Mon Sep 17 00:00:00 2001 From: serinko <97586125+serinko@users.noreply.github.com> Date: Mon, 31 Jul 2023 13:25:11 +0200 Subject: [PATCH 027/361] operators: book build - minor issues remain --- documentation/operators/book.toml | 10 ++++++---- documentation/operators/src/SUMMARY.md | 3 +-- documentation/operators/src/faq.md | 2 +- .../operators/src/nodes/mix-node-setup.md | 14 -------------- 4 files changed, 8 insertions(+), 21 deletions(-) diff --git a/documentation/operators/book.toml b/documentation/operators/book.toml index c39bb1c6a1..4202abc3f9 100644 --- a/documentation/operators/book.toml +++ b/documentation/operators/book.toml @@ -52,9 +52,11 @@ minimum_rust_version = "1.66" platform_release_version = "v1.1.22" wallet_release_version = "v1.2.5" -[preprocessor.last-changed] -command = "mdbook-last-changed" -renderer = ["html"] + +# [preprocessor.last-changed] +# command = "mdbook-last-changed" +# renderer = ["html"] + # used for grabbing output of binary commands for automation # https://github.com/FauconFan/mdbook-cmdrun @@ -85,7 +87,7 @@ curly-quotes = true # mathjax-support = false # useful if we want to pull equations in copy-fonts = true no-section-label = false -additional-css = ["theme/pagetoc.css", "././mdbook-admonish.css", "last-changed.css"] +additional-css = ["theme/pagetoc.css", "././mdbook-admonish.css"] #,"last-changed.css"] additional-js = ["theme/pagetoc.js"] git-repository-url = "https://github.com/nymtech/nym" git-repository-icon = "fa-github" diff --git a/documentation/operators/src/SUMMARY.md b/documentation/operators/src/SUMMARY.md index f767aec6f7..04d855952f 100644 --- a/documentation/operators/src/SUMMARY.md +++ b/documentation/operators/src/SUMMARY.md @@ -18,6 +18,5 @@ - [Nyx Validator Setup](./nodes/validator-setup.md) - [Maintenance](./nodes/maintenance.md) - [Troubleshooting](./nodes/troubleshooting.md) - + diff --git a/documentation/operators/src/faq.md b/documentation/operators/src/faq.md index 4fbc17694a..0de60740bd 100644 --- a/documentation/operators/src/faq.md +++ b/documentation/operators/src/faq.md @@ -31,7 +31,7 @@ For more detailed calculation, read our blog post [Nym Token Economics update](h ### Which VPS providers would you recommend? -Consider in which jurisdiction they reside and where they want to run a mix node. Do you want to pay by crypto or not and what are the other important particularities for your case? We always recommend operators to try to chose smaller and decentralized VPS providers over the most known ones controlling a majority of the internet. We recieve some good feedback on these: Hertzner, DigitalOcean, Linode, Ghandi and Exoscale. Do your own research and share with the community. +Consider in which jurisdiction you reside and where do you want to run a mix node. Do you want to pay by crypto or not and what are the other important particularities for your case? We always recommend operators to try to chose smaller and decentralized VPS providers over the most known ones controlling a majority of the internet. We recieve some good feedback on these: Hertzner, DigitalOcean, Linode, Ghandi and Exoscale. Do your own research and share with the community. ### Why is a mixnode setup on a self-hosted machine so tricky? diff --git a/documentation/operators/src/nodes/mix-node-setup.md b/documentation/operators/src/nodes/mix-node-setup.md index 7e1cafa64c..f8981fed68 100644 --- a/documentation/operators/src/nodes/mix-node-setup.md +++ b/documentation/operators/src/nodes/mix-node-setup.md @@ -1,17 +1,3 @@ - - # Mix Nodes > The Nym mix node binary was built in the [building nym](https://nymtech.net/operators/binaries/building-nym.html) section. If you haven't yet built Nym and want to run the code, go there first. From ce87df3026474eb1597f152a8a7e9534d96a17d5 Mon Sep 17 00:00:00 2001 From: serinko <97586125+serinko@users.noreply.github.com> Date: Mon, 31 Jul 2023 13:47:57 +0200 Subject: [PATCH 028/361] operators: comment missing info --- documentation/operators/src/faq.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/documentation/operators/src/faq.md b/documentation/operators/src/faq.md index 0de60740bd..3c832b68ed 100644 --- a/documentation/operators/src/faq.md +++ b/documentation/operators/src/faq.md @@ -33,7 +33,7 @@ For more detailed calculation, read our blog post [Nym Token Economics update](h Consider in which jurisdiction you reside and where do you want to run a mix node. Do you want to pay by crypto or not and what are the other important particularities for your case? We always recommend operators to try to chose smaller and decentralized VPS providers over the most known ones controlling a majority of the internet. We recieve some good feedback on these: Hertzner, DigitalOcean, Linode, Ghandi and Exoscale. Do your own research and share with the community. -### Why is a mixnode setup on a self-hosted machine so tricky? + ### What's the Sphinx packet size? From 432af5a20495e6829dd5b506461ef1e321b0205d Mon Sep 17 00:00:00 2001 From: serinko <97586125+serinko@users.noreply.github.com> Date: Mon, 31 Jul 2023 13:53:21 +0200 Subject: [PATCH 029/361] operators: typo edit --- documentation/operators/src/faq.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/documentation/operators/src/faq.md b/documentation/operators/src/faq.md index 3c832b68ed..faefd70b18 100644 --- a/documentation/operators/src/faq.md +++ b/documentation/operators/src/faq.md @@ -21,7 +21,7 @@ make a note that the keys will be overwritten in bold when you're noting it in t ### What determines the rewards when running a mixnode? -The stake required for a mix node to achieve maximum rewards is called to the mix node saturation point os simply stake saturation point. This is calculated from the staking supply (all circulating supply + part of unlocked tokens). The target level of staking is to have 50% of the staking supply locked in mix nodes. +The stake required for a mix node to achieve maximum rewards is called mix node saturation point. This is calculated from the staking supply (all circulating supply + part of unlocked tokens). The target level of staking is to have 50% of the staking supply locked in mix nodes. The node stake saturation point, which we denote by Nsat, is given by the stake supply, target level of staking divided by the number of rewarded (active) nodes. From 82872ae02a72922a75c26f125c2e1fe432437f71 Mon Sep 17 00:00:00 2001 From: mfahampshire Date: Mon, 31 Jul 2023 15:38:19 +0200 Subject: [PATCH 030/361] working lib setup for tutorial --- documentation/dev-portal/book.toml | 1 - .../src/tutorials/cosmos-service/intro.md | 5 +- .../src/tutorials/cosmos-service/lib.md | 87 ++++++++++++++++++- .../src/tutorials/cosmos-service/overview.md | 2 +- .../tutorials/cosmos-service/preparing-env.md | 42 ++++++++- 5 files changed, 126 insertions(+), 11 deletions(-) diff --git a/documentation/dev-portal/book.toml b/documentation/dev-portal/book.toml index cf434a31f5..d6907bd88a 100644 --- a/documentation/dev-portal/book.toml +++ b/documentation/dev-portal/book.toml @@ -38,7 +38,6 @@ chapter-line-height = "2em" section-line-height = "1.5em" # # if true, never read and touch the files in theme dir -turn-off = false [preprocessor.admonish] command = "mdbook-admonish" diff --git a/documentation/dev-portal/src/tutorials/cosmos-service/intro.md b/documentation/dev-portal/src/tutorials/cosmos-service/intro.md index 1b9e40096a..1510d31e2e 100644 --- a/documentation/dev-portal/src/tutorials/cosmos-service/intro.md +++ b/documentation/dev-portal/src/tutorials/cosmos-service/intro.md @@ -1,4 +1,7 @@ # Building a Cosmos Blockchain Service with the Rust SDK This tutorial is for Rust developers wanting to interact with the Rust SDK and take a first step at building a service with which to interact with a blockchain. -The key here is to think of the service as a proxy: it interacts with the blockchain _on the client's behalf_, shielding it from the Validator it interacts with, whilst also being shielded from the client by the mixnet! + +The key here is to think of the service as a proxy: it interacts with the blockchain _on the client's behalf_, shielding it from the Validator it interacts with, whilst also being shielded from the client by the mixnet. + +> This service also nicely highlights the limitations of the mixnet - even though with this code your metadata is shielded from the Validator, and even the service does not know your Nym address, application-level information such as a blockchain address is not made private, in virtue of the fact that using the mixnet provides solely network-level privacy. For information on what application-level privacy Nym offers, check out the [coconut credential SDK example](). diff --git a/documentation/dev-portal/src/tutorials/cosmos-service/lib.md b/documentation/dev-portal/src/tutorials/cosmos-service/lib.md index 9988384581..ef041b1838 100644 --- a/documentation/dev-portal/src/tutorials/cosmos-service/lib.md +++ b/documentation/dev-portal/src/tutorials/cosmos-service/lib.md @@ -1,13 +1,92 @@ # Preparing Your Lib -We'll now move on to preparing shared data structures and functions in `src/lib.rs`. +Now move on to preparing shared data structures and functions in `src/lib.rs`. -These include the Request and Response types we'll be passing between the Client and the Service through the mixnet, as well as shared functions such as client creation, and parsing incoming messages. +These include the request and response types the client and the service will be passing through the mixnet, as well as shared functions such as client creation, and message parsing. -## Data Structures +## Dependencies +The dependecies for the shared `lib` file are the following: +```rust +use cosmrs::{tendermint, AccountId}; +use nym_sdk::mixnet::{ + AnonymousSenderTag, MixnetClient, MixnetClientBuilder, ReconstructedMessage, StoragePaths, +}; +use serde::{Deserialize, Serialize}; +use std::path::PathBuf; +pub mod client; +pub mod service; +``` + +Since this is the file where client creation and message parsing are handled, the various `nym_sdk` imports, as well as `serde`'s (de)serialisation functionality, are required. This file also imports the `client` and `service` specific logic not found in `bin/`, as well as `PathBuf` to read filepaths, and `cosmrs` types for defining Nyx blockchain accounts. + +## Constants +Below this are the chain-related `const` variables. These have been hardcoded for this demo. + +```rust +pub const DEFAULT_VALIDATOR_RPC: &str = "https://sandbox-validator1.nymtech.net"; +pub const DEFAULT_DENOM: &str = "unym"; +pub const DEFAULT_PREFIX: &str = "n"; +``` + +These define the RPC endpoint your service will use to interact with the blockchain - in this case the Sandbox testnet - as well as the coin denomination and Bech32-prefix of the blockchain accounts. + +## Shared Data Structures +Define the following structs for our different request and responses that will be serialised and sent through the mixnet between your client and service binaries: + +```rust +#[derive(Deserialize, Serialize, Debug)] +pub struct SequenceRequest { + pub validator: String, + pub signer_address: AccountId, +} + +#[derive(Deserialize, Serialize, Debug)] +pub struct SequenceRequestResponse { + pub account_number: u64, + pub sequence: u64, + pub chain_id: tendermint::chain::Id, +} + +#[derive(Debug, Deserialize, Serialize)] +#[serde(untagged)] +pub enum RequestTypes { + Sequence(SequenceRequest), +} + +#[derive(Debug, Deserialize, Serialize)] +#[serde(untagged)] +pub enum ResponseTypes { + Sequence(SequenceRequestResponse), +} +``` + +TODO explainer of each ## Shared Functions +Now to define functions shared by the `client` and `service` binaries. ### Client Creation +The following function is called on startup by each binary, with the `config_path` being a filepath for storing client config. -### Parsing Incoming +```rust +// create our client with specified path for key storage +pub async fn create_client(config_path: PathBuf) -> MixnetClient { + let config_dir = config_path; + let storage_paths = StoragePaths::new_from_dir(&config_dir).unwrap(); + let client = MixnetClientBuilder::new_with_default_storage(storage_paths) + .await + .unwrap() + .build() + .await + .unwrap(); + + client.connect_to_mixnet().await.unwrap() +} +``` + +> If keys and config already exist at this location, re-running this function **will not** overwrite them. + +### Parsing Incoming messages +TODO smoosh them into one function with the return type being an `Option` instead + +Next to define two functions: one for listening _for_ messages from the mixnet (used by our `service`), and one for listening out for a _reply_ after sending a message to another Nym client (in this case, when sending a message from the `client` to the `service`). diff --git a/documentation/dev-portal/src/tutorials/cosmos-service/overview.md b/documentation/dev-portal/src/tutorials/cosmos-service/overview.md index a24a381662..13acb800c9 100644 --- a/documentation/dev-portal/src/tutorials/cosmos-service/overview.md +++ b/documentation/dev-portal/src/tutorials/cosmos-service/overview.md @@ -2,7 +2,7 @@ This tutorial involves writing two pieces of code in Rust: -- A client side binary used to construct a blockchain query and send this query to a service, which will interact with a Cosmos SDK blockchain on our behalf. +- A client side binary used to construct a blockchain query and send this query to a service, which will interact with a Cosmos SDK blockchain on our behalf (bear in mind this principle works for all blockchains - we're just utilising the `cosmrs` library to interact with the Nyx blockchain in this tutorial). - A service which will listen out for requests from the mixnet, act on those requests, and anonymously reply to the client sending the requests. You will learn how to do the following with the Rust SDK: diff --git a/documentation/dev-portal/src/tutorials/cosmos-service/preparing-env.md b/documentation/dev-portal/src/tutorials/cosmos-service/preparing-env.md index 5a98938837..341c89a216 100644 --- a/documentation/dev-portal/src/tutorials/cosmos-service/preparing-env.md +++ b/documentation/dev-portal/src/tutorials/cosmos-service/preparing-env.md @@ -20,13 +20,47 @@ cargo new nym-cosmos-service └── src ├── client.rs ├── lib.rs - ├── main.rs └── service.rs -3 directories, 7 files +3 directories, 6 files ``` -* Finally add the following dependencies - you can just copy and paste this into your `Cargo.toml` file: +* Add the following dependencies to your `Cargo.toml` file: ``` -TODO pull validator client code etc from the monorepo instead of workspace loading +[dependencies] +anyhow = "1.0.72" +clap = { version = "4.0", features = ["derive"] } +bip39 = { version = "2.0.0", features = ["zeroize"] } +cosmrs = "=0.14.0" +TODO +# tokio = { workspace = true, features = ["rt-multi-thread", "macros"] } +tokio = "1.24.1" +bs58 = "0.5.0" +serde = "1.0.152" +serde_json = "1.0.91" ``` + +These are non Nym-specific dependencies for the project. `anyhow` is for catch-all error handling, `clap` is for setting up the CLI commands, `cosmrs` for cosmos-specific types and functionality, `tokio` for the async/await environment, and `serde` for (de)serialisation. + +* Next add Nym-specific dependencies. Since these libraries are not yet on [crates io](https://crates.io) then you need to import them from the Nym monorepo: +``` +nym-sdk = { git = "https://github.com/nymtech/nym" } +nym-sphinx-addressing = { git = "https://github.com/nymtech/nym" } +nym-validator-client = { git = "https://github.com/nymtech/nym" } +nym-bin-common = { git = "https://github.com/nymtech/nym" } +nym-sphinx-anonymous-replies = { git = "https://github.com/nymtech/nym" } +``` + +The `sphinx` dependencies are for packet- and address-related functionality, the `validator-client` for Nyx blockchain specific configs, `common` for client logging, and the `sdk` for SDK functionality: creating and managing client storage and connections, and sending and receiving messages to and from the mixnet. + +* Finally add the following underneath your `[dependencies]`: +``` +[[bin]] +name = "client" +path = "bin/client.rs" + +[[bin]] +name = "service" +path = "bin/service.rs" +``` +This defines multiple binaries to run in a single cargo project, as outlined [here](https://doc.rust-lang.org/cargo/reference/cargo-targets.html#binaries). From bb263f8c5ef70fa004ae5eb67890cb00db0e4da6 Mon Sep 17 00:00:00 2001 From: mfahampshire Date: Mon, 31 Jul 2023 15:38:49 +0200 Subject: [PATCH 031/361] added tree output for created client with storage example --- documentation/docs/src/sdk/rust.md | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/documentation/docs/src/sdk/rust.md b/documentation/docs/src/sdk/rust.md index 68004b7576..aec482359b 100644 --- a/documentation/docs/src/sdk/rust.md +++ b/documentation/docs/src/sdk/rust.md @@ -46,6 +46,26 @@ The example above involves ephemeral keys - if we want to create and then mainta As seen in the example above, the `mixnet::MixnetClientBuilder::new()` function handles checking for keys in a storage location, loading them if present, or creating them and storing them if not, making client key management very simple. +Assuming our client config is stored in `/tmp/mixnet-client`, the following files are generated: +``` +$ tree /tmp/mixnet-client + +mixnet-client +├── ack_key.pem +├── db.sqlite +├── db.sqlite-shm +├── db.sqlite-wal +├── gateway_details.json +├── gateway_shared.pem +├── persistent_reply_store.sqlite +├── private_encryption.pem +├── private_identity.pem +├── public_encryption.pem +└── public_identity.pem + +1 directory, 11 files +``` + ### Manually handling storage If you're integrating mixnet functionality into an existing app and want to integrate saving client configs and keys into your existing storage logic, you can manually perform the actions taken automatically above (`examples/manually_handle_keys_and_config.rs`) @@ -64,7 +84,7 @@ The number of SURBs is set [here](https://github.com/nymtech/nym/blob/release/{{ You can read more about how SURBs function under the hood [here](../architecture/traffic-flow.md#private-replies-using-surbs). -In order to reply to an incoming message using SURBs, you can construct a `recipient` from the `sender_tag` sent along with the message you wish to reply to: +In order to reply to an incoming message using SURBs, you can construct a `recipient` from the `sender_tag` sent along with the message you wish to reply to: ```rust,noplayground {{#include ../../../../sdk/rust/nym-sdk/examples/surb-reply.rs}} From 97956afdf642c5ea6fedefc8406d94f50dfc6fe2 Mon Sep 17 00:00:00 2001 From: mfahampshire Date: Tue, 1 Aug 2023 10:33:34 +0200 Subject: [PATCH 032/361] removed old wallet address flag again --- documentation/docs/src/nodes/mix-node-setup.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/documentation/docs/src/nodes/mix-node-setup.md b/documentation/docs/src/nodes/mix-node-setup.md index 2f93797e1e..4635aa56ac 100644 --- a/documentation/docs/src/nodes/mix-node-setup.md +++ b/documentation/docs/src/nodes/mix-node-setup.md @@ -84,15 +84,15 @@ To check available configuration options for initializing your node use: ``` ~~~ -Initalise your mixnode with the following command, replacing the value of `--id` with the moniker you wish to give your mixnode, and the `--wallet-address` with the Nym address you created earlier. Your `--host` must be publicly routable on the internet in order to mix packets, and can be either an Ipv4 or IPv6 address. The `$(curl ifconfig.me)` command returns your IP automatically using an external service. If you enter your IP address manually, enter it **without** any port information. +Initalise your mixnode with the following command, replacing the value of `--id` with the moniker you wish to give your mixnode. Your `--host` must be publicly routable on the internet in order to mix packets, and can be either an Ipv4 or IPv6 address. The `$(curl ifconfig.me)` command returns your IP automatically using an external service. If you enter your IP address manually, enter it **without** any port information. ``` -./nym-mixnode init --id winston-smithnode --host $(curl ifconfig.me) --wallet-address n1eufxdlgt0puwrwptgjfqne8pj4nhy2u5ft62uq +./nym-mixnode init --id winston-smithnode --host $(curl ifconfig.me) ``` ~~~admonish example collapsible=true title="Console output" ``` - + ``` ~~~ From 15a6af49f092a86a095299ffcfdbac02118030f3 Mon Sep 17 00:00:00 2001 From: Lorexia Date: Tue, 25 Jul 2023 12:54:38 +0200 Subject: [PATCH 033/361] Add updates to community list projects --- .../community-applications-and-guides.md | 8 ++++++++ .../src/images/profile_picture/nymgraph_pp.png | Bin 0 -> 8489 bytes 2 files changed, 8 insertions(+) create mode 100644 documentation/dev-portal/src/images/profile_picture/nymgraph_pp.png diff --git a/documentation/dev-portal/src/community-resources/community-applications-and-guides.md b/documentation/dev-portal/src/community-resources/community-applications-and-guides.md index ba00922658..90e0801e80 100644 --- a/documentation/dev-portal/src/community-resources/community-applications-and-guides.md +++ b/documentation/dev-portal/src/community-resources/community-applications-and-guides.md @@ -93,6 +93,14 @@ We love seeing our developer community create applications using Nym. If you wou +## NymGraph + +> NymGraph is a graphical chat client for Nym running on Ubuntu and Debian. Test it to chat over the Nym network! +* [Github](https://github.com/Tyz3/nymgraph) + + + +

# Community Guides diff --git a/documentation/dev-portal/src/images/profile_picture/nymgraph_pp.png b/documentation/dev-portal/src/images/profile_picture/nymgraph_pp.png new file mode 100644 index 0000000000000000000000000000000000000000..411dbec6e01e27f9f960c521d4cdc453f874326b GIT binary patch literal 8489 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 literal 0 HcmV?d00001 From a344cda9160b24ef8ce767725604fdbcd2b47bd0 Mon Sep 17 00:00:00 2001 From: mfahampshire Date: Tue, 1 Aug 2023 10:33:34 +0200 Subject: [PATCH 034/361] removed old wallet address flag again --- documentation/docs/src/nodes/mix-node-setup.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/documentation/docs/src/nodes/mix-node-setup.md b/documentation/docs/src/nodes/mix-node-setup.md index 2f93797e1e..4635aa56ac 100644 --- a/documentation/docs/src/nodes/mix-node-setup.md +++ b/documentation/docs/src/nodes/mix-node-setup.md @@ -84,15 +84,15 @@ To check available configuration options for initializing your node use: ``` ~~~ -Initalise your mixnode with the following command, replacing the value of `--id` with the moniker you wish to give your mixnode, and the `--wallet-address` with the Nym address you created earlier. Your `--host` must be publicly routable on the internet in order to mix packets, and can be either an Ipv4 or IPv6 address. The `$(curl ifconfig.me)` command returns your IP automatically using an external service. If you enter your IP address manually, enter it **without** any port information. +Initalise your mixnode with the following command, replacing the value of `--id` with the moniker you wish to give your mixnode. Your `--host` must be publicly routable on the internet in order to mix packets, and can be either an Ipv4 or IPv6 address. The `$(curl ifconfig.me)` command returns your IP automatically using an external service. If you enter your IP address manually, enter it **without** any port information. ``` -./nym-mixnode init --id winston-smithnode --host $(curl ifconfig.me) --wallet-address n1eufxdlgt0puwrwptgjfqne8pj4nhy2u5ft62uq +./nym-mixnode init --id winston-smithnode --host $(curl ifconfig.me) ``` ~~~admonish example collapsible=true title="Console output" ``` - + ``` ~~~ From c7147ebfb25228fcdfd32ddfccddbba4ba476dfd Mon Sep 17 00:00:00 2001 From: Lorexia Date: Tue, 25 Jul 2023 12:54:38 +0200 Subject: [PATCH 035/361] Add updates to community list projects --- .../community-applications-and-guides.md | 8 ++++++++ .../src/images/profile_picture/nymgraph_pp.png | Bin 0 -> 8489 bytes 2 files changed, 8 insertions(+) create mode 100644 documentation/dev-portal/src/images/profile_picture/nymgraph_pp.png diff --git a/documentation/dev-portal/src/community-resources/community-applications-and-guides.md b/documentation/dev-portal/src/community-resources/community-applications-and-guides.md index ba00922658..90e0801e80 100644 --- a/documentation/dev-portal/src/community-resources/community-applications-and-guides.md +++ b/documentation/dev-portal/src/community-resources/community-applications-and-guides.md @@ -93,6 +93,14 @@ We love seeing our developer community create applications using Nym. If you wou +## NymGraph + +> NymGraph is a graphical chat client for Nym running on Ubuntu and Debian. Test it to chat over the Nym network! +* [Github](https://github.com/Tyz3/nymgraph) + + + +

# Community Guides diff --git a/documentation/dev-portal/src/images/profile_picture/nymgraph_pp.png b/documentation/dev-portal/src/images/profile_picture/nymgraph_pp.png new file mode 100644 index 0000000000000000000000000000000000000000..411dbec6e01e27f9f960c521d4cdc453f874326b GIT binary patch literal 8489 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 literal 0 HcmV?d00001 From 615c65cb22c9c2923e169859813ae8200b32873b Mon Sep 17 00:00:00 2001 From: serinko <97586125+serinko@users.noreply.github.com> Date: Tue, 1 Aug 2023 10:58:29 +0200 Subject: [PATCH 036/361] operators guide: add src/binaries/ files, fix links --- documentation/operators/src/SUMMARY.md | 6 +- .../operators/src/binaries/building-nym.html | 350 ++++++++++++++++++ .../operators/src/binaries/building-nym.md | 71 ++++ .../src/binaries/init-and-config.html | 290 +++++++++++++++ .../operators/src/binaries/init-and-config.md | 13 + .../src/binaries/pre-built-binaries.html | 286 ++++++++++++++ .../src/binaries/pre-built-binaries.md | 6 + .../src/binaries/version-compatiblity.md | 38 ++ .../operators/src/nodes/gateway-setup.md | 2 +- .../operators/src/nodes/mix-node-setup.md | 4 +- .../src/nodes/network-requester-setup.md | 2 +- .../operators/src/nodes/setup-guides.md | 8 +- .../operators/src/nodes/validator-setup.md | 10 +- .../operators/src/preliminary-steps.md | 2 +- 14 files changed, 1071 insertions(+), 17 deletions(-) create mode 100644 documentation/operators/src/binaries/building-nym.html create mode 100644 documentation/operators/src/binaries/building-nym.md create mode 100644 documentation/operators/src/binaries/init-and-config.html create mode 100644 documentation/operators/src/binaries/init-and-config.md create mode 100644 documentation/operators/src/binaries/pre-built-binaries.html create mode 100644 documentation/operators/src/binaries/pre-built-binaries.md create mode 100644 documentation/operators/src/binaries/version-compatiblity.md diff --git a/documentation/operators/src/SUMMARY.md b/documentation/operators/src/SUMMARY.md index 04d855952f..a3f55376c8 100644 --- a/documentation/operators/src/SUMMARY.md +++ b/documentation/operators/src/SUMMARY.md @@ -3,9 +3,9 @@ - [Introduction](introduction.md) # Binaries -- [Pre-built Binaries](../../docs/src/binaries/pre-built-binaries.md) -- [Building from Source](../../docs/src/binaries/building-nym.md) -- [Binary Initialisation and Configuration](../../docs/src/binaries/init-and-config.md) +- [Pre-built Binaries](./binaries/pre-built-binaries.md) +- [Building from Source](./binaries/building-nym.md) +- [Binary Initialisation and Configuration](./binaries/init-and-config.md) # Operators Guides diff --git a/documentation/operators/src/binaries/building-nym.html b/documentation/operators/src/binaries/building-nym.html new file mode 100644 index 0000000000..2f73af4b4f --- /dev/null +++ b/documentation/operators/src/binaries/building-nym.html @@ -0,0 +1,350 @@ + + + + + + Building from Source - Nym Operators Guides + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

+ + + + + + + + + + + + + + +
+ +
+ + + + + + + + +
+
+ {comment} +
+ + {comment} +
+ + {comment} +
+ + {comment} +
+ + {comment} +
+ + {comment} +
+ + {comment} +
+ + {comment} +
+ + {comment} +
+ + {comment} +
+ + {comment} +
+ + {comment} +
+ + {comment} +
+ + {comment} +
+ + {comment} +
+ + {comment} +
+ + {comment} +
+ + {comment} +
+ + {comment} +
+ + {comment} +
+ + {comment} +
+ +

Building from Source

+
+

Nym runs on Mac OS X, Linux, and Windows. All nodes except the Desktop Wallet and NymConnect on Windows should be considered experimental - it works fine if you’re an app developer but isn’t recommended for running nodes.

+
+

Building Nym

+

Nym has two main codebases:

+
    +
  • the Nym platform, written in Rust. This contains all of our code except for the validators.
  • +
  • the Nym validators, written in Go.
  • +
+
+

This page details how to build the main Nym platform code. If you want to build and run a validator, go here instead.

+
+

Prerequisites

+
    +
  • Debian/Ubuntu: pkg-config, build-essential, libssl-dev, curl, jq, git
  • +
+
apt install pkg-config build-essential libssl-dev curl jq git
+
+
    +
  • Arch/Manjaro: base-devel
  • +
+
pacman -S base-devel
+
+
    +
  • Mac OS X: pkg-config , brew, openss1, protobuf, curl, git +Running the following the script installs Homebrew and the above dependencies:
  • +
+
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
+
+
    +
  • Rust & cargo >= 1.66
  • +
+

We recommend using the Rust shell script installer. Installing cargo from your package manager (e.g. apt) is not recommended as the packaged versions are usually too old.

+

If you really don’t want to use the shell script installer, the Rust installation docs contain instructions for many platforms.

+

Download and build Nym binaries

+

The following commands will compile binaries into the nym/target/release directory:

+
rustup update
+git clone https://github.com/nymtech/nym.git
+cd nym
+
+git reset --hard # in case you made any changes on your branch
+git pull # in case you've checked it out before
+
+git checkout release/v1.1.22 # checkout to the latest release branch: `develop` will most likely be incompatible with deployed public networks
+
+cargo build --release # build your binaries with **mainnet** configuration
+NETWORK=sandbox cargo build --release # build your binaries with **sandbox** configuration
+
+

Quite a bit of stuff gets built. The key working parts are:

+ +

The repository also contains Typescript applications which aren’t built in this process. These can be built by following the instructions on their respective docs pages.

+ +
+

You cannot build from GitHub’s .zip or .tar.gz archive files on the releases page - the Nym build scripts automatically include the current git commit hash in the built binary during compilation, so the build will fail if you use the archive code (which isn’t a Git repository). Check the code out from github using git clone instead.

+
+ +
+ + +
+
+ + + +
+ + + + + + + + + + + + + + + + + + + + + +
+ + diff --git a/documentation/operators/src/binaries/building-nym.md b/documentation/operators/src/binaries/building-nym.md new file mode 100644 index 0000000000..8610fcb63a --- /dev/null +++ b/documentation/operators/src/binaries/building-nym.md @@ -0,0 +1,71 @@ +# Building from Source + +> Nym runs on Mac OS X, Linux, and Windows. All nodes **except the Desktop Wallet and NymConnect** on Windows should be considered experimental - it works fine if you're an app developer but isn't recommended for running nodes. + +## Building Nym +Nym has two main codebases: + +- the [Nym platform](https://github.com/nymtech/nym), written in Rust. This contains all of our code _except_ for the validators. +- the [Nym validators](https://github.com/nymtech/nyxd), written in Go. + +> This page details how to build the main Nym platform code. **If you want to build and run a validator, [go here](../nodes/validator-setup.md) instead.** + +## Prerequisites +- Debian/Ubuntu: `pkg-config`, `build-essential`, `libssl-dev`, `curl`, `jq`, `git` + +``` +apt install pkg-config build-essential libssl-dev curl jq git +``` + +- Arch/Manjaro: `base-devel` + +``` +pacman -S base-devel +``` + +- Mac OS X: `pkg-config` , `brew`, `openss1`, `protobuf`, `curl`, `git` +Running the following the script installs Homebrew and the above dependencies: + +``` +/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" +``` + +- `Rust & cargo >= {{minimum_rust_version}}` + +We recommend using the [Rust shell script installer](https://www.rust-lang.org/tools/install). Installing cargo from your package manager (e.g. `apt`) is not recommended as the packaged versions are usually too old. + +If you really don't want to use the shell script installer, the [Rust installation docs](https://forge.rust-lang.org/infra/other-installation-methods.html) contain instructions for many platforms. + +## Download and build Nym binaries +The following commands will compile binaries into the `nym/target/release` directory: + +``` +rustup update +git clone https://github.com/nymtech/nym.git +cd nym + +git reset --hard # in case you made any changes on your branch +git pull # in case you've checked it out before + +git checkout release/{{platform_release_version}} # checkout to the latest release branch: `develop` will most likely be incompatible with deployed public networks + +cargo build --release # build your binaries with **mainnet** configuration +NETWORK=sandbox cargo build --release # build your binaries with **sandbox** configuration +``` + +Quite a bit of stuff gets built. The key working parts are: + +* [mix node](../nodes/mix-node-setup.md): `nym-mixnode` +* [gateway node](../nodes/gateway-setup.md): `nym-gateway` +* [websocket client](https://nymtech.net/docs/clients/websocket-client.html): `nym-client` +* [socks5 client](https://nymtech.net/docs/clients/socks5-client.html): `nym-socks5-client` +* [webassembly client](https://nymtech.net/docs/clients/webassembly-client.html): `webassembly-client` +* [network requester](../nodes/network-requester-setup.md): `nym-network-requester` +* [nym-cli tool](https://nymtech.net/docs/tools/nym-cli.html): `nym-cli` + +The repository also contains Typescript applications which aren't built in this process. These can be built by following the instructions on their respective docs pages. +* [Nym Wallet](https://nymtech.net/docs/wallet/desktop-wallet.html) +* [Nym Connect](https://nymtech.net/developers/quickstart/nymconnect-gui.html) +* [Network Explorer UI](https://nymtech.net/docs/explorers/mixnet-explorer.html) + +> You cannot build from GitHub's .zip or .tar.gz archive files on the releases page - the Nym build scripts automatically include the current git commit hash in the built binary during compilation, so the build will fail if you use the archive code (which isn't a Git repository). Check the code out from github using `git clone` instead. diff --git a/documentation/operators/src/binaries/init-and-config.html b/documentation/operators/src/binaries/init-and-config.html new file mode 100644 index 0000000000..147c89cc2d --- /dev/null +++ b/documentation/operators/src/binaries/init-and-config.html @@ -0,0 +1,290 @@ + + + + + + Binary Initialisation and Configuration - Nym Operators Guides + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + +
+ +
+ + + + + + + + +
+
+ {comment} +
+ + {comment} +
+ + {comment} +
+ + {comment} +
+ + {comment} +
+ + {comment} +
+ + {comment} +
+ + {comment} +
+ + {comment} +
+ + {comment} +
+ + {comment} +
+ + {comment} +
+ + {comment} +
+ + {comment} +
+ + {comment} +
+ + {comment} +
+ + {comment} +
+ + {comment} +
+ + {comment} +
+ + {comment} +
+ + {comment} +
+ +

Binary Initialisation and Configuration

+

All Nym binaries must first be initialised with init before being run.

+

The init command is usually where you pass flags specifying configuration arguments such as the gateway you wish to communicate with, the ports you wish your binary to listen on, etc.

+

The init command will also create the necessary keypairs and configuration files at ~/.nym/<BINARY_TYPE>/<BINARY_ID>/ if these files do not already exist. It will not overwrite existing keypairs if they are present.

+

You can reconfigure your binaries at any time by editing the config file located at ~/.nym/<BINARY_TYPE>/<BINARY_ID>/config/config.toml and restarting the binary process.

+

Once you have run init, you can start your binary with the run command, usually only accompanied by the id of the binary that you specified.

+

This id is never transmitted over the network, and is used to select which local config and key files to use for startup.

+ +
+ + +
+
+ + + +
+ + + + + + + + + + + + + + + + + + + + + +
+ + diff --git a/documentation/operators/src/binaries/init-and-config.md b/documentation/operators/src/binaries/init-and-config.md new file mode 100644 index 0000000000..e749662b8e --- /dev/null +++ b/documentation/operators/src/binaries/init-and-config.md @@ -0,0 +1,13 @@ +# Binary Initialisation and Configuration + +All Nym binaries must first be initialised with `init` before being `run`. + +The `init` command is usually where you pass flags specifying configuration arguments such as the gateway you wish to communicate with, the ports you wish your binary to listen on, etc. + +The `init` command will also create the necessary keypairs and configuration files at `~/.nym///` if these files do not already exist. **It will not overwrite existing keypairs if they are present.** + +You can reconfigure your binaries at any time by editing the config file located at `~/.nym///config/config.toml` and restarting the binary process. + +Once you have run `init`, you can start your binary with the `run` command, usually only accompanied by the `id` of the binary that you specified. + +This `id` is **never** transmitted over the network, and is used to select which local config and key files to use for startup. diff --git a/documentation/operators/src/binaries/pre-built-binaries.html b/documentation/operators/src/binaries/pre-built-binaries.html new file mode 100644 index 0000000000..3497c15d85 --- /dev/null +++ b/documentation/operators/src/binaries/pre-built-binaries.html @@ -0,0 +1,286 @@ + + + + + + Pre-built Binaries - Nym Operators Guides + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + +
+ +
+ + + + + + + + +
+
+ {comment} +
+ + {comment} +
+ + {comment} +
+ + {comment} +
+ + {comment} +
+ + {comment} +
+ + {comment} +
+ + {comment} +
+ + {comment} +
+ + {comment} +
+ + {comment} +
+ + {comment} +
+ + {comment} +
+ + {comment} +
+ + {comment} +
+ + {comment} +
+ + {comment} +
+ + {comment} +
+ + {comment} +
+ + {comment} +
+ + {comment} +
+ +

Pre-built Binaries

+

The Github releases page has pre-built binaries which should work on Ubuntu 20.04 and other Debian-based systems, but at this stage cannot be guaranteed to work everywhere.

+

If the pre-built binaries don’t work or are unavailable for your system, you will need to build the platform yourself.

+ +
+ + +
+
+ + + +
+ + + + + + + + + + + + + + + + + + + + + +
+ + diff --git a/documentation/operators/src/binaries/pre-built-binaries.md b/documentation/operators/src/binaries/pre-built-binaries.md new file mode 100644 index 0000000000..269c26276c --- /dev/null +++ b/documentation/operators/src/binaries/pre-built-binaries.md @@ -0,0 +1,6 @@ +# Pre-built Binaries + +The [Github releases page](https://github.com/nymtech/nym/releases) has pre-built binaries which should work on Ubuntu 20.04 and other Debian-based systems, but at this stage cannot be guaranteed to work everywhere. + +If the pre-built binaries don't work or are unavailable for your system, you will need to build the platform yourself. + diff --git a/documentation/operators/src/binaries/version-compatiblity.md b/documentation/operators/src/binaries/version-compatiblity.md new file mode 100644 index 0000000000..67e8a71ce6 --- /dev/null +++ b/documentation/operators/src/binaries/version-compatiblity.md @@ -0,0 +1,38 @@ +# Version Compatibility Table + +There are numerous components to Nym which are released independently of one another aside from when breaking changes occur in the [core platform code](https://github.com/nymtech/nym/) - mix nodes, gateways, network requesters, and clients. + +Whilst in general it recommended to be running the most recent version of any software, if you cannot do that for whatever reason this table will tell you which versions of different components are mutually compatible with which platform code releases. + + +| Core Platform | SDK | Wallet | NymConnect | Network Explorer | Mixnet contract | Vesting contract | +| ---------------- | ------------- | --------------- | --------------- | ---------------- | --------------- | ---------------- | +| 1.1.13 - 1.1.14* | 1.1.7 | 1.1.12 - 1.1.13 | 1.1.12 - 1.1.13 | 1.1.2 | 1.2.0 - 1.3.0 | 1.2.0 - 1.3.0 | +| 1.1.1 - 1.1.12 | 1.1.4 - 1.1.7 | 1.1.0 - 1.1.12 | 1.1.1 - 1.1.12 | 1.1.0 - 1.1.2 | 1.1.0 - 1.1.3 | 1.1.0 - 1.1.3 | +| 1.1.0 - 1.1.1 | 1.1.4 | 1.1.0 | 1.1.0 - 1.1.1 | 1.1.0 | 1.1.0 | 1.1.0 | +| 1.1.0 | x | 1.1.0 | 1.1.0 | 1.1.0 | 1.1.0 | 1.1.0 | + +`*` the `nym-mixnode` binary is currently one point ahead of the main platform release version + +> There are seperate changelogs for [`NymConnect`](https://github.com/nymtech/nym/blob/release/{{platform_release_version}}/nym-connect/CHANGELOG.md) and the [`Desktop Wallet`](https://github.com/nymtech/nym/blob/release/{{platform_release_version}}/nym-wallet/CHANGELOG.md). The changelog referenced below is for the core platform code. + +| Platform release changelog | +| ---------------------------------------------------------------------------------------- | +| 1.1.14 ([CHANGELOG](https://github.com/nymtech/nym/blob/release/v1.1.14/CHANGELOG.md)) | +| 1.1.13 ([CHANGELOG](https://github.com/nymtech/nym/blob/release/v1.1.13/CHANGELOG.md)) | +| 1.1.12 ([CHANGELOG](https://github.com/nymtech/nym/blob/release/v1.1.12/CHANGELOG.md)) | +| 1.1.11 ([CHANGELOG](https://github.com/nymtech/nym/blob/release/v1.1.11/CHANGELOG.md)) | +| 1.1.10 ([CHANGELOG](https://github.com/nymtech/nym/blob/release/v1.1.10/CHANGELOG.md)) | +| 1.1.9 ([CHANGELOG](https://github.com/nymtech/nym/blob/release/v1.1.9/CHANGELOG.md)) | +| 1.1.8 ([CHANGELOG](https://github.com/nymtech/nym/blob/release/v1.1.8/CHANGELOG.md)) | +| 1.1.7 ([CHANGELOG](https://github.com/nymtech/nym/blob/release/v1.1.7/CHANGELOG.md)) | +| 1.1.6 ([CHANGELOG](https://github.com/nymtech/nym/blob/release/v1.1.6/CHANGELOG.md)) | +| 1.1.5 ([CHANGELOG](https://github.com/nymtech/nym/blob/release/v1.1.5/CHANGELOG.md)) | +| 1.1.4 ([CHANGELOG](https://github.com/nymtech/nym/blob/release/v1.1.4/CHANGELOG.md)) | +| 1.1.3 ([CHANGELOG](https://github.com/nymtech/nym/blob/release/v1.1.3/CHANGELOG.md)) | +| 1.1.2 ([CHANGELOG](https://github.com/nymtech/nym/blob/release/v1.1.2/CHANGELOG.md)) | +| 1.1.1 ([CHANGELOG](https://github.com/nymtech/nym/blob/release/v1.1.1/CHANGELOG.md)) | +| 1.1.0 ([CHANGELOG](https://github.com/nymtech/nym/blob/release/v1.1.0/CHANGELOG.md)) | +| 1.0.2 ([CHANGELOG](https://github.com/nymtech/nym/blob/nym-binaries-1.0.2/CHANGELOG.md)) | +| 1.0.1 ([CHANGELOG](https://github.com/nymtech/nym/blob/nym-binaries-1.0.1/CHANGELOG.md)) | +| 1.0.0 ([CHANGELOG](https://github.com/nymtech/nym/blob/nym-binaries-1.0.0/CHANGELOG.md)) | diff --git a/documentation/operators/src/nodes/gateway-setup.md b/documentation/operators/src/nodes/gateway-setup.md index cdc72700e2..31cff54c22 100644 --- a/documentation/operators/src/nodes/gateway-setup.md +++ b/documentation/operators/src/nodes/gateway-setup.md @@ -4,7 +4,7 @@ TODO ---> # Gateways -> The Nym gateway was built in the [building nym](https://nymtech.net/operators/binaries/building-nym.html) section. If you haven't yet built Nym and want to run the code, go there first. +> The Nym gateway was built in the [building nym](../binaries/building-nym.md) section. If you haven't yet built Nym and want to run the code, go there first. > Any syntax in `<>` brackets is a user's unique variable. Exchange with a corresponding name without the `<>` brackets. diff --git a/documentation/operators/src/nodes/mix-node-setup.md b/documentation/operators/src/nodes/mix-node-setup.md index f8981fed68..0e711b6faf 100644 --- a/documentation/operators/src/nodes/mix-node-setup.md +++ b/documentation/operators/src/nodes/mix-node-setup.md @@ -1,6 +1,6 @@ # Mix Nodes -> The Nym mix node binary was built in the [building nym](https://nymtech.net/operators/binaries/building-nym.html) section. If you haven't yet built Nym and want to run the code, go there first. +> The Nym mix node binary was built in the [building nym](../binaries/building-nym.md) section. If you haven't yet built Nym and want to run the code, go there first. > Any syntax in `<>` brackets is a user's unique variable. Exchange with a corresponding name without the `<>` brackets. @@ -17,7 +17,7 @@ Make sure you do the preparation listed in the [preliminary steps page](../preli ## Mix node setup -Now that you have built the [codebase](https://nymtech.net/docs/binaries/building-nym.html), set up your [wallet](https://nymtech.net/docs/wallet/desktop-wallet.html), and have a VPS with the `nym-mixnode` binary, you can set up your mix node with the instructions below. +Now that you have built the [codebase](../binaries/building-nym.md), set up your [wallet](https://nymtech.net/docs/wallet/desktop-wallet.html), and have a VPS with the `nym-mixnode` binary, you can set up your mix node with the instructions below. To begin, move to `/taget/release` directory from which you run the node commands: diff --git a/documentation/operators/src/nodes/network-requester-setup.md b/documentation/operators/src/nodes/network-requester-setup.md index 33dc3c7c90..1cfee00e9b 100644 --- a/documentation/operators/src/nodes/network-requester-setup.md +++ b/documentation/operators/src/nodes/network-requester-setup.md @@ -4,7 +4,7 @@ TODO: ---> # Network Requesters -> The Nym gateway was built in the [building nym](https://nymtech.net/operators/binaries/building-nym.html) section. If you haven't yet built Nym and want to run the code, go there first. +> The Nym gateway was built in the [building nym](../binaries/building-nym.md) section. If you haven't yet built Nym and want to run the code, go there first. > Any syntax in `<>` brackets is a user's unique variable. Exchange with a corresponding name without the `<>` brackets. diff --git a/documentation/operators/src/nodes/setup-guides.md b/documentation/operators/src/nodes/setup-guides.md index b19cf07deb..37955a6549 100644 --- a/documentation/operators/src/nodes/setup-guides.md +++ b/documentation/operators/src/nodes/setup-guides.md @@ -1,13 +1,13 @@ # Node Setup Guides -To setup any type of Nym's node, start with building [Nym's platform](https://nymtech.net/docs/binaries/building-nym.html) on the machine (VPS) where you want to run the node. Nodes will need to be bond to Nym's wallet, setup one [here](https://nymtech.net/docs/wallet/desktop-wallet.html). +To setup any type of Nym's node, start with building [Nym's platform](../binaries/building-nym.md) on the machine (VPS) where you want to run the node. Nodes will need to be bond to Nym's wallet, setup one [here](https://nymtech.net/docs/wallet/desktop-wallet.html). This section contains setup guides for the following node types: * [Mix node](./mix-node-setup.md) diff --git a/documentation/operators/src/nodes/validator-setup.md b/documentation/operators/src/nodes/validator-setup.md index 6eafb49106..1c843a4cab 100644 --- a/documentation/operators/src/nodes/validator-setup.md +++ b/documentation/operators/src/nodes/validator-setup.md @@ -351,7 +351,7 @@ sudo ufw status Ports `22`, `80`, and `443` are for ssh, http, and https connections respectively. The rest of the ports are documented [here](https://docs.cosmos.network/main/core/grpc_rest). -For more information about your validator's port configuration, check the [validator port reference table](https://nymtech.net/operators/binaries/maintenance.html#ports) below. +For more information about your validator's port configuration, check the [validator port reference table](./maintenance.md#ports) below. > If you are planning to use [Cockpit](https://cockpit-project.org/) on your validator server then you will have defined a different `grpc` port in your `config.toml` above: remember to open this port as well. @@ -465,15 +465,15 @@ nyxd tx staking edit-validator With above command you can specify the `gpg` key last numbers (as used in `keybase`) as well as validator details and your email for security contact. ### Automating your validator with systemd -You will most likely want to automate your validator restarting if your server reboots. Checkout the [maintenance page](https://nymtech.net/operators/binaries/maintenance.html#systemd) with a quick tutorial. +You will most likely want to automate your validator restarting if your server reboots. Checkout the [maintenance page](./maintenance.md#systemd) with a quick tutorial. ### Installing and configuring nginx for HTTPS -If you want to set up a reverse proxying on the validator server to improve security and performance, using [nginx](https://www.nginx.com/resources/glossary/nginx/#:~:text=NGINX%20is%20open%20source%20software,%2C%20media%20streaming%2C%20and%20more.&text=In%20addition%20to%20its%20HTTP,%2C%20TCP%2C%20and%20UDP%20servers.), follow the manual on the [maintenance page](https://nymtech.net/operators/binaries/maintenance.html#setup). +If you want to set up a reverse proxying on the validator server to improve security and performance, using [nginx](https://www.nginx.com/resources/glossary/nginx/#:~:text=NGINX%20is%20open%20source%20software,%2C%20media%20streaming%2C%20and%20more.&text=In%20addition%20to%20its%20HTTP,%2C%20TCP%2C%20and%20UDP%20servers.), follow the manual on the [maintenance page](./maintenance.md#setup). ### Setting the ulimit -Linux machines limit how many open files a user is allowed to have. This is called a `ulimit`. We need to set it to a higher value than the default 1024. Follow the instructions in the [maintenance page](https://nymtech.net/operators/binaries/maintenance.html#Setting-the-ulimit) to change the `ulimit` value for validators. +Linux machines limit how many open files a user is allowed to have. This is called a `ulimit`. We need to set it to a higher value than the default 1024. Follow the instructions in the [maintenance page](./maintenance.md#Setting-the-ulimit) to change the `ulimit` value for validators. ## Using your validator ### Unjailing your validator @@ -502,7 +502,7 @@ nyxd tx slashing unjail ### Upgrading your validator -To upgrade your validator, follow the steps on the [maintenance page](https://nymtech.net/operators/binaries/maintenance.html#Setting-the-ulimit). +To upgrade your validator, follow the steps on the [maintenance page](./maintenance.md#setting-the-ulimit). #### Common reasons for your validator being jailed diff --git a/documentation/operators/src/preliminary-steps.md b/documentation/operators/src/preliminary-steps.md index 1c738b34bb..5cb76d8e19 100644 --- a/documentation/operators/src/preliminary-steps.md +++ b/documentation/operators/src/preliminary-steps.md @@ -1,6 +1,6 @@ # Preliminary Steps -> The Nym mixnode, gateway and network requester binaries were built in the [building nym](https://nymtech.net/docs/binaries/building-nym.html) section. If you haven't yet built Nym and want to run the code, go there first. +> The Nym mixnode, gateway and network requester binaries were built in the [building nym](../binaries/building-nym.md) section. If you haven't yet built Nym and want to run the code, go there first. ## Preliminary steps From 1965df05e724e6e5f472ef6e4db4f7e705cb95be Mon Sep 17 00:00:00 2001 From: serinko <97586125+serinko@users.noreply.github.com> Date: Tue, 1 Aug 2023 11:04:11 +0200 Subject: [PATCH 037/361] operators guide: book built --- docs/src/binaries/building-nym.html | 242 ++++++++++++ docs/src/binaries/building-nym.md | 1 + docs/src/binaries/init-and-config.html | 242 ++++++++++++ docs/src/binaries/init-and-config.md | 1 + docs/src/binaries/pre-built-binaries.html | 242 ++++++++++++ docs/src/binaries/pre-built-binaries.md | 1 + .../docs/src/binaries/building-nym.html | 350 ++++++++++++++++++ .../docs/src/binaries/init-and-config.html | 290 +++++++++++++++ .../docs/src/binaries/pre-built-binaries.html | 286 ++++++++++++++ 9 files changed, 1655 insertions(+) create mode 100644 docs/src/binaries/building-nym.html create mode 100644 docs/src/binaries/building-nym.md create mode 100644 docs/src/binaries/init-and-config.html create mode 100644 docs/src/binaries/init-and-config.md create mode 100644 docs/src/binaries/pre-built-binaries.html create mode 100644 docs/src/binaries/pre-built-binaries.md create mode 100644 documentation/docs/src/binaries/building-nym.html create mode 100644 documentation/docs/src/binaries/init-and-config.html create mode 100644 documentation/docs/src/binaries/pre-built-binaries.html diff --git a/docs/src/binaries/building-nym.html b/docs/src/binaries/building-nym.html new file mode 100644 index 0000000000..90e9c937cc --- /dev/null +++ b/docs/src/binaries/building-nym.html @@ -0,0 +1,242 @@ + + + + + + Building from Source - Nym Operators Guides + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + +
+ +
+ + + + + + + + +
+
+ {comment} +
+ + {comment} +
+ + {comment} +
+ + {comment} +
+ + {comment} +
+ + {comment} +
+ + {comment} +
+ +

Building from Source

+ +
+ + +
+
+ + + +
+ + + + + + + + + + + + + + + + + + + + + +
+ + diff --git a/docs/src/binaries/building-nym.md b/docs/src/binaries/building-nym.md new file mode 100644 index 0000000000..f195be32f9 --- /dev/null +++ b/docs/src/binaries/building-nym.md @@ -0,0 +1 @@ +# Building from Source diff --git a/docs/src/binaries/init-and-config.html b/docs/src/binaries/init-and-config.html new file mode 100644 index 0000000000..2a9390a316 --- /dev/null +++ b/docs/src/binaries/init-and-config.html @@ -0,0 +1,242 @@ + + + + + + Binary Initialisation and Configuration - Nym Operators Guides + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + +
+ +
+ + + + + + + + +
+
+ {comment} +
+ + {comment} +
+ + {comment} +
+ + {comment} +
+ + {comment} +
+ + {comment} +
+ + {comment} +
+ +

Binary Initialisation and Configuration

+ +
+ + +
+
+ + + +
+ + + + + + + + + + + + + + + + + + + + + +
+ + diff --git a/docs/src/binaries/init-and-config.md b/docs/src/binaries/init-and-config.md new file mode 100644 index 0000000000..9087d5cf00 --- /dev/null +++ b/docs/src/binaries/init-and-config.md @@ -0,0 +1 @@ +# Binary Initialisation and Configuration diff --git a/docs/src/binaries/pre-built-binaries.html b/docs/src/binaries/pre-built-binaries.html new file mode 100644 index 0000000000..a02f51d2bd --- /dev/null +++ b/docs/src/binaries/pre-built-binaries.html @@ -0,0 +1,242 @@ + + + + + + Pre-built Binaries - Nym Operators Guides + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + +
+ +
+ + + + + + + + +
+
+ {comment} +
+ + {comment} +
+ + {comment} +
+ + {comment} +
+ + {comment} +
+ + {comment} +
+ + {comment} +
+ +

Pre-built Binaries

+ +
+ + +
+
+ + + +
+ + + + + + + + + + + + + + + + + + + + + +
+ + diff --git a/docs/src/binaries/pre-built-binaries.md b/docs/src/binaries/pre-built-binaries.md new file mode 100644 index 0000000000..8c52595ce9 --- /dev/null +++ b/docs/src/binaries/pre-built-binaries.md @@ -0,0 +1 @@ +# Pre-built Binaries diff --git a/documentation/docs/src/binaries/building-nym.html b/documentation/docs/src/binaries/building-nym.html new file mode 100644 index 0000000000..2f73af4b4f --- /dev/null +++ b/documentation/docs/src/binaries/building-nym.html @@ -0,0 +1,350 @@ + + + + + + Building from Source - Nym Operators Guides + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + +
+ +
+ + + + + + + + +
+
+ {comment} +
+ + {comment} +
+ + {comment} +
+ + {comment} +
+ + {comment} +
+ + {comment} +
+ + {comment} +
+ + {comment} +
+ + {comment} +
+ + {comment} +
+ + {comment} +
+ + {comment} +
+ + {comment} +
+ + {comment} +
+ + {comment} +
+ + {comment} +
+ + {comment} +
+ + {comment} +
+ + {comment} +
+ + {comment} +
+ + {comment} +
+ +

Building from Source

+
+

Nym runs on Mac OS X, Linux, and Windows. All nodes except the Desktop Wallet and NymConnect on Windows should be considered experimental - it works fine if you’re an app developer but isn’t recommended for running nodes.

+
+

Building Nym

+

Nym has two main codebases:

+
    +
  • the Nym platform, written in Rust. This contains all of our code except for the validators.
  • +
  • the Nym validators, written in Go.
  • +
+
+

This page details how to build the main Nym platform code. If you want to build and run a validator, go here instead.

+
+

Prerequisites

+
    +
  • Debian/Ubuntu: pkg-config, build-essential, libssl-dev, curl, jq, git
  • +
+
apt install pkg-config build-essential libssl-dev curl jq git
+
+
    +
  • Arch/Manjaro: base-devel
  • +
+
pacman -S base-devel
+
+
    +
  • Mac OS X: pkg-config , brew, openss1, protobuf, curl, git +Running the following the script installs Homebrew and the above dependencies:
  • +
+
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
+
+
    +
  • Rust & cargo >= 1.66
  • +
+

We recommend using the Rust shell script installer. Installing cargo from your package manager (e.g. apt) is not recommended as the packaged versions are usually too old.

+

If you really don’t want to use the shell script installer, the Rust installation docs contain instructions for many platforms.

+

Download and build Nym binaries

+

The following commands will compile binaries into the nym/target/release directory:

+
rustup update
+git clone https://github.com/nymtech/nym.git
+cd nym
+
+git reset --hard # in case you made any changes on your branch
+git pull # in case you've checked it out before
+
+git checkout release/v1.1.22 # checkout to the latest release branch: `develop` will most likely be incompatible with deployed public networks
+
+cargo build --release # build your binaries with **mainnet** configuration
+NETWORK=sandbox cargo build --release # build your binaries with **sandbox** configuration
+
+

Quite a bit of stuff gets built. The key working parts are:

+ +

The repository also contains Typescript applications which aren’t built in this process. These can be built by following the instructions on their respective docs pages.

+ +
+

You cannot build from GitHub’s .zip or .tar.gz archive files on the releases page - the Nym build scripts automatically include the current git commit hash in the built binary during compilation, so the build will fail if you use the archive code (which isn’t a Git repository). Check the code out from github using git clone instead.

+
+ +
+ + +
+
+ + + +
+ + + + + + + + + + + + + + + + + + + + + +
+ + diff --git a/documentation/docs/src/binaries/init-and-config.html b/documentation/docs/src/binaries/init-and-config.html new file mode 100644 index 0000000000..147c89cc2d --- /dev/null +++ b/documentation/docs/src/binaries/init-and-config.html @@ -0,0 +1,290 @@ + + + + + + Binary Initialisation and Configuration - Nym Operators Guides + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + +
+ +
+ + + + + + + + +
+
+ {comment} +
+ + {comment} +
+ + {comment} +
+ + {comment} +
+ + {comment} +
+ + {comment} +
+ + {comment} +
+ + {comment} +
+ + {comment} +
+ + {comment} +
+ + {comment} +
+ + {comment} +
+ + {comment} +
+ + {comment} +
+ + {comment} +
+ + {comment} +
+ + {comment} +
+ + {comment} +
+ + {comment} +
+ + {comment} +
+ + {comment} +
+ +

Binary Initialisation and Configuration

+

All Nym binaries must first be initialised with init before being run.

+

The init command is usually where you pass flags specifying configuration arguments such as the gateway you wish to communicate with, the ports you wish your binary to listen on, etc.

+

The init command will also create the necessary keypairs and configuration files at ~/.nym/<BINARY_TYPE>/<BINARY_ID>/ if these files do not already exist. It will not overwrite existing keypairs if they are present.

+

You can reconfigure your binaries at any time by editing the config file located at ~/.nym/<BINARY_TYPE>/<BINARY_ID>/config/config.toml and restarting the binary process.

+

Once you have run init, you can start your binary with the run command, usually only accompanied by the id of the binary that you specified.

+

This id is never transmitted over the network, and is used to select which local config and key files to use for startup.

+ +
+ + +
+
+ + + +
+ + + + + + + + + + + + + + + + + + + + + +
+ + diff --git a/documentation/docs/src/binaries/pre-built-binaries.html b/documentation/docs/src/binaries/pre-built-binaries.html new file mode 100644 index 0000000000..3497c15d85 --- /dev/null +++ b/documentation/docs/src/binaries/pre-built-binaries.html @@ -0,0 +1,286 @@ + + + + + + Pre-built Binaries - Nym Operators Guides + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + +
+ +
+ + + + + + + + +
+
+ {comment} +
+ + {comment} +
+ + {comment} +
+ + {comment} +
+ + {comment} +
+ + {comment} +
+ + {comment} +
+ + {comment} +
+ + {comment} +
+ + {comment} +
+ + {comment} +
+ + {comment} +
+ + {comment} +
+ + {comment} +
+ + {comment} +
+ + {comment} +
+ + {comment} +
+ + {comment} +
+ + {comment} +
+ + {comment} +
+ + {comment} +
+ +

Pre-built Binaries

+

The Github releases page has pre-built binaries which should work on Ubuntu 20.04 and other Debian-based systems, but at this stage cannot be guaranteed to work everywhere.

+

If the pre-built binaries don’t work or are unavailable for your system, you will need to build the platform yourself.

+ +
+ + +
+
+ + + +
+ + + + + + + + + + + + + + + + + + + + + +
+ + From 20d51437b59750ff1ece9ee53639229183234578 Mon Sep 17 00:00:00 2001 From: serinko <97586125+serinko@users.noreply.github.com> Date: Tue, 1 Aug 2023 16:30:07 +0200 Subject: [PATCH 038/361] reinitialized the book - simplified book.toml --- documentation/operators/.gitignore | 26 -- documentation/operators/book.toml | 120 +----- documentation/operators/mdbook-admonish.css | 352 ------------------ documentation/operators/src/faq.md | 17 - .../operators/src/nodes/gateway-setup.md | 4 - .../src/nodes/network-requester-setup.md | 4 - .../operators/src/nodes/setup-guides.md | 7 - .../operators/src/nodes/validator-setup.md | 8 - 8 files changed, 1 insertion(+), 537 deletions(-) delete mode 100644 documentation/operators/mdbook-admonish.css diff --git a/documentation/operators/.gitignore b/documentation/operators/.gitignore index e84b38bdfb..7585238efe 100644 --- a/documentation/operators/.gitignore +++ b/documentation/operators/.gitignore @@ -1,27 +1 @@ -# mdbook files book -book/* -theme/index.hbs -theme/pagetoc.css -theme/pagetoc.js -theme/* -theme - -# --------------- -# Compiled assets -# --------------- -.sass-cache -_site - -# ---------- -# Developing -# ---------- -todo.md -.vscode - -# OSX -.DS_Store - -theme/ -theme -theme/* diff --git a/documentation/operators/book.toml b/documentation/operators/book.toml index 4202abc3f9..45ffb514ae 100644 --- a/documentation/operators/book.toml +++ b/documentation/operators/book.toml @@ -1,125 +1,7 @@ [book] -title = "Nym Operators Guides" +title = "Nym Operators Guide" authors = ["Max Hampshire", "serinko"] description = "Guides to setup Nym infrastructure" language = "en" multilingual = false # for the moment - ideally work on chinese, brazillian, spanish next src = "src" - -################# -# PREPROCESSORS # -################# - -# Note if changing these fields: -# - make sure you aren't running `mdbook serve` -# - change value of `turn-off = true` to `false`. This is set to `true` usually to avoid a known constant-reload bug with `mdbook serve` and this preprocessor -# - delete `./theme/` dir -# - change the preprocessor values that you want to edit -# - run `mdbook build`: this will rebuild the `./theme` directory -# - change value of `turn-off` back to `true` -[preprocessor.theme] -pagetoc = true -# some variables related (defined in theme/css/variables.css) -pagetoc-width = "13%" -content-max-width = "70%" # 82 -pagetoc-fontsize = "14.5px" -sidebar-width = "300px" -menu-bar-height = "40px" # memu-bar = the bar on the top -page-padding = "20px" -mobile-content-max-width = "98%" - -# layout -content-padding = "0 10px" -content-main-margin-left = "10%" -content-main-margin-right = "10%" -nav-chapters-max-width = "auto" -nav-chapters-min-width = "auto" -chapter-line-height = "2em" -section-line-height = "1.5em" - -# if true, never read and touch the files in theme dir: this is used to stop the looping reload issue referred to in the readme -turn-off = false - -[preprocessor.admonish] -command = "mdbook-admonish" -assets_version = "2.0.0" # do not edit: managed by `mdbook-admonish install` - -# variables preprocessor: import variables into files -# https://gitlab.com/tglman/mdbook-variables/ -[preprocessor.variables.variables] -minimum_rust_version = "1.66" -# vars for links: TODO think on how to streamline updating -platform_release_version = "v1.1.22" -wallet_release_version = "v1.2.5" - - -# [preprocessor.last-changed] -# command = "mdbook-last-changed" -# renderer = ["html"] - - -# used for grabbing output of binary commands for automation -# https://github.com/FauconFan/mdbook-cmdrun -[preprocessor.cmdrun] - -# more pre-processor plugins to look into from https://github.com/rust-lang/mdBook/wiki/Third-party-plugins & https://lib.rs/keywords/mdbook-preprocessor -# mdbook-i18n - -######### -# BUILD # -######### - -[build] -build-dir = "book" # the directory where the output is placed -create-missing = true # whether or not to create missing pages -use-default-preprocessors = true # use the default preprocessors -extra-watch-dirs = [] # directories to watch for triggering builds - -########## -# OUTPUT # -########## - -[output.html] -theme = "theme" # this is the default if not explicitly set -default-theme = "coal" -preferred-dark-theme = "coal" -curly-quotes = true -# mathjax-support = false # useful if we want to pull equations in -copy-fonts = true -no-section-label = false -additional-css = ["theme/pagetoc.css", "././mdbook-admonish.css"] #,"last-changed.css"] -additional-js = ["theme/pagetoc.js"] -git-repository-url = "https://github.com/nymtech/nym" -git-repository-icon = "fa-github" -# edit-url-template = "https://github.com/rust-lang/mdBook/edit/master/guide/{path}" -# site-url = "/docs/" -# cname = "nymtech.net" -# input-404 = "not-found.md" # todo make custom 404 page - -[output.html.fold] -enable = true # whether or not to enable section folding -level = 0 # the depth to start folding - -# controlling rust sample code blocks -[output.html.playground] -editable = false # allows editing the source code -copyable = true # include the copy button for copying code snippets -copy-js = true # includes the JavaScript for the code editor -line-numbers = false # displays line numbers for editable code -runnable = true # displays a run button for rust code - -# options for the built in text search -[output.html.search] -enable = true # enables the search feature -limit-results = 30 # maximum number of search results -teaser-word-count = 30 # number of words used for a search result teaser -use-boolean-and = true # multiple search terms must all match -boost-title = 2 # ranking boost factor for matches in headers -boost-hierarchy = 1 # ranking boost factor for matches in page names -boost-paragraph = 1 # ranking boost factor for matches in text -expand = true # partial words will match longer terms -heading-split-level = 3 # link results to heading levels -copy-js = true # include Javascript code for search - -# [output.linkcheck] -# warning-policy = "warn" diff --git a/documentation/operators/mdbook-admonish.css b/documentation/operators/mdbook-admonish.css deleted file mode 100644 index 5e360387df..0000000000 --- a/documentation/operators/mdbook-admonish.css +++ /dev/null @@ -1,352 +0,0 @@ -@charset "UTF-8"; -:root { - --md-admonition-icon--note: - url("data:image/svg+xml;charset=utf-8,"); - --md-admonition-icon--abstract: - url("data:image/svg+xml;charset=utf-8,"); - --md-admonition-icon--info: - url("data:image/svg+xml;charset=utf-8,"); - --md-admonition-icon--tip: - url("data:image/svg+xml;charset=utf-8,"); - --md-admonition-icon--success: - url("data:image/svg+xml;charset=utf-8,"); - --md-admonition-icon--question: - url("data:image/svg+xml;charset=utf-8,"); - --md-admonition-icon--warning: - url("data:image/svg+xml;charset=utf-8,"); - --md-admonition-icon--failure: - url("data:image/svg+xml;charset=utf-8,"); - --md-admonition-icon--danger: - url("data:image/svg+xml;charset=utf-8,"); - --md-admonition-icon--bug: - url("data:image/svg+xml;charset=utf-8,"); - --md-admonition-icon--example: - url("data:image/svg+xml;charset=utf-8,"); - --md-admonition-icon--quote: - url("data:image/svg+xml;charset=utf-8,"); - --md-details-icon: - url("data:image/svg+xml;charset=utf-8,"); -} - -:is(.admonition) { - display: flow-root; - margin: 1.5625em 0; - padding: 0 1.2rem; - color: var(--fg); - page-break-inside: avoid; - background-color: var(--bg); - border: 0 solid black; - border-inline-start-width: 0.4rem; - border-radius: 0.2rem; - box-shadow: 0 0.2rem 1rem rgba(0, 0, 0, 0.05), 0 0 0.1rem rgba(0, 0, 0, 0.1); -} -@media print { - :is(.admonition) { - box-shadow: none; - } -} -:is(.admonition) > * { - box-sizing: border-box; -} -:is(.admonition) :is(.admonition) { - margin-top: 1em; - margin-bottom: 1em; -} -:is(.admonition) > .tabbed-set:only-child { - margin-top: 0; -} -html :is(.admonition) > :last-child { - margin-bottom: 1.2rem; -} - -a.admonition-anchor-link { - display: none; - position: absolute; - left: -1.2rem; - padding-right: 1rem; -} -a.admonition-anchor-link:link, a.admonition-anchor-link:visited { - color: var(--fg); -} -a.admonition-anchor-link:link:hover, a.admonition-anchor-link:visited:hover { - text-decoration: none; -} -a.admonition-anchor-link::before { - content: "§"; -} - -:is(.admonition-title, summary) { - position: relative; - margin-block: 0; - margin-inline: -1.6rem -1.2rem; - padding-block: 0.8rem; - padding-inline: 4.4rem 1.2rem; - font-weight: 700; - background-color: rgba(68, 138, 255, 0.1); - display: flex; -} -:is(.admonition-title, summary) p { - margin: 0; -} -html :is(.admonition-title, summary):last-child { - margin-bottom: 0; -} -:is(.admonition-title, summary)::before { - position: absolute; - top: 0.625em; - inset-inline-start: 1.6rem; - width: 2rem; - height: 2rem; - background-color: #448aff; - mask-image: url('data:image/svg+xml;charset=utf-8,'); - -webkit-mask-image: url('data:image/svg+xml;charset=utf-8,'); - mask-repeat: no-repeat; - -webkit-mask-repeat: no-repeat; - mask-size: contain; - -webkit-mask-size: contain; - content: ""; -} -:is(.admonition-title, summary):hover a.admonition-anchor-link { - display: initial; -} - -details.admonition > summary.admonition-title::after { - position: absolute; - top: 0.625em; - inset-inline-end: 1.6rem; - height: 2rem; - width: 2rem; - background-color: currentcolor; - mask-image: var(--md-details-icon); - -webkit-mask-image: var(--md-details-icon); - mask-repeat: no-repeat; - -webkit-mask-repeat: no-repeat; - mask-size: contain; - -webkit-mask-size: contain; - content: ""; - transform: rotate(0deg); - transition: transform 0.25s; -} -details[open].admonition > summary.admonition-title::after { - transform: rotate(90deg); -} - -:is(.admonition):is(.note) { - border-color: #448aff; -} - -:is(.note) > :is(.admonition-title, summary) { - background-color: rgba(68, 138, 255, 0.1); -} -:is(.note) > :is(.admonition-title, summary)::before { - background-color: #448aff; - mask-image: var(--md-admonition-icon--note); - -webkit-mask-image: var(--md-admonition-icon--note); - mask-repeat: no-repeat; - -webkit-mask-repeat: no-repeat; - mask-size: contain; - -webkit-mask-repeat: no-repeat; -} - -:is(.admonition):is(.abstract, .summary, .tldr) { - border-color: #00b0ff; -} - -:is(.abstract, .summary, .tldr) > :is(.admonition-title, summary) { - background-color: rgba(0, 176, 255, 0.1); -} -:is(.abstract, .summary, .tldr) > :is(.admonition-title, summary)::before { - background-color: #00b0ff; - mask-image: var(--md-admonition-icon--abstract); - -webkit-mask-image: var(--md-admonition-icon--abstract); - mask-repeat: no-repeat; - -webkit-mask-repeat: no-repeat; - mask-size: contain; - -webkit-mask-repeat: no-repeat; -} - -:is(.admonition):is(.info, .todo) { - border-color: #00b8d4; -} - -:is(.info, .todo) > :is(.admonition-title, summary) { - background-color: rgba(0, 184, 212, 0.1); -} -:is(.info, .todo) > :is(.admonition-title, summary)::before { - background-color: #00b8d4; - mask-image: var(--md-admonition-icon--info); - -webkit-mask-image: var(--md-admonition-icon--info); - mask-repeat: no-repeat; - -webkit-mask-repeat: no-repeat; - mask-size: contain; - -webkit-mask-repeat: no-repeat; -} - -:is(.admonition):is(.tip, .hint, .important) { - border-color: #00bfa5; -} - -:is(.tip, .hint, .important) > :is(.admonition-title, summary) { - background-color: rgba(0, 191, 165, 0.1); -} -:is(.tip, .hint, .important) > :is(.admonition-title, summary)::before { - background-color: #00bfa5; - mask-image: var(--md-admonition-icon--tip); - -webkit-mask-image: var(--md-admonition-icon--tip); - mask-repeat: no-repeat; - -webkit-mask-repeat: no-repeat; - mask-size: contain; - -webkit-mask-repeat: no-repeat; -} - -:is(.admonition):is(.success, .check, .done) { - border-color: #00c853; -} - -:is(.success, .check, .done) > :is(.admonition-title, summary) { - background-color: rgba(0, 200, 83, 0.1); -} -:is(.success, .check, .done) > :is(.admonition-title, summary)::before { - background-color: #00c853; - mask-image: var(--md-admonition-icon--success); - -webkit-mask-image: var(--md-admonition-icon--success); - mask-repeat: no-repeat; - -webkit-mask-repeat: no-repeat; - mask-size: contain; - -webkit-mask-repeat: no-repeat; -} - -:is(.admonition):is(.question, .help, .faq) { - border-color: #64dd17; -} - -:is(.question, .help, .faq) > :is(.admonition-title, summary) { - background-color: rgba(100, 221, 23, 0.1); -} -:is(.question, .help, .faq) > :is(.admonition-title, summary)::before { - background-color: #64dd17; - mask-image: var(--md-admonition-icon--question); - -webkit-mask-image: var(--md-admonition-icon--question); - mask-repeat: no-repeat; - -webkit-mask-repeat: no-repeat; - mask-size: contain; - -webkit-mask-repeat: no-repeat; -} - -:is(.admonition):is(.warning, .caution, .attention) { - border-color: #ff9100; -} - -:is(.warning, .caution, .attention) > :is(.admonition-title, summary) { - background-color: rgba(255, 145, 0, 0.1); -} -:is(.warning, .caution, .attention) > :is(.admonition-title, summary)::before { - background-color: #ff9100; - mask-image: var(--md-admonition-icon--warning); - -webkit-mask-image: var(--md-admonition-icon--warning); - mask-repeat: no-repeat; - -webkit-mask-repeat: no-repeat; - mask-size: contain; - -webkit-mask-repeat: no-repeat; -} - -:is(.admonition):is(.failure, .fail, .missing) { - border-color: #ff5252; -} - -:is(.failure, .fail, .missing) > :is(.admonition-title, summary) { - background-color: rgba(255, 82, 82, 0.1); -} -:is(.failure, .fail, .missing) > :is(.admonition-title, summary)::before { - background-color: #ff5252; - mask-image: var(--md-admonition-icon--failure); - -webkit-mask-image: var(--md-admonition-icon--failure); - mask-repeat: no-repeat; - -webkit-mask-repeat: no-repeat; - mask-size: contain; - -webkit-mask-repeat: no-repeat; -} - -:is(.admonition):is(.danger, .error) { - border-color: #ff1744; -} - -:is(.danger, .error) > :is(.admonition-title, summary) { - background-color: rgba(255, 23, 68, 0.1); -} -:is(.danger, .error) > :is(.admonition-title, summary)::before { - background-color: #ff1744; - mask-image: var(--md-admonition-icon--danger); - -webkit-mask-image: var(--md-admonition-icon--danger); - mask-repeat: no-repeat; - -webkit-mask-repeat: no-repeat; - mask-size: contain; - -webkit-mask-repeat: no-repeat; -} - -:is(.admonition):is(.bug) { - border-color: #f50057; -} - -:is(.bug) > :is(.admonition-title, summary) { - background-color: rgba(245, 0, 87, 0.1); -} -:is(.bug) > :is(.admonition-title, summary)::before { - background-color: #f50057; - mask-image: var(--md-admonition-icon--bug); - -webkit-mask-image: var(--md-admonition-icon--bug); - mask-repeat: no-repeat; - -webkit-mask-repeat: no-repeat; - mask-size: contain; - -webkit-mask-repeat: no-repeat; -} - -:is(.admonition):is(.example) { - border-color: #7c4dff; -} - -:is(.example) > :is(.admonition-title, summary) { - background-color: rgba(124, 77, 255, 0.1); -} -:is(.example) > :is(.admonition-title, summary)::before { - background-color: #7c4dff; - mask-image: var(--md-admonition-icon--example); - -webkit-mask-image: var(--md-admonition-icon--example); - mask-repeat: no-repeat; - -webkit-mask-repeat: no-repeat; - mask-size: contain; - -webkit-mask-repeat: no-repeat; -} - -:is(.admonition):is(.quote, .cite) { - border-color: #9e9e9e; -} - -:is(.quote, .cite) > :is(.admonition-title, summary) { - background-color: rgba(158, 158, 158, 0.1); -} -:is(.quote, .cite) > :is(.admonition-title, summary)::before { - background-color: #9e9e9e; - mask-image: var(--md-admonition-icon--quote); - -webkit-mask-image: var(--md-admonition-icon--quote); - mask-repeat: no-repeat; - -webkit-mask-repeat: no-repeat; - mask-size: contain; - -webkit-mask-repeat: no-repeat; -} - -.navy :is(.admonition) { - background-color: var(--sidebar-bg); -} - -.ayu :is(.admonition), .coal :is(.admonition) { - background-color: var(--theme-hover); -} - -.rust :is(.admonition) { - background-color: var(--sidebar-bg); - color: var(--sidebar-fg); -} -.rust .admonition-anchor-link:link, .rust .admonition-anchor-link:visited { - color: var(--sidebar-fg); -} diff --git a/documentation/operators/src/faq.md b/documentation/operators/src/faq.md index faefd70b18..bc3c210352 100644 --- a/documentation/operators/src/faq.md +++ b/documentation/operators/src/faq.md @@ -1,20 +1,3 @@ - # Frequently Asked Questions ## Mixnet nodes diff --git a/documentation/operators/src/nodes/gateway-setup.md b/documentation/operators/src/nodes/gateway-setup.md index 31cff54c22..026e7afde0 100644 --- a/documentation/operators/src/nodes/gateway-setup.md +++ b/documentation/operators/src/nodes/gateway-setup.md @@ -1,7 +1,3 @@ - # Gateways > The Nym gateway was built in the [building nym](../binaries/building-nym.md) section. If you haven't yet built Nym and want to run the code, go there first. diff --git a/documentation/operators/src/nodes/network-requester-setup.md b/documentation/operators/src/nodes/network-requester-setup.md index 1cfee00e9b..049b139de6 100644 --- a/documentation/operators/src/nodes/network-requester-setup.md +++ b/documentation/operators/src/nodes/network-requester-setup.md @@ -1,7 +1,3 @@ - # Network Requesters > The Nym gateway was built in the [building nym](../binaries/building-nym.md) section. If you haven't yet built Nym and want to run the code, go there first. diff --git a/documentation/operators/src/nodes/setup-guides.md b/documentation/operators/src/nodes/setup-guides.md index 37955a6549..ca86dd99f4 100644 --- a/documentation/operators/src/nodes/setup-guides.md +++ b/documentation/operators/src/nodes/setup-guides.md @@ -1,10 +1,3 @@ - # Node Setup Guides To setup any type of Nym's node, start with building [Nym's platform](../binaries/building-nym.md) on the machine (VPS) where you want to run the node. Nodes will need to be bond to Nym's wallet, setup one [here](https://nymtech.net/docs/wallet/desktop-wallet.html). diff --git a/documentation/operators/src/nodes/validator-setup.md b/documentation/operators/src/nodes/validator-setup.md index 1c843a4cab..9e233808d7 100644 --- a/documentation/operators/src/nodes/validator-setup.md +++ b/documentation/operators/src/nodes/validator-setup.md @@ -1,11 +1,3 @@ - # Validators > Nym has two main codebases: From 7ac3bb10cbcfbaf6b724d150fe792f6237c6e074 Mon Sep 17 00:00:00 2001 From: serinko <97586125+serinko@users.noreply.github.com> Date: Tue, 1 Aug 2023 16:43:57 +0200 Subject: [PATCH 039/361] reinit the book, delete *.html in /src --- .../operators/src/binaries/building-nym.html | 350 ------------------ .../src/binaries/init-and-config.html | 290 --------------- .../src/binaries/pre-built-binaries.html | 286 -------------- 3 files changed, 926 deletions(-) delete mode 100644 documentation/operators/src/binaries/building-nym.html delete mode 100644 documentation/operators/src/binaries/init-and-config.html delete mode 100644 documentation/operators/src/binaries/pre-built-binaries.html diff --git a/documentation/operators/src/binaries/building-nym.html b/documentation/operators/src/binaries/building-nym.html deleted file mode 100644 index 2f73af4b4f..0000000000 --- a/documentation/operators/src/binaries/building-nym.html +++ /dev/null @@ -1,350 +0,0 @@ - - - - - - Building from Source - Nym Operators Guides - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - -
- -
- - - - - - - - -
-
- {comment} -
- - {comment} -
- - {comment} -
- - {comment} -
- - {comment} -
- - {comment} -
- - {comment} -
- - {comment} -
- - {comment} -
- - {comment} -
- - {comment} -
- - {comment} -
- - {comment} -
- - {comment} -
- - {comment} -
- - {comment} -
- - {comment} -
- - {comment} -
- - {comment} -
- - {comment} -
- - {comment} -
- -

Building from Source

-
-

Nym runs on Mac OS X, Linux, and Windows. All nodes except the Desktop Wallet and NymConnect on Windows should be considered experimental - it works fine if you’re an app developer but isn’t recommended for running nodes.

-
-

Building Nym

-

Nym has two main codebases:

-
    -
  • the Nym platform, written in Rust. This contains all of our code except for the validators.
  • -
  • the Nym validators, written in Go.
  • -
-
-

This page details how to build the main Nym platform code. If you want to build and run a validator, go here instead.

-
-

Prerequisites

-
    -
  • Debian/Ubuntu: pkg-config, build-essential, libssl-dev, curl, jq, git
  • -
-
apt install pkg-config build-essential libssl-dev curl jq git
-
-
    -
  • Arch/Manjaro: base-devel
  • -
-
pacman -S base-devel
-
-
    -
  • Mac OS X: pkg-config , brew, openss1, protobuf, curl, git -Running the following the script installs Homebrew and the above dependencies:
  • -
-
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
-
-
    -
  • Rust & cargo >= 1.66
  • -
-

We recommend using the Rust shell script installer. Installing cargo from your package manager (e.g. apt) is not recommended as the packaged versions are usually too old.

-

If you really don’t want to use the shell script installer, the Rust installation docs contain instructions for many platforms.

-

Download and build Nym binaries

-

The following commands will compile binaries into the nym/target/release directory:

-
rustup update
-git clone https://github.com/nymtech/nym.git
-cd nym
-
-git reset --hard # in case you made any changes on your branch
-git pull # in case you've checked it out before
-
-git checkout release/v1.1.22 # checkout to the latest release branch: `develop` will most likely be incompatible with deployed public networks
-
-cargo build --release # build your binaries with **mainnet** configuration
-NETWORK=sandbox cargo build --release # build your binaries with **sandbox** configuration
-
-

Quite a bit of stuff gets built. The key working parts are:

- -

The repository also contains Typescript applications which aren’t built in this process. These can be built by following the instructions on their respective docs pages.

- -
-

You cannot build from GitHub’s .zip or .tar.gz archive files on the releases page - the Nym build scripts automatically include the current git commit hash in the built binary during compilation, so the build will fail if you use the archive code (which isn’t a Git repository). Check the code out from github using git clone instead.

-
- -
- - -
-
- - - -
- - - - - - - - - - - - - - - - - - - - - -
- - diff --git a/documentation/operators/src/binaries/init-and-config.html b/documentation/operators/src/binaries/init-and-config.html deleted file mode 100644 index 147c89cc2d..0000000000 --- a/documentation/operators/src/binaries/init-and-config.html +++ /dev/null @@ -1,290 +0,0 @@ - - - - - - Binary Initialisation and Configuration - Nym Operators Guides - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - -
- -
- - - - - - - - -
-
- {comment} -
- - {comment} -
- - {comment} -
- - {comment} -
- - {comment} -
- - {comment} -
- - {comment} -
- - {comment} -
- - {comment} -
- - {comment} -
- - {comment} -
- - {comment} -
- - {comment} -
- - {comment} -
- - {comment} -
- - {comment} -
- - {comment} -
- - {comment} -
- - {comment} -
- - {comment} -
- - {comment} -
- -

Binary Initialisation and Configuration

-

All Nym binaries must first be initialised with init before being run.

-

The init command is usually where you pass flags specifying configuration arguments such as the gateway you wish to communicate with, the ports you wish your binary to listen on, etc.

-

The init command will also create the necessary keypairs and configuration files at ~/.nym/<BINARY_TYPE>/<BINARY_ID>/ if these files do not already exist. It will not overwrite existing keypairs if they are present.

-

You can reconfigure your binaries at any time by editing the config file located at ~/.nym/<BINARY_TYPE>/<BINARY_ID>/config/config.toml and restarting the binary process.

-

Once you have run init, you can start your binary with the run command, usually only accompanied by the id of the binary that you specified.

-

This id is never transmitted over the network, and is used to select which local config and key files to use for startup.

- -
- - -
-
- - - -
- - - - - - - - - - - - - - - - - - - - - -
- - diff --git a/documentation/operators/src/binaries/pre-built-binaries.html b/documentation/operators/src/binaries/pre-built-binaries.html deleted file mode 100644 index 3497c15d85..0000000000 --- a/documentation/operators/src/binaries/pre-built-binaries.html +++ /dev/null @@ -1,286 +0,0 @@ - - - - - - Pre-built Binaries - Nym Operators Guides - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - -
- -
- - - - - - - - -
-
- {comment} -
- - {comment} -
- - {comment} -
- - {comment} -
- - {comment} -
- - {comment} -
- - {comment} -
- - {comment} -
- - {comment} -
- - {comment} -
- - {comment} -
- - {comment} -
- - {comment} -
- - {comment} -
- - {comment} -
- - {comment} -
- - {comment} -
- - {comment} -
- - {comment} -
- - {comment} -
- - {comment} -
- -

Pre-built Binaries

-

The Github releases page has pre-built binaries which should work on Ubuntu 20.04 and other Debian-based systems, but at this stage cannot be guaranteed to work everywhere.

-

If the pre-built binaries don’t work or are unavailable for your system, you will need to build the platform yourself.

- -
- - -
-
- - - -
- - - - - - - - - - - - - - - - - - - - - -
- - From e97a068bf0ae341b1d1a716f8aafb018815feec5 Mon Sep 17 00:00:00 2001 From: Fouad Date: Tue, 1 Aug 2023 16:21:12 +0100 Subject: [PATCH 040/361] Type documentation for NymSDK (#3701) * set up development process for docs * set up development process for docs * Add local installs for Typedoc on gitignore * Add Typedoc comments on types.ts file * add typedoc config file * update types and add annotations * Add updates on types file * add examples + manage sort order for doc items * update client methods with examples * add description of NymMixnetClientOptions * add description of NymMixnetClientOptions * fix linting --------- Co-authored-by: Lorexia --- sdk/typescript/packages/sdk/.gitignore | 3 +- .../packages/sdk/README-GenerateDocs.md | 17 + sdk/typescript/packages/sdk/package.json | 18 +- .../packages/sdk/src/coconut/index.ts | 5 + .../sdk/src/mixnet/node-tester/constants.ts | 5 - .../sdk/src/mixnet/node-tester/types.ts | 30 -- .../packages/sdk/src/mixnet/wasm/index.ts | 42 ++- .../packages/sdk/src/mixnet/wasm/types.ts | 309 +++++++++++++++--- sdk/typescript/packages/sdk/typedoc.json | 32 ++ yarn.lock | 215 +++++++++++- 10 files changed, 570 insertions(+), 106 deletions(-) create mode 100644 sdk/typescript/packages/sdk/README-GenerateDocs.md delete mode 100644 sdk/typescript/packages/sdk/src/mixnet/node-tester/constants.ts create mode 100644 sdk/typescript/packages/sdk/typedoc.json diff --git a/sdk/typescript/packages/sdk/.gitignore b/sdk/typescript/packages/sdk/.gitignore index 6166d37c02..ed481bbc45 100644 --- a/sdk/typescript/packages/sdk/.gitignore +++ b/sdk/typescript/packages/sdk/.gitignore @@ -1,2 +1,3 @@ src/mixnet/wasm/worker.js -src/mixnet/node-tester/worker.js \ No newline at end of file +src/mixnet/node-tester/worker.js +docs/ \ No newline at end of file diff --git a/sdk/typescript/packages/sdk/README-GenerateDocs.md b/sdk/typescript/packages/sdk/README-GenerateDocs.md new file mode 100644 index 0000000000..7458ba1069 --- /dev/null +++ b/sdk/typescript/packages/sdk/README-GenerateDocs.md @@ -0,0 +1,17 @@ +## Install dependencies + +run `yarn install` to install dependencies + +## Generate docs + +We use TypeDoc for document generation https://typedoc.org/ + +To generate docs run `yarn docs:generate`. Generated docs can be found the `./docs` directory. + +To view the generated docs in a webpage run `yarn docs:serve`. The docs will be available to view at `http://localhost:3000`. + +## Local document development + +To support the development process we have a local server that will watch for changes to the src files and update the docs in real time. + +Run `yarn docs:dev` to start a local server to view the docs. Again, The docs will be available to view at `http://localhost:3000`. diff --git a/sdk/typescript/packages/sdk/package.json b/sdk/typescript/packages/sdk/package.json index c156bd5876..17d2c9d116 100644 --- a/sdk/typescript/packages/sdk/package.json +++ b/sdk/typescript/packages/sdk/package.json @@ -28,11 +28,15 @@ "build:dev": "yarn build:dev:only-this", "build:dev:only-this": "scripts/build.sh", "build:local": "run-s build:dependencies:nym-client-wasm build:dev:only-this", + "docs:watch": "nodemon --ext ts --watch './src/**/*' --watch './typedoc.json' --exec \"yarn docs:generate\"", + "docs:generate": "typedoc", + "docs:serve": "reload -b -d ./docs -p 3000", + "docs:dev": "run-p docs:watch docs:serve ", "test": "node --experimental-vm-modules --no-warnings node_modules/jest/bin/jest.js -c=jest.config.mjs --no-cache" }, "dependencies": { "@npmcli/node-gyp": "^3.0.0", - "@nymproject/nym-client-wasm": "1.0.0", + "@nymproject/nym-client-wasm": "1", "comlink": "^4.3.1", "lerna": "^6.6.2", "node-gyp": "^9.3.1" @@ -52,10 +56,10 @@ "@rollup/plugin-terser": "^0.2.1", "@rollup/plugin-typescript": "^10.0.1", "@rollup/plugin-wasm": "^6.1.1", - "@typescript-eslint/eslint-plugin": "^5.13.0", - "@typescript-eslint/parser": "^5.13.0", "@types/jest": "^27.0.1", "@types/node": "^16.7.13", + "@typescript-eslint/eslint-plugin": "^5.13.0", + "@typescript-eslint/parser": "^5.13.0", "eslint": "^8.10.0", "eslint-config-airbnb": "^19.0.4", "eslint-config-airbnb-typescript": "^16.1.0", @@ -67,13 +71,15 @@ "eslint-plugin-prettier": "^4.0.0", "eslint-plugin-react": "^7.29.2", "eslint-plugin-react-hooks": "^4.3.0", + "jest": "^29.5.0", + "nodemon": "3.0.1", + "reload": "^3.2.1", "rimraf": "^3.0.2", "rollup": "^3.9.1", "rollup-plugin-base64": "^1.0.1", "rollup-plugin-web-worker-loader": "^1.6.1", - "typescript": "^4.8.4", - "jest": "^29.5.0", "ts-jest": "^29.1.0", - "ts-loader": "^9.4.2" + "typedoc": "^0.24.8", + "typescript": "^4.8.4" } } diff --git a/sdk/typescript/packages/sdk/src/coconut/index.ts b/sdk/typescript/packages/sdk/src/coconut/index.ts index 02138c9bab..3011daa2e0 100644 --- a/sdk/typescript/packages/sdk/src/coconut/index.ts +++ b/sdk/typescript/packages/sdk/src/coconut/index.ts @@ -1,2 +1,7 @@ // eslint-disable-next-line no-console + +/** + * @ignore + * @internal + */ export const notImplementedYet = () => console.log('Not implement, coming soon...'); diff --git a/sdk/typescript/packages/sdk/src/mixnet/node-tester/constants.ts b/sdk/typescript/packages/sdk/src/mixnet/node-tester/constants.ts deleted file mode 100644 index 55770cd39b..0000000000 --- a/sdk/typescript/packages/sdk/src/mixnet/node-tester/constants.ts +++ /dev/null @@ -1,5 +0,0 @@ -const QA_VALIDATOR_URL = 'https://qa-nym-api.qa.nymte.ch/api'; -const QWERTY_VALIDATOR_URL = 'https://qwerty-validator-api.qa.nymte.ch/api'; -const MAINNET_VALIDATOR_URL = 'https://validator.nymtech.net/api/'; - -export { QA_VALIDATOR_URL, QWERTY_VALIDATOR_URL, MAINNET_VALIDATOR_URL }; diff --git a/sdk/typescript/packages/sdk/src/mixnet/node-tester/types.ts b/sdk/typescript/packages/sdk/src/mixnet/node-tester/types.ts index a556fec243..5bf1afb4d1 100644 --- a/sdk/typescript/packages/sdk/src/mixnet/node-tester/types.ts +++ b/sdk/typescript/packages/sdk/src/mixnet/node-tester/types.ts @@ -25,8 +25,6 @@ export interface NodeTesterLoadedEvent { }; } -export type Network = 'QA' | 'SANDBOX' | 'MAINNET'; - export type NodeTestResultResponse = { score: number; sentPackets: number; @@ -35,31 +33,3 @@ export type NodeTestResultResponse = { duplicatePackets: number; duplicateAcks: number; }; - -export type Error = { - kind: 'Error'; - args: { message: string }; -}; - -export type WorkerLoaded = { - kind: 'WorkerLoaded'; -}; - -export type DisplayTesterResults = { - kind: 'DisplayTesterResults'; - args: { - result: NodeTestResultResponse; - }; -}; - -export type TestPacket = { - kind: 'TestPacket'; - args: { - mixnodeIdentity: string; - network: Network; - }; -}; - -export type TestStatus = 'Stopped' | 'Running' | 'Complete'; - -export type NodeTestEvent = Error | DisplayTesterResults | TestPacket | WorkerLoaded; diff --git a/sdk/typescript/packages/sdk/src/mixnet/wasm/index.ts b/sdk/typescript/packages/sdk/src/mixnet/wasm/index.ts index 1b80d844e8..e124b7136c 100644 --- a/sdk/typescript/packages/sdk/src/mixnet/wasm/index.ts +++ b/sdk/typescript/packages/sdk/src/mixnet/wasm/index.ts @@ -5,8 +5,8 @@ import { ConnectedEvent, EventKinds, IWebWorker, - IWebWorkerAsync, - IWebWorkerEvents, + Client, + Events, LoadedEvent, MimeTypes, RawMessageReceivedEvent, @@ -15,20 +15,40 @@ import { import { createSubscriptions } from './subscriptions'; /** - * Client for the Nym mixnet. + * Options for the Nym mixnet client. + * @property autoConvertStringMimeTypes - An array of mime types. + * @example + * ```typescript + * const client = await createNymMixnetClient({ + * autoConvertStringMimeTypes: [MimeTypes.ApplicationJson, MimeTypes.TextPlain], + * }); + * ``` + */ + +export interface NymMixnetClientOptions { + autoConvertStringMimeTypes?: string[] | MimeTypes[]; +} + +/** + * The client for the Nym mixnet which gives access to client methods and event subscriptions. + * Returned by the {@link createNymMixnetClient} function. + * */ export interface NymMixnetClient { - client: IWebWorkerAsync; - events: IWebWorkerEvents; + client: Client; + events: Events; } /** * Create a client to send and receive traffic from the Nym mixnet. - * + * @required + * @returns + * @example + * ```typescript + * const client = await createNymMixnetClient(); + * ``` */ -export const createNymMixnetClient = async (options?: { - autoConvertStringMimeTypes?: string[] | MimeTypes[]; -}): Promise => { +export const createNymMixnetClient = async (options?: NymMixnetClientOptions): Promise => { // create a web worker that runs the WASM client on another thread and wait until it signals that it is ready // eslint-disable-next-line @typescript-eslint/no-use-before-define const worker = await createWorker(); @@ -53,7 +73,7 @@ export const createNymMixnetClient = async (options?: { }); // manage the subscribers, returning self-unsubscribe methods - const events: IWebWorkerEvents = { + const events: Events = { subscribeToConnected: (handler) => addSubscription(EventKinds.Connected, handler), subscribeToLoaded: (handler) => addSubscription(EventKinds.Loaded, handler), subscribeToTextMessageReceivedEvent: (handler) => @@ -65,7 +85,7 @@ export const createNymMixnetClient = async (options?: { }; // let comlink handle interop with the web worker - const client: IWebWorkerAsync = Comlink.wrap(worker); + const client: Client = Comlink.wrap(worker); // set any options if (options?.autoConvertStringMimeTypes) { diff --git a/sdk/typescript/packages/sdk/src/mixnet/wasm/types.ts b/sdk/typescript/packages/sdk/src/mixnet/wasm/types.ts index 1f11384b1a..7df48ba73b 100644 --- a/sdk/typescript/packages/sdk/src/mixnet/wasm/types.ts +++ b/sdk/typescript/packages/sdk/src/mixnet/wasm/types.ts @@ -3,32 +3,138 @@ import type { DebugWasm } from './types-from-wasm-pack'; export * from './types-from-wasm-pack'; /** - * Some common mime types, however, you can always just specify the mime-type as a string + * + * @ignore + * @hidden + * @internal */ -export enum MimeTypes { - ApplicationOctetStream = 'application/octet-stream', - TextPlain = 'text/plain', - ApplicationJson = 'application/json', +export interface IWebWorker { + start: (config: NymClientConfig) => void; + stop: () => void; + selfAddress: () => string | undefined; + setTextMimeTypes: (mimeTypes: string[]) => void; + getTextMimeTypes: () => string[]; + send: (args: { payload: Payload; recipient: string; replySurbs?: number }) => void; + rawSend: (args: { payload: Uint8Array; recipient: string; replySurbs?: number }) => void; } -export interface Payload { - message: string | Uint8Array; - - mimeType?: MimeTypes | string; - - headers?: string; +export interface Client { + /** + * Start the client. + * + * @example + * + * ```typescript + * const client = await createNymMixnetClient(); + * await client.start({ + * clientId: 'my-client', + * nymApiUrl: 'https://validator.nymtech.net/api', + * }); + * + */ + start: (config: NymClientConfig) => Promise; + /** + * Stop the client. + * @example + * ```typescript + * const client = await createNymMixnetClient(); + * await client.start({ + * clientId: 'my-client', + * nymApiUrl: 'https://validator.nymtech.net/api', + * }); + * await client.stop(); + * ``` + */ + stop: () => Promise; + /** + * Get the client address + * @example + * ```typescript + * const client = await createNymMixnetClient(); + * await client.start({ + * clientId: 'my-client', + * nymApiUrl: 'https://validator.nymtech.net/api', + * }); + * const address = await client.selfAddress(); + * ``` + */ + selfAddress: () => Promise; + /** + * Set the mime-types that should be used when using the {@link Client.send} method. + * @example + * ```typescript + * const client = await createNymMixnetClient(); + * await client.start({ + * clientId: 'my-client', + * nymApiUrl: 'https://validator.nymtech.net/api', + * }); + * await client.setTextMimeTypes(['text/plain', 'application/json']); + * ``` + * @param mimeTypes + * @see {@link MimeTypes} + * @see {@link Client.send} + * @see {@link Client.getTextMimeTypes} + */ + setTextMimeTypes: (mimeTypes: string[]) => void; + /** + * Get the mime-types that are automatically converted to strings. + * @example + * ```typescript + * const client = await createNymMixnetClient(); + * await client.start({ + * clientId: 'my-client', + * nymApiUrl: 'https://validator.nymtech.net/api', + * }); + * const mimeTypes = await client.getTextMimeTypes(); + * ``` + * @see {@link MimeTypes} + * @see {@link Payload} + * @see {@link Client.send} + * @see {@link Client.setTextMimeTypes} + */ + getTextMimeTypes: () => Promise; + /** + * Send some data through the mixnet message. + * @example + * ```typescript + * const client = await createNymMixnetClient(); + * await client.start({ + * clientId: 'my-client', + * nymApiUrl: 'https://validator.nymtech.net/api', + * }); + * await client.send({ + * payload: 'Hello world', + * recipient: // recipient address, + * }); + * ``` + * @see {@link MimeTypes} + * @see {@link Payload} + */ + send: (args: { payload: Payload; recipient: string; replySurbs?: number }) => Promise; + /** + * Send a raw payload, without any mime-type conversion. + * @example + * ```typescript + * const client = await createNymMixnetClient(); + * await client.start({ + * clientId: 'my-client', + * nymApiUrl: 'https://validator.nymtech.net/api', + * }); + * const payload = new Uint8Array([1, 2, 3]); + * await client.rawSend({ + * payload, + * recipient: // recipient address, + * }); + * ``` + * @see {@link MimeTypes} + * @see {@link Payload} + */ + rawSend: (args: { payload: Uint8Array; recipient: string; replySurbs?: number }) => Promise; } -export type OnPayloadFn = (payload: Payload) => void; - -export type OnRawPayloadFn = (payload: Uint8Array) => void; - -export type EventHandlerFn = (e: E) => void | Promise; - -export type EventHandlerSubscribeFn = (fn: EventHandlerFn) => EventHandlerUnsubscribeFn; - -export type EventHandlerUnsubscribeFn = () => void; - +/** + * The configuration passed to the {@link Client.start} method of the {@link Client} + */ export interface NymClientConfig { /** * A human-readable id for the client. @@ -56,31 +162,99 @@ export interface NymClientConfig { debug?: DebugWasm; } -export interface IWebWorker { - start: (config: NymClientConfig) => void; - stop: () => void; - selfAddress: () => string | undefined; - setTextMimeTypes: (mimeTypes: string[]) => void; - getTextMimeTypes: () => string[]; - send: (args: { payload: Payload; recipient: string; replySurbs?: number }) => void; - rawSend: (args: { payload: Uint8Array; recipient: string; replySurbs?: number }) => void; -} - -export interface IWebWorkerAsync { - start: (config: NymClientConfig) => Promise; - stop: () => Promise; - selfAddress: () => Promise; - setTextMimeTypes: (mimeTypes: string[]) => void; - getTextMimeTypes: () => Promise; - send: (args: { payload: Payload; recipient: string; replySurbs?: number }) => Promise; - rawSend: (args: { payload: Uint8Array; recipient: string; replySurbs?: number }) => Promise; +export interface Events { + /** + * @see {@link LoadedEvent} + * @example + * ```typescript + * events.subscribeToLoaded((e) => { + * console.log(e.args); // { loaded: true } + * }); + * ``` + */ + subscribeToLoaded: EventHandlerSubscribeFn; + /** + * @see {@link ConnectedEvent} + * @example + * ```typescript + * events.subscribeConnected((e) => { + * console.log(e.args.address); // Client address + * }); + * + */ + subscribeToConnected: EventHandlerSubscribeFn; + /** + * @returns {@link EventHandlerUnsubscribeFn} + * @see {@link StringMessageReceivedEvent} + * @example + * ```typescript + * const unsubscribe = events.subscribeToTextMessageReceivedEvent((e) => { + * console.log(e.args.payload); // string + * }); + * + * // Stop listening to the event + * unsubscribe(); + * ``` + */ + subscribeToTextMessageReceivedEvent: EventHandlerSubscribeFn; + /** + * @returns {@link EventHandlerUnsubscribeFn} + * @see {@link BinaryMessageReceivedEvent} + * @example + * ```typescript + * const unsubscribe = events.subscribeToBinaryMessageReceivedEvent((e) => { + * console.log(e.args.payload); // Uint8Array + * }); + * + * // Stop listening to the event + * unsubscribe(); + * ``` + */ + subscribeToBinaryMessageReceivedEvent: EventHandlerSubscribeFn; + /** + * @returns {@link EventHandlerUnsubscribeFn} + * @see {@link RawMessageReceivedEvent} + * @example + * ```typescript + * const unsubscribe = events.subscribeToRawMessageReceivedEvent((e) => { + * console.log(e.args.payload); // Uint8Array + * }); + * + * // Stop listening to the event + * unsubscribe(); + * ``` + */ + subscribeToRawMessageReceivedEvent: EventHandlerSubscribeFn; } +/** + * Enum representing various event kinds. + * @enum + */ export enum EventKinds { + /** + * The event emitted when the nodetester is ready to be used. + */ Loaded = 'Loaded', + + /** + * The event emitted when connection to the gateway is established. + */ Connected = 'Connected', + + /** + * The event for when a message is received and interpreted as a string. + */ StringMessageReceived = 'StringMessageReceived', + + /** + * The event for when a binary message is received. BinaryMessage is a type of message that contains additional metadata, such as MIME type and some headers, along with the actual payload data. + */ BinaryMessageReceived = 'BinaryMessageReceived', + + /** + * The event for when a raw message is received. RawMessage represents the bytes that are received directly from the mixnet with no further parsing or interpretation done on them. + */ RawMessageReceived = 'RawMessageReceived', } @@ -107,7 +281,6 @@ export interface StringMessageReceivedEvent { headers?: string; }; } - export interface BinaryMessageReceivedEvent { kind: EventKinds.BinaryMessageReceived; args: { @@ -124,10 +297,54 @@ export interface RawMessageReceivedEvent { }; } -export interface IWebWorkerEvents { - subscribeToLoaded: EventHandlerSubscribeFn; - subscribeToConnected: EventHandlerSubscribeFn; - subscribeToTextMessageReceivedEvent: EventHandlerSubscribeFn; - subscribeToBinaryMessageReceivedEvent: EventHandlerSubscribeFn; - subscribeToRawMessageReceivedEvent: EventHandlerSubscribeFn; +/** + * Some common mime types, however, you can always just specify the mime-type as a string + */ +export enum MimeTypes { + ApplicationOctetStream = 'application/octet-stream', + TextPlain = 'text/plain', + ApplicationJson = 'application/json', } + +export interface Payload { + message: string | Uint8Array; + mimeType?: MimeTypes | string; + headers?: string; +} + +/** + * @ignore + * @internal + */ +export type OnPayloadFn = (payload: Payload) => void; +/** + * @ignore + * @internal + */ +export type OnRawPayloadFn = (payload: Uint8Array) => void; + +/** + * The **EventHandlerSubscribeFn** is a function that takes a callback of type {@link EventHandlerFn} + * + * @see {@link Events} + * @see {@link EventHandlerFn} + * @see {@link EventHandlerUnsubscribeFn} + */ +export type EventHandlerSubscribeFn = (fn: EventHandlerFn) => EventHandlerUnsubscribeFn; + +/** + * The **EventHandlerFn** is a callback function that is passed to the {@link EventHandlerSubscribeFn} + * @see {@link Events} + * @see {@link EventHandlerFn} + * @see {@link EventHandlerSubscribeFn} + */ +export type EventHandlerFn = (e: E) => void | Promise; + +/** + * The **EventHandlerUnsubscribeFn** function is returned by the {@link EventHandlerSubscribeFn} + * and can be used to stop listening for particular events + * @see {@link Events} + * @see {@link EventHandlerFn} + * @see {@link EventHandlerSubscribeFn} + */ +export type EventHandlerUnsubscribeFn = () => void; diff --git a/sdk/typescript/packages/sdk/typedoc.json b/sdk/typescript/packages/sdk/typedoc.json new file mode 100644 index 0000000000..507b6156cc --- /dev/null +++ b/sdk/typescript/packages/sdk/typedoc.json @@ -0,0 +1,32 @@ +{ + "sort": ["kind"], + "entryPoints": ["./src/index.ts"], + "out": "./docs", + "exclude": ["./src/**/node-tester/*", "./src/**/wasm/types-from-wasm-pack.ts"], + "kindSortOrder": [ + "Function", + "Interface", + "TypeAlias", + "Reference", + "Project", + "Module", + "Namespace", + "Enum", + "EnumMember", + "Class", + "Constructor", + "Property", + "Variable", + "Accessor", + "Method", + "ObjectLiteral", + "Parameter", + "TypeParameter", + "TypeLiteral", + "CallSignature", + "ConstructorSignature", + "IndexSignature", + "GetSignature", + "SetSignature" + ] +} diff --git a/yarn.lock b/yarn.lock index 8c6092b23f..178152e2f1 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6421,6 +6421,11 @@ ansi-regex@^6.0.1: resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-6.0.1.tgz#3183e38fae9a65d7cb5e53945cd5897d0260a06a" integrity sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA== +ansi-sequence-parser@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/ansi-sequence-parser/-/ansi-sequence-parser-1.1.0.tgz#4d790f31236ac20366b23b3916b789e1bde39aed" + integrity sha512-lEm8mt52to2fT8GhciPCGeCXACSz2UwIN4X2e2LJSnZ5uAbn2/dsYdOmUXq0AtWS5cpAupysIneExOgH0Vd2TQ== + ansi-styles@^3.2.1: version "3.2.1" resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d" @@ -7821,6 +7826,17 @@ cli-boxes@^2.2.1: resolved "https://registry.yarnpkg.com/cli-boxes/-/cli-boxes-2.2.1.tgz#ddd5035d25094fce220e9cab40a45840a440318f" integrity sha512-y4coMcylgSCdVinjiDBuR8PCC2bLjyGTwEmPb9NHR/QaNU6EUOXcTY/s6VjGMD6ENSEaeQYHCY0GNGS5jfMwPw== +cli-color@~2.0.0: + version "2.0.3" + resolved "https://registry.yarnpkg.com/cli-color/-/cli-color-2.0.3.tgz#73769ba969080629670f3f2ef69a4bf4e7cc1879" + integrity sha512-OkoZnxyC4ERN3zLzZaY9Emb7f/MhBOIpePv0Ycok0fJYT+Ouo00UBEIwsVsr0yoow++n5YWlSUgST9GKhNHiRQ== + dependencies: + d "^1.0.1" + es5-ext "^0.10.61" + es6-iterator "^2.0.3" + memoizee "^0.4.15" + timers-ext "^0.1.7" + cli-cursor@3.1.0, cli-cursor@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-3.1.0.tgz#264305a7ae490d1d03bf0c9ba7c925d1753af307" @@ -8093,6 +8109,11 @@ commander@^9.4.1: resolved "https://registry.yarnpkg.com/commander/-/commander-9.5.0.tgz#bc08d1eb5cedf7ccb797a96199d41c7bc3e60d30" integrity sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ== +commander@~9.4.0: + version "9.4.1" + resolved "https://registry.yarnpkg.com/commander/-/commander-9.4.1.tgz#d1dd8f2ce6faf93147295c0df13c7c21141cfbdd" + integrity sha512-5EEkTNyHNGFPD2H+c/dXXfQZYa/scCKasxWcXJaWnNJ99pnQN9Vnmqow+p+PlFPE63Q6mThaZws1T+HxfpgtPw== + common-ancestor-path@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/common-ancestor-path/-/common-ancestor-path-1.0.1.tgz#4f7d2d1394d91b7abdf51871c62f71eadb0182a7" @@ -8871,6 +8892,14 @@ d3-zoom@^2.0.0: d3-selection "2" d3-transition "2" +d@1, d@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/d/-/d-1.0.1.tgz#8698095372d58dbee346ffd0c7093f99f8f9eb5a" + integrity sha512-m62ShEObQ39CfralilEQRjH6oAMtNCV1xJyEx5LpRYUVN+EviphDgUc/F3hnYbADmkiNs67Y+3ylmlG7Lnu+FA== + dependencies: + es5-ext "^0.10.50" + type "^1.0.1" + damerau-levenshtein@^1.0.8: version "1.0.8" resolved "https://registry.yarnpkg.com/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz#b43d286ccbd36bc5b2f7ed41caf2d0aba1f8a6e7" @@ -9678,16 +9707,52 @@ es-to-primitive@^1.2.1: is-date-object "^1.0.1" is-symbol "^1.0.2" +es5-ext@^0.10.35, es5-ext@^0.10.46, es5-ext@^0.10.50, es5-ext@^0.10.53, es5-ext@^0.10.61, es5-ext@~0.10.14, es5-ext@~0.10.2, es5-ext@~0.10.46: + version "0.10.62" + resolved "https://registry.yarnpkg.com/es5-ext/-/es5-ext-0.10.62.tgz#5e6adc19a6da524bf3d1e02bbc8960e5eb49a9a5" + integrity sha512-BHLqn0klhEpnOKSrzn/Xsz2UIW8j+cGmo9JLzr8BiUapV8hPL9+FliFqjwr9ngW7jWdnxv6eO+/LqyhJVqgrjA== + dependencies: + es6-iterator "^2.0.3" + es6-symbol "^3.1.3" + next-tick "^1.1.0" + es5-shim@^4.5.13: version "4.6.7" resolved "https://registry.yarnpkg.com/es5-shim/-/es5-shim-4.6.7.tgz#bc67ae0fc3dd520636e0a1601cc73b450ad3e955" integrity sha512-jg21/dmlrNQI7JyyA2w7n+yifSxBng0ZralnSfVZjoCawgNTCnS+yBCyVM9DL5itm7SUnDGgv7hcq2XCZX4iRQ== +es6-iterator@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/es6-iterator/-/es6-iterator-2.0.3.tgz#a7de889141a05a94b0854403b2d0a0fbfa98f3b7" + integrity sha512-zw4SRzoUkd+cl+ZoE15A9o1oQd920Bb0iOJMQkQhl3jNc03YqVjAhG7scf9C5KWRU/R13Orf588uCC6525o02g== + dependencies: + d "1" + es5-ext "^0.10.35" + es6-symbol "^3.1.1" + es6-shim@^0.35.5: version "0.35.8" resolved "https://registry.yarnpkg.com/es6-shim/-/es6-shim-0.35.8.tgz#89216f6fbf8bacba3f897c8c0e814d2a41c05fb7" integrity sha512-Twf7I2v4/1tLoIXMT8HlqaBSS5H2wQTs2wx3MNYCI8K1R1/clXyCazrcVCPm/FuO9cyV8+leEaZOWD5C253NDg== +es6-symbol@^3.1.1, es6-symbol@^3.1.3: + version "3.1.3" + resolved "https://registry.yarnpkg.com/es6-symbol/-/es6-symbol-3.1.3.tgz#bad5d3c1bcdac28269f4cb331e431c78ac705d18" + integrity sha512-NJ6Yn3FuDinBaBRWl/q5X/s4koRHBrgKAu+yGI6JCBeiu3qrcbJhwT2GeR/EXVfylRk8dpQVJoLEFhK+Mu31NA== + dependencies: + d "^1.0.1" + ext "^1.1.2" + +es6-weak-map@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/es6-weak-map/-/es6-weak-map-2.0.3.tgz#b6da1f16cc2cc0d9be43e6bdbfc5e7dfcdf31d53" + integrity sha512-p5um32HOTO1kP+w7PRnB+5lQ43Z6muuMuIMffvDN8ZB4GcnjLBV6zGStpbASIMk4DCAvEaamhe2zhyCb/QXXsA== + dependencies: + d "1" + es5-ext "^0.10.46" + es6-iterator "^2.0.3" + es6-symbol "^3.1.1" + escalade@^3.1.1: version "3.1.1" resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40" @@ -10150,6 +10215,14 @@ etag@~1.8.1: resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887" integrity sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg== +event-emitter@^0.3.5: + version "0.3.5" + resolved "https://registry.yarnpkg.com/event-emitter/-/event-emitter-0.3.5.tgz#df8c69eef1647923c7157b9ce83840610b02cc39" + integrity sha512-D9rRn9y7kLPnJ+hMq7S/nhvoKwwvVJahBi2BPmx3bvbsEdK3W9ii8cBSGjP+72/LnM4n6fo3+dkCX5FeTQruXA== + dependencies: + d "1" + es5-ext "~0.10.14" + event-target-shim@^5.0.0: version "5.0.1" resolved "https://registry.yarnpkg.com/event-target-shim/-/event-target-shim-5.0.1.tgz#5d4d3ebdf9583d63a5333ce2deb7480ab2b05789" @@ -10330,6 +10403,13 @@ express@^4.17.1, express@^4.17.3, express@^4.18.2: utils-merge "1.0.1" vary "~1.1.2" +ext@^1.1.2: + version "1.7.0" + resolved "https://registry.yarnpkg.com/ext/-/ext-1.7.0.tgz#0ea4383c0103d60e70be99e9a7f11027a33c4f5f" + integrity sha512-6hxeJYaL110a9b5TEJSj0gojyHQAmA2ch5Os+ySCiA1QGdS697XWY1pzsrSjqA9LDEEgdB/KypIlR59RcLuHYw== + dependencies: + type "^2.7.2" + extend-shallow@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/extend-shallow/-/extend-shallow-2.0.1.tgz#51af7d614ad9a9f610ea1bafbb989d6b1c56890f" @@ -10590,7 +10670,7 @@ fill-range@^7.0.1: dependencies: to-regex-range "^5.0.1" -finalhandler@1.2.0: +finalhandler@1.2.0, finalhandler@~1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.2.0.tgz#7d23fe5731b207b4640e4fcd00aec1f9207a7b32" integrity sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg== @@ -12432,6 +12512,11 @@ is-potential-custom-element-name@^1.0.1: resolved "https://registry.yarnpkg.com/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz#171ed6f19e3ac554394edf78caa05784a45bebb5" integrity sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ== +is-promise@^2.2.2: + version "2.2.2" + resolved "https://registry.yarnpkg.com/is-promise/-/is-promise-2.2.2.tgz#39ab959ccbf9a774cf079f7b40c7a26f763135f1" + integrity sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ== + is-reference@1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/is-reference/-/is-reference-1.2.1.tgz#8b2dac0b371f4bc994fdeaba9eb542d03002d0b7" @@ -13749,7 +13834,7 @@ json5@^1.0.1, json5@^1.0.2: dependencies: minimist "^1.2.0" -jsonc-parser@3.2.0, jsonc-parser@^3.0.0: +jsonc-parser@3.2.0, jsonc-parser@^3.0.0, jsonc-parser@^3.2.0: version "3.2.0" resolved "https://registry.yarnpkg.com/jsonc-parser/-/jsonc-parser-3.2.0.tgz#31ff3f4c2b9793f89c67212627c51c6394f88e76" integrity sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w== @@ -14314,6 +14399,13 @@ lru-cache@^7.4.4, lru-cache@^7.5.1, lru-cache@^7.7.1: resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-10.0.0.tgz#b9e2a6a72a129d81ab317202d93c7691df727e61" integrity sha512-svTf/fzsKHffP42sujkO/Rjs37BCIsQVRCeNYIm9WN8rgT7ffoUnRtZCqU+6BqcSBdv8gwJeTz8knJpgACeQMw== +lru-queue@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/lru-queue/-/lru-queue-0.1.0.tgz#2738bd9f0d3cf4f84490c5736c48699ac632cda3" + integrity sha512-BpdYkt9EvGl8OfWHDQPISVpcl5xZthb+XPsbELj5AQXxIC8IriDZIQYjBJPEm5rS420sjZ0TLEzRcq5KdBhYrQ== + dependencies: + es5-ext "~0.10.2" + lunr@^2.3.9: version "2.3.9" resolved "https://registry.yarnpkg.com/lunr/-/lunr-2.3.9.tgz#18b123142832337dd6e964df1a5a7707b25d35e1" @@ -14464,7 +14556,7 @@ markdown-extensions@^1.0.0: resolved "https://registry.yarnpkg.com/markdown-extensions/-/markdown-extensions-1.1.1.tgz#fea03b539faeaee9b4ef02a3769b455b189f7fc3" integrity sha512-WWC0ZuMzCyDHYCasEGs4IPvLyTGftYwh6wIEOULOF0HXcqZlhwRzrK0w2VUlxWA98xnvb/jszw4ZSkJ6ADpM6Q== -marked@^4.0.16: +marked@^4.0.16, marked@^4.3.0: version "4.3.0" resolved "https://registry.yarnpkg.com/marked/-/marked-4.3.0.tgz#796362821b019f734054582038b116481b456cf3" integrity sha512-PRsaiG84bK+AMvxziE/lCFss8juXjNaWzVbN5tXAm4XjeaS9NAHhop+PjQxz2A9h8Q4M/xGmzP8vqNwy6JeK0A== @@ -14662,6 +14754,20 @@ memfs@^3.1.2, memfs@^3.2.2, memfs@^3.4.1, memfs@^3.4.3: dependencies: fs-monkey "^1.0.4" +memoizee@^0.4.15: + version "0.4.15" + resolved "https://registry.yarnpkg.com/memoizee/-/memoizee-0.4.15.tgz#e6f3d2da863f318d02225391829a6c5956555b72" + integrity sha512-UBWmJpLZd5STPm7PMUlOw/TSy972M+z8gcyQ5veOnSDRREz/0bmpyTfKt3/51DhEBqCZQn1udM/5flcSPYhkdQ== + dependencies: + d "^1.0.1" + es5-ext "^0.10.53" + es6-weak-map "^2.0.3" + event-emitter "^0.3.5" + is-promise "^2.2.2" + lru-queue "^0.1.0" + next-tick "^1.1.0" + timers-ext "^0.1.7" + memoizerific@^1.11.3: version "1.11.3" resolved "https://registry.yarnpkg.com/memoizerific/-/memoizerific-1.11.3.tgz#7c87a4646444c32d75438570905f2dbd1b1a805a" @@ -15195,7 +15301,7 @@ minimist-options@4.1.0: is-plain-obj "^1.1.0" kind-of "^6.0.3" -minimist@^1.1.1, minimist@^1.1.3, minimist@^1.2.0, minimist@^1.2.3, minimist@^1.2.5, minimist@^1.2.6: +minimist@^1.1.1, minimist@^1.1.3, minimist@^1.2.0, minimist@^1.2.3, minimist@^1.2.5, minimist@^1.2.6, minimist@~1.2.0: version "1.2.8" resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c" integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA== @@ -15505,6 +15611,11 @@ nested-error-stacks@^2.0.0, nested-error-stacks@^2.1.0: resolved "https://registry.yarnpkg.com/nested-error-stacks/-/nested-error-stacks-2.1.1.tgz#26c8a3cee6cc05fbcf1e333cd2fc3e003326c0b5" integrity sha512-9iN1ka/9zmX1ZvLV9ewJYEk9h7RyRRtqdK0woXcqohu8EWIerfPUjYJPg0ULy0UqP7cslmdGc8xKDJcojlKiaw== +next-tick@1, next-tick@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/next-tick/-/next-tick-1.1.0.tgz#1836ee30ad56d67ef281b22bd199f709449b35eb" + integrity sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ== + nice-try@^1.0.4: version "1.0.5" resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366" @@ -15632,6 +15743,22 @@ node-releases@^2.0.12: resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.12.tgz#35627cc224a23bfb06fb3380f2b3afaaa7eb1039" integrity sha512-QzsYKWhXTWx8h1kIvqfnC++o0pEmpRQA/aenALsL2F4pqNVr7YzcdMlDij5WBnwftRbJCNJL/O7zdKaxKPHqgQ== +nodemon@3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/nodemon/-/nodemon-3.0.1.tgz#affe822a2c5f21354466b2fc8ae83277d27dadc7" + integrity sha512-g9AZ7HmkhQkqXkRc20w+ZfQ73cHLbE8hnPbtaFbFtCumZsjyMhKk9LajQ07U5Ux28lvFjZ5X7HvWR1xzU8jHVw== + dependencies: + chokidar "^3.5.2" + debug "^3.2.7" + ignore-by-default "^1.0.1" + minimatch "^3.1.2" + pstree.remy "^1.1.8" + semver "^7.5.3" + simple-update-notifier "^2.0.0" + supports-color "^5.5.0" + touch "^3.1.0" + undefsafe "^2.0.5" + nodemon@^2.0.21: version "2.0.22" resolved "https://registry.yarnpkg.com/nodemon/-/nodemon-2.0.22.tgz#182c45c3a78da486f673d6c1702e00728daf5258" @@ -16143,7 +16270,7 @@ open@^7.0.3: is-docker "^2.0.0" is-wsl "^2.1.1" -open@^8.0.9, open@^8.4.0: +open@^8.0.0, open@^8.0.9, open@^8.4.0: version "8.4.2" resolved "https://registry.yarnpkg.com/open/-/open-8.4.2.tgz#5b5ffe2a8f793dcd2aad73e550cb87b59cb084f9" integrity sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ== @@ -18108,6 +18235,20 @@ relateurl@^0.2.7: resolved "https://registry.yarnpkg.com/relateurl/-/relateurl-0.2.7.tgz#54dbf377e51440aca90a4cd274600d3ff2d888a9" integrity sha512-G08Dxvm4iDN3MLM0EsP62EDV9IuhXPR6blNz6Utcp7zyV3tr4HVNINt6MpaRWbxoOHT3Q7YN2P+jaHX8vUbgog== +reload@^3.2.1: + version "3.2.1" + resolved "https://registry.yarnpkg.com/reload/-/reload-3.2.1.tgz#42d43e33e327efe1348c723272c6835fe333349a" + integrity sha512-ZdM8ZSEeI72zkhh6heMEvJ0vHZoovZXcJI6Zae8CzS7o5vO/WjZsAMMr0y1+3I/fCN7y7ZxABoUwwCswcLHkjQ== + dependencies: + cli-color "~2.0.0" + commander "~9.4.0" + finalhandler "~1.2.0" + minimist "~1.2.0" + open "^8.0.0" + serve-static "~1.15.0" + supervisor "~0.12.0" + ws "~8.11.0" + remark-external-links@^8.0.0: version "8.0.0" resolved "https://registry.yarnpkg.com/remark-external-links/-/remark-external-links-8.0.0.tgz#308de69482958b5d1cd3692bc9b725ce0240f345" @@ -18730,7 +18871,7 @@ serve-index@^1.9.1: mime-types "~2.1.17" parseurl "~1.3.2" -serve-static@1.15.0: +serve-static@1.15.0, serve-static@~1.15.0: version "1.15.0" resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.15.0.tgz#faaef08cffe0a1a62f60cad0c4e513cff0ac9540" integrity sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g== @@ -18851,6 +18992,16 @@ shiki@^0.10.1: vscode-oniguruma "^1.6.1" vscode-textmate "5.2.0" +shiki@^0.14.1: + version "0.14.3" + resolved "https://registry.yarnpkg.com/shiki/-/shiki-0.14.3.tgz#d1a93c463942bdafb9866d74d619a4347d0bbf64" + integrity sha512-U3S/a+b0KS+UkTyMjoNojvTgrBHjgp7L6ovhFVZsXmBGnVdQ4K4U9oK0z63w538S91ATngv1vXigHCSWOwnr+g== + dependencies: + ansi-sequence-parser "^1.1.0" + jsonc-parser "^3.2.0" + vscode-oniguruma "^1.7.0" + vscode-textmate "^8.0.0" + side-channel@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.0.4.tgz#efce5c8fdc104ee751b25c58d4290011fa5ea2cf" @@ -18908,6 +19059,13 @@ simple-update-notifier@^1.0.7: dependencies: semver "~7.0.0" +simple-update-notifier@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz#d70b92bdab7d6d90dfd73931195a30b6e3d7cebb" + integrity sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w== + dependencies: + semver "^7.5.3" + sisteransi@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/sisteransi/-/sisteransi-1.0.5.tgz#134d681297756437cc05ca01370d3a7a571075ed" @@ -19527,6 +19685,11 @@ stylis@4.2.0: resolved "https://registry.yarnpkg.com/stylis/-/stylis-4.2.0.tgz#79daee0208964c8fe695a42fcffcac633a211a51" integrity sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw== +supervisor@~0.12.0: + version "0.12.0" + resolved "https://registry.yarnpkg.com/supervisor/-/supervisor-0.12.0.tgz#de7e6337015b291851c10f3538c4a7f04917ecc1" + integrity sha512-iBYeU5Or4WiiIa3+ns1DpHIiHjNNXSuYUiixKcznewwo4ImBJ8EobktaAo2csOcauhrz4SvKRTou8Z2C3W28+A== + supports-color@8.1.1, supports-color@^8.0.0: version "8.1.1" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-8.1.1.tgz#cd6fc17e28500cff56c1b86c0a7fd4a54a73005c" @@ -19847,6 +20010,14 @@ timers-browserify@^2.0.4: dependencies: setimmediate "^1.0.4" +timers-ext@^0.1.7: + version "0.1.7" + resolved "https://registry.yarnpkg.com/timers-ext/-/timers-ext-0.1.7.tgz#6f57ad8578e07a3fb9f91d9387d65647555e25c6" + integrity sha512-b85NUNzTSdodShTIbky6ZF02e8STtVVfD+fu4aXXShEELpozH+bCpJLYMPZbsABN2wDH7fJpqIoXxJpzbf0NqQ== + dependencies: + es5-ext "~0.10.46" + next-tick "1" + tiny-warning@^1.0.2: version "1.0.3" resolved "https://registry.yarnpkg.com/tiny-warning/-/tiny-warning-1.0.3.tgz#94a30db453df4c643d0fd566060d60a875d84754" @@ -20237,6 +20408,16 @@ type-is@~1.6.18: media-typer "0.3.0" mime-types "~2.1.24" +type@^1.0.1: + version "1.2.0" + resolved "https://registry.yarnpkg.com/type/-/type-1.2.0.tgz#848dd7698dafa3e54a6c479e759c4bc3f18847a0" + integrity sha512-+5nt5AAniqsCnu2cEQQdpzCAh33kVx8n0VoFidKpB1dVVLAN/F+bgVOqOJqOnEnrhp222clB5p3vUlD+1QAnfg== + +type@^2.7.2: + version "2.7.2" + resolved "https://registry.yarnpkg.com/type/-/type-2.7.2.tgz#2376a15a3a28b1efa0f5350dcf72d24df6ef98d0" + integrity sha512-dzlvlNlt6AXU7EBSfpAscydQ7gXB+pPGsPnfJnZpiNJBDj7IaJzQlBZYGdEi4R9HmPdBv2XmWJ6YUtoTa7lmCw== + typed-array-length@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/typed-array-length/-/typed-array-length-1.0.4.tgz#89d83785e5c4098bec72e08b319651f0eac9c1bb" @@ -20269,6 +20450,16 @@ typedoc@^0.22.13: minimatch "^5.1.0" shiki "^0.10.1" +typedoc@^0.24.8: + version "0.24.8" + resolved "https://registry.yarnpkg.com/typedoc/-/typedoc-0.24.8.tgz#cce9f47ba6a8d52389f5e583716a2b3b4335b63e" + integrity sha512-ahJ6Cpcvxwaxfu4KtjA8qZNqS43wYt6JL27wYiIgl1vd38WW/KWX11YuAeZhuz9v+ttrutSsgK+XO1CjL1kA3w== + dependencies: + lunr "^2.3.9" + marked "^4.3.0" + minimatch "^9.0.0" + shiki "^0.14.1" + "typescript@^3 || ^4", typescript@^4.6.2, typescript@^4.8.4: version "4.9.5" resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.9.5.tgz#095979f9bcc0d09da324d58d03ce8f8374cbe65a" @@ -20850,7 +21041,7 @@ vm-browserify@^1.0.1: resolved "https://registry.yarnpkg.com/vm-browserify/-/vm-browserify-1.1.2.tgz#78641c488b8e6ca91a75f511e7a3b32a86e5dda0" integrity sha512-2ham8XPWTONajOR0ohOKOHXkm3+gaBmGut3SRuu75xLd/RRaY6vqgh8NBYYk7+RW3u5AtzPQZG8F10LHkl0lAQ== -vscode-oniguruma@^1.6.1: +vscode-oniguruma@^1.6.1, vscode-oniguruma@^1.7.0: version "1.7.0" resolved "https://registry.yarnpkg.com/vscode-oniguruma/-/vscode-oniguruma-1.7.0.tgz#439bfad8fe71abd7798338d1cd3dc53a8beea94b" integrity sha512-L9WMGRfrjOhgHSdOYgCt/yRMsXzLDJSL7BPrOZt73gU0iWO4mpqzqQzOz5srxqTvMBaR0XZTSrVWo4j55Rc6cA== @@ -20860,6 +21051,11 @@ vscode-textmate@5.2.0: resolved "https://registry.yarnpkg.com/vscode-textmate/-/vscode-textmate-5.2.0.tgz#01f01760a391e8222fe4f33fbccbd1ad71aed74e" integrity sha512-Uw5ooOQxRASHgu6C7GVvUxisKXfSgW4oFlO+aa+PAkgmH89O3CXxEEzNRNtHSqtXFTl0nAC1uYj0GMSH27uwtQ== +vscode-textmate@^8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/vscode-textmate/-/vscode-textmate-8.0.0.tgz#2c7a3b1163ef0441097e0b5d6389cd5504b59e5d" + integrity sha512-AFbieoL7a5LMqcnOF04ji+rpXadgOXnZsxQr//r83kLPr7biP7am3g9zbaZIaBGwBRWeSvoMD4mgPdX3e4NWBg== + w3c-hr-time@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz#0a89cdf5cc15822df9c360543676963e0cc308cd" @@ -21416,6 +21612,11 @@ ws@^8.13.0, ws@^8.2.3: resolved "https://registry.yarnpkg.com/ws/-/ws-8.13.0.tgz#9a9fb92f93cf41512a0735c8f4dd09b8a1211cd0" integrity sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA== +ws@~8.11.0: + version "8.11.0" + resolved "https://registry.yarnpkg.com/ws/-/ws-8.11.0.tgz#6a0d36b8edfd9f96d8b25683db2f8d7de6e8e143" + integrity sha512-HPG3wQd9sNQoT9xHyNCXoDUa+Xw/VevmY9FoHyQ+g+rrMn4j6FB4np7Z0OhdTgjx6MgQLK7jwSy1YecU1+4Asg== + x-default-browser@^0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/x-default-browser/-/x-default-browser-0.4.0.tgz#70cf0da85da7c0ab5cb0f15a897f2322a6bdd481" From 00b5a46cbc15c13e6e2fcb34247e42eeca8873c2 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 1 Aug 2023 15:22:00 +0000 Subject: [PATCH 041/361] Bump semver from 5.7.1 to 5.7.2 in /nym-wallet/webdriver Bumps [semver](https://github.com/npm/node-semver) from 5.7.1 to 5.7.2. - [Release notes](https://github.com/npm/node-semver/releases) - [Changelog](https://github.com/npm/node-semver/blob/v5.7.2/CHANGELOG.md) - [Commits](https://github.com/npm/node-semver/compare/v5.7.1...v5.7.2) --- updated-dependencies: - dependency-name: semver dependency-type: indirect ... Signed-off-by: dependabot[bot] --- nym-wallet/webdriver/yarn.lock | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/nym-wallet/webdriver/yarn.lock b/nym-wallet/webdriver/yarn.lock index 0c01bfb5cc..3e139e91a3 100644 --- a/nym-wallet/webdriver/yarn.lock +++ b/nym-wallet/webdriver/yarn.lock @@ -2024,10 +2024,10 @@ pkg-dir@4.2.0: dependencies: find-up "^4.0.0" -prettier@2.4.1: - version "2.4.1" - resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.4.1.tgz#671e11c89c14a4cfc876ce564106c4a6726c9f5c" - integrity sha512-9fbDAXSBcc6Bs1mZrDYb3XKzDLm4EXXL9sC1LqKP5rZkT6KRr/rf9amVUcODVXgguK/isJz0d0hP72WeaKWsvA== +prettier@^2.8.7: + version "2.8.8" + resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.8.8.tgz#e8c5d7e98a4305ffe3de2e1fc4aca1a71c28b1da" + integrity sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q== pretty-format@^27.3.1: version "27.3.1" @@ -2259,9 +2259,9 @@ safe-buffer@~5.1.0, safe-buffer@~5.1.1: integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== "semver@2 || 3 || 4 || 5": - version "5.7.1" - resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" - integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== + version "5.7.2" + resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.2.tgz#48d55db737c3287cd4835e17fa13feace1c41ef8" + integrity sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g== serialize-error@^8.0.0: version "8.1.0" From 974cee1f22b2d9f04463561c93b0531dbcd181a7 Mon Sep 17 00:00:00 2001 From: serinko <97586125+serinko@users.noreply.github.com> Date: Tue, 1 Aug 2023 17:37:12 +0200 Subject: [PATCH 042/361] operators: book built, no {comments} left, cmdrun and admonish not working --- documentation/operators/book.toml | 118 + documentation/operators/last-changed.css | 6 + documentation/operators/mdbook-admonish.css | 352 ++ documentation/operators/theme/css/chrome.css | 538 +++ documentation/operators/theme/css/general.css | 203 + .../operators/theme/css/variables.css | 3257 +++++++++++++++++ documentation/operators/theme/index.hbs | 1063 ++++++ documentation/operators/theme/pagetoc.css | 47 + documentation/operators/theme/pagetoc.js | 68 + 9 files changed, 5652 insertions(+) create mode 100644 documentation/operators/last-changed.css create mode 100644 documentation/operators/mdbook-admonish.css create mode 100644 documentation/operators/theme/css/chrome.css create mode 100644 documentation/operators/theme/css/general.css create mode 100644 documentation/operators/theme/css/variables.css create mode 100644 documentation/operators/theme/index.hbs create mode 100644 documentation/operators/theme/pagetoc.css create mode 100644 documentation/operators/theme/pagetoc.js diff --git a/documentation/operators/book.toml b/documentation/operators/book.toml index 45ffb514ae..3a0a9f722b 100644 --- a/documentation/operators/book.toml +++ b/documentation/operators/book.toml @@ -5,3 +5,121 @@ description = "Guides to setup Nym infrastructure" language = "en" multilingual = false # for the moment - ideally work on chinese, brazillian, spanish next src = "src" + +################# +# PREPROCESSORS # +################# + +# Note if changing these fields: +# - make sure you aren't running `mdbook serve` +# - change value of `turn-off = true` to `false`. This is set to `true` usually to avoid a known constant-reload bug with `mdbook serve` and this preprocessor +# - delete `./theme/` dir +# - change the preprocessor values that you want to edit +# - run `mdbook build`: this will rebuild the `./theme` directory +# - change value of `turn-off` back to `true` +[preprocessor.theme] +pagetoc = true +# some variables related (defined in theme/css/variables.css) +pagetoc-width = "13%" +content-max-width = "70%" # 82 +pagetoc-fontsize = "14.5px" +sidebar-width = "300px" +menu-bar-height = "40px" # memu-bar = the bar on the top +page-padding = "20px" +mobile-content-max-width = "98%" + +# layout +content-padding = "0 10px" +content-main-margin-left = "10%" +content-main-margin-right = "10%" +nav-chapters-max-width = "auto" +nav-chapters-min-width = "auto" +chapter-line-height = "2em" +section-line-height = "1.5em" + +# if true, never read and touch the files in theme dir: this is used to stop the looping reload issue referred to in the readme +turn-off = false + +[preprocessor.admonish] +command = "mdbook-admonish" +assets_version = "2.0.0" # do not edit: managed by `mdbook-admonish install` + +# variables preprocessor: import variables into files +# https://gitlab.com/tglman/mdbook-variables/ +[preprocessor.variables.variables] +minimum_rust_version = "1.66" +# vars for links: TODO think on how to streamline updating +platform_release_version = "v1.1.22" +wallet_release_version = "v1.2.5" + + +# [preprocessor.last-changed] +# command = "mdbook-last-changed" +# renderer = ["html"] + + +# used for grabbing output of binary commands for automation +# https://github.com/FauconFan/mdbook-cmdrun +[preprocessor.cmdrun] + +# more pre-processor plugins to look into from https://github.com/rust-lang/mdBook/wiki/Third-party-plugins & https://lib.rs/keywords/mdbook-preprocessor +# mdbook-i18n + +######### +# BUILD # +######### + +[build] +build-dir = "book" # the directory where the output is placed +create-missing = true # whether or not to create missing pages +use-default-preprocessors = true # use the default preprocessors +extra-watch-dirs = [] # directories to watch for triggering builds + +########## +# OUTPUT # +########## + +[output.html] +theme = "theme" # this is the default if not explicitly set +default-theme = "coal" +preferred-dark-theme = "coal" +curly-quotes = true +# mathjax-support = false # useful if we want to pull equations in +copy-fonts = true +no-section-label = false +additional-css = ["theme/pagetoc.css", "././mdbook-admonish.css","./last-changed.css"] +additional-js = ["theme/pagetoc.js"] +git-repository-url = "https://github.com/nymtech/nym" +git-repository-icon = "fa-github" +# edit-url-template = "https://github.com/rust-lang/mdBook/edit/master/guide/{path}" +# site-url = "/docs/" +# cname = "nymtech.net" +# input-404 = "not-found.md" # todo make custom 404 page + +[output.html.fold] +enable = true # whether or not to enable section folding +level = 0 # the depth to start folding + +# controlling rust sample code blocks +[output.html.playground] +editable = false # allows editing the source code +copyable = true # include the copy button for copying code snippets +copy-js = true # includes the JavaScript for the code editor +line-numbers = false # displays line numbers for editable code +runnable = true # displays a run button for rust code + +# options for the built in text search +[output.html.search] +enable = true # enables the search feature +limit-results = 30 # maximum number of search results +teaser-word-count = 30 # number of words used for a search result teaser +use-boolean-and = true # multiple search terms must all match +boost-title = 2 # ranking boost factor for matches in headers +boost-hierarchy = 1 # ranking boost factor for matches in page names +boost-paragraph = 1 # ranking boost factor for matches in text +expand = true # partial words will match longer terms +heading-split-level = 3 # link results to heading levels +copy-js = true # include Javascript code for search + +# [output.linkcheck] +# warning-policy = "warn" diff --git a/documentation/operators/last-changed.css b/documentation/operators/last-changed.css new file mode 100644 index 0000000000..0c4228de32 --- /dev/null +++ b/documentation/operators/last-changed.css @@ -0,0 +1,6 @@ +footer { + font-size: 0.8em; + text-align: center; + border-top: 1px solid black; + padding: 5px 0; + } \ No newline at end of file diff --git a/documentation/operators/mdbook-admonish.css b/documentation/operators/mdbook-admonish.css new file mode 100644 index 0000000000..5e360387df --- /dev/null +++ b/documentation/operators/mdbook-admonish.css @@ -0,0 +1,352 @@ +@charset "UTF-8"; +:root { + --md-admonition-icon--note: + url("data:image/svg+xml;charset=utf-8,"); + --md-admonition-icon--abstract: + url("data:image/svg+xml;charset=utf-8,"); + --md-admonition-icon--info: + url("data:image/svg+xml;charset=utf-8,"); + --md-admonition-icon--tip: + url("data:image/svg+xml;charset=utf-8,"); + --md-admonition-icon--success: + url("data:image/svg+xml;charset=utf-8,"); + --md-admonition-icon--question: + url("data:image/svg+xml;charset=utf-8,"); + --md-admonition-icon--warning: + url("data:image/svg+xml;charset=utf-8,"); + --md-admonition-icon--failure: + url("data:image/svg+xml;charset=utf-8,"); + --md-admonition-icon--danger: + url("data:image/svg+xml;charset=utf-8,"); + --md-admonition-icon--bug: + url("data:image/svg+xml;charset=utf-8,"); + --md-admonition-icon--example: + url("data:image/svg+xml;charset=utf-8,"); + --md-admonition-icon--quote: + url("data:image/svg+xml;charset=utf-8,"); + --md-details-icon: + url("data:image/svg+xml;charset=utf-8,"); +} + +:is(.admonition) { + display: flow-root; + margin: 1.5625em 0; + padding: 0 1.2rem; + color: var(--fg); + page-break-inside: avoid; + background-color: var(--bg); + border: 0 solid black; + border-inline-start-width: 0.4rem; + border-radius: 0.2rem; + box-shadow: 0 0.2rem 1rem rgba(0, 0, 0, 0.05), 0 0 0.1rem rgba(0, 0, 0, 0.1); +} +@media print { + :is(.admonition) { + box-shadow: none; + } +} +:is(.admonition) > * { + box-sizing: border-box; +} +:is(.admonition) :is(.admonition) { + margin-top: 1em; + margin-bottom: 1em; +} +:is(.admonition) > .tabbed-set:only-child { + margin-top: 0; +} +html :is(.admonition) > :last-child { + margin-bottom: 1.2rem; +} + +a.admonition-anchor-link { + display: none; + position: absolute; + left: -1.2rem; + padding-right: 1rem; +} +a.admonition-anchor-link:link, a.admonition-anchor-link:visited { + color: var(--fg); +} +a.admonition-anchor-link:link:hover, a.admonition-anchor-link:visited:hover { + text-decoration: none; +} +a.admonition-anchor-link::before { + content: "§"; +} + +:is(.admonition-title, summary) { + position: relative; + margin-block: 0; + margin-inline: -1.6rem -1.2rem; + padding-block: 0.8rem; + padding-inline: 4.4rem 1.2rem; + font-weight: 700; + background-color: rgba(68, 138, 255, 0.1); + display: flex; +} +:is(.admonition-title, summary) p { + margin: 0; +} +html :is(.admonition-title, summary):last-child { + margin-bottom: 0; +} +:is(.admonition-title, summary)::before { + position: absolute; + top: 0.625em; + inset-inline-start: 1.6rem; + width: 2rem; + height: 2rem; + background-color: #448aff; + mask-image: url('data:image/svg+xml;charset=utf-8,'); + -webkit-mask-image: url('data:image/svg+xml;charset=utf-8,'); + mask-repeat: no-repeat; + -webkit-mask-repeat: no-repeat; + mask-size: contain; + -webkit-mask-size: contain; + content: ""; +} +:is(.admonition-title, summary):hover a.admonition-anchor-link { + display: initial; +} + +details.admonition > summary.admonition-title::after { + position: absolute; + top: 0.625em; + inset-inline-end: 1.6rem; + height: 2rem; + width: 2rem; + background-color: currentcolor; + mask-image: var(--md-details-icon); + -webkit-mask-image: var(--md-details-icon); + mask-repeat: no-repeat; + -webkit-mask-repeat: no-repeat; + mask-size: contain; + -webkit-mask-size: contain; + content: ""; + transform: rotate(0deg); + transition: transform 0.25s; +} +details[open].admonition > summary.admonition-title::after { + transform: rotate(90deg); +} + +:is(.admonition):is(.note) { + border-color: #448aff; +} + +:is(.note) > :is(.admonition-title, summary) { + background-color: rgba(68, 138, 255, 0.1); +} +:is(.note) > :is(.admonition-title, summary)::before { + background-color: #448aff; + mask-image: var(--md-admonition-icon--note); + -webkit-mask-image: var(--md-admonition-icon--note); + mask-repeat: no-repeat; + -webkit-mask-repeat: no-repeat; + mask-size: contain; + -webkit-mask-repeat: no-repeat; +} + +:is(.admonition):is(.abstract, .summary, .tldr) { + border-color: #00b0ff; +} + +:is(.abstract, .summary, .tldr) > :is(.admonition-title, summary) { + background-color: rgba(0, 176, 255, 0.1); +} +:is(.abstract, .summary, .tldr) > :is(.admonition-title, summary)::before { + background-color: #00b0ff; + mask-image: var(--md-admonition-icon--abstract); + -webkit-mask-image: var(--md-admonition-icon--abstract); + mask-repeat: no-repeat; + -webkit-mask-repeat: no-repeat; + mask-size: contain; + -webkit-mask-repeat: no-repeat; +} + +:is(.admonition):is(.info, .todo) { + border-color: #00b8d4; +} + +:is(.info, .todo) > :is(.admonition-title, summary) { + background-color: rgba(0, 184, 212, 0.1); +} +:is(.info, .todo) > :is(.admonition-title, summary)::before { + background-color: #00b8d4; + mask-image: var(--md-admonition-icon--info); + -webkit-mask-image: var(--md-admonition-icon--info); + mask-repeat: no-repeat; + -webkit-mask-repeat: no-repeat; + mask-size: contain; + -webkit-mask-repeat: no-repeat; +} + +:is(.admonition):is(.tip, .hint, .important) { + border-color: #00bfa5; +} + +:is(.tip, .hint, .important) > :is(.admonition-title, summary) { + background-color: rgba(0, 191, 165, 0.1); +} +:is(.tip, .hint, .important) > :is(.admonition-title, summary)::before { + background-color: #00bfa5; + mask-image: var(--md-admonition-icon--tip); + -webkit-mask-image: var(--md-admonition-icon--tip); + mask-repeat: no-repeat; + -webkit-mask-repeat: no-repeat; + mask-size: contain; + -webkit-mask-repeat: no-repeat; +} + +:is(.admonition):is(.success, .check, .done) { + border-color: #00c853; +} + +:is(.success, .check, .done) > :is(.admonition-title, summary) { + background-color: rgba(0, 200, 83, 0.1); +} +:is(.success, .check, .done) > :is(.admonition-title, summary)::before { + background-color: #00c853; + mask-image: var(--md-admonition-icon--success); + -webkit-mask-image: var(--md-admonition-icon--success); + mask-repeat: no-repeat; + -webkit-mask-repeat: no-repeat; + mask-size: contain; + -webkit-mask-repeat: no-repeat; +} + +:is(.admonition):is(.question, .help, .faq) { + border-color: #64dd17; +} + +:is(.question, .help, .faq) > :is(.admonition-title, summary) { + background-color: rgba(100, 221, 23, 0.1); +} +:is(.question, .help, .faq) > :is(.admonition-title, summary)::before { + background-color: #64dd17; + mask-image: var(--md-admonition-icon--question); + -webkit-mask-image: var(--md-admonition-icon--question); + mask-repeat: no-repeat; + -webkit-mask-repeat: no-repeat; + mask-size: contain; + -webkit-mask-repeat: no-repeat; +} + +:is(.admonition):is(.warning, .caution, .attention) { + border-color: #ff9100; +} + +:is(.warning, .caution, .attention) > :is(.admonition-title, summary) { + background-color: rgba(255, 145, 0, 0.1); +} +:is(.warning, .caution, .attention) > :is(.admonition-title, summary)::before { + background-color: #ff9100; + mask-image: var(--md-admonition-icon--warning); + -webkit-mask-image: var(--md-admonition-icon--warning); + mask-repeat: no-repeat; + -webkit-mask-repeat: no-repeat; + mask-size: contain; + -webkit-mask-repeat: no-repeat; +} + +:is(.admonition):is(.failure, .fail, .missing) { + border-color: #ff5252; +} + +:is(.failure, .fail, .missing) > :is(.admonition-title, summary) { + background-color: rgba(255, 82, 82, 0.1); +} +:is(.failure, .fail, .missing) > :is(.admonition-title, summary)::before { + background-color: #ff5252; + mask-image: var(--md-admonition-icon--failure); + -webkit-mask-image: var(--md-admonition-icon--failure); + mask-repeat: no-repeat; + -webkit-mask-repeat: no-repeat; + mask-size: contain; + -webkit-mask-repeat: no-repeat; +} + +:is(.admonition):is(.danger, .error) { + border-color: #ff1744; +} + +:is(.danger, .error) > :is(.admonition-title, summary) { + background-color: rgba(255, 23, 68, 0.1); +} +:is(.danger, .error) > :is(.admonition-title, summary)::before { + background-color: #ff1744; + mask-image: var(--md-admonition-icon--danger); + -webkit-mask-image: var(--md-admonition-icon--danger); + mask-repeat: no-repeat; + -webkit-mask-repeat: no-repeat; + mask-size: contain; + -webkit-mask-repeat: no-repeat; +} + +:is(.admonition):is(.bug) { + border-color: #f50057; +} + +:is(.bug) > :is(.admonition-title, summary) { + background-color: rgba(245, 0, 87, 0.1); +} +:is(.bug) > :is(.admonition-title, summary)::before { + background-color: #f50057; + mask-image: var(--md-admonition-icon--bug); + -webkit-mask-image: var(--md-admonition-icon--bug); + mask-repeat: no-repeat; + -webkit-mask-repeat: no-repeat; + mask-size: contain; + -webkit-mask-repeat: no-repeat; +} + +:is(.admonition):is(.example) { + border-color: #7c4dff; +} + +:is(.example) > :is(.admonition-title, summary) { + background-color: rgba(124, 77, 255, 0.1); +} +:is(.example) > :is(.admonition-title, summary)::before { + background-color: #7c4dff; + mask-image: var(--md-admonition-icon--example); + -webkit-mask-image: var(--md-admonition-icon--example); + mask-repeat: no-repeat; + -webkit-mask-repeat: no-repeat; + mask-size: contain; + -webkit-mask-repeat: no-repeat; +} + +:is(.admonition):is(.quote, .cite) { + border-color: #9e9e9e; +} + +:is(.quote, .cite) > :is(.admonition-title, summary) { + background-color: rgba(158, 158, 158, 0.1); +} +:is(.quote, .cite) > :is(.admonition-title, summary)::before { + background-color: #9e9e9e; + mask-image: var(--md-admonition-icon--quote); + -webkit-mask-image: var(--md-admonition-icon--quote); + mask-repeat: no-repeat; + -webkit-mask-repeat: no-repeat; + mask-size: contain; + -webkit-mask-repeat: no-repeat; +} + +.navy :is(.admonition) { + background-color: var(--sidebar-bg); +} + +.ayu :is(.admonition), .coal :is(.admonition) { + background-color: var(--theme-hover); +} + +.rust :is(.admonition) { + background-color: var(--sidebar-bg); + color: var(--sidebar-fg); +} +.rust .admonition-anchor-link:link, .rust .admonition-anchor-link:visited { + color: var(--sidebar-fg); +} diff --git a/documentation/operators/theme/css/chrome.css b/documentation/operators/theme/css/chrome.css new file mode 100644 index 0000000000..febd1d40a6 --- /dev/null +++ b/documentation/operators/theme/css/chrome.css @@ -0,0 +1,538 @@ +/* CSS for UI elements (a.k.a. chrome) */ + +@import 'variables.css'; + +::-webkit-scrollbar { + background: var(--bg); +} +::-webkit-scrollbar-thumb { + background: var(--scrollbar); +} +html { + scrollbar-color: var(--scrollbar) var(--bg); +} +#searchresults a, +.content a:link, +a:visited, +a > .hljs { + color: var(--links); +} + +/* Menu Bar */ + +#menu-bar, +#menu-bar-hover-placeholder { + z-index: 101; + margin: auto calc(0px - var(--page-padding)); +} +#menu-bar { + position: relative; + display: flex; + flex-wrap: wrap; + background-color: var(--bg); + border-bottom-color: var(--bg); + border-bottom-width: 1px; + border-bottom-style: solid; +} +#menu-bar.sticky, +.js #menu-bar-hover-placeholder:hover + #menu-bar, +.js #menu-bar:hover, +.js.sidebar-visible #menu-bar { + position: -webkit-sticky; + position: sticky; + top: 0 !important; +} +#menu-bar-hover-placeholder { + position: sticky; + position: -webkit-sticky; + top: 0; + height: var(--menu-bar-height); +} +#menu-bar.bordered { + border-bottom-color: var(--table-border-color); +} +#menu-bar i, #menu-bar .icon-button { + position: relative; + padding: 0 8px; + z-index: 10; + line-height: var(--menu-bar-height); + cursor: pointer; + transition: color 0.5s; +} +@media only screen and (max-width: 420px) { + #menu-bar i, #menu-bar .icon-button { + padding: 0 5px; + } +} + +.icon-button { + border: none; + background: none; + padding: 0; + color: inherit; +} +.icon-button i { + margin: 0; +} + +.right-buttons { + margin: 0 15px; +} +.right-buttons a { + text-decoration: none; +} + +.left-buttons { + display: flex; + margin: 0 5px; +} +.no-js .left-buttons { + display: none; +} + +.menu-title { + display: inline-block; + font-weight: 200; + font-size: 2.4rem; + line-height: var(--menu-bar-height); + text-align: center; + margin: 0; + flex: 1; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} +.js .menu-title { + cursor: pointer; +} + +.menu-bar, +.menu-bar:visited, +.nav-chapters, +.nav-chapters:visited, +.mobile-nav-chapters, +.mobile-nav-chapters:visited, +.menu-bar .icon-button, +.menu-bar a i { + color: var(--icons); +} + +.menu-bar i:hover, +.menu-bar .icon-button:hover, +.nav-chapters:hover, +.mobile-nav-chapters i:hover { + color: var(--icons-hover); +} + +/* Nav Icons */ + +.nav-chapters { + font-size: 2.5em; + text-align: center; + text-decoration: none; + + position: fixed; + top: 0; + bottom: 0; + margin: 0; + max-width: auto; + min-width: auto; + + display: flex; + justify-content: center; + align-content: center; + flex-direction: column; + + transition: color 0.5s, background-color 0.5s; +} + +.nav-chapters:hover { + text-decoration: none; + background-color: var(--theme-hover); + transition: background-color 0.15s, color 0.15s; +} + +.nav-wrapper { + margin-top: 50px; + display: none; +} + +.mobile-nav-chapters { + font-size: 2.5em; + text-align: center; + text-decoration: none; + width: 90px; + border-radius: 5px; + background-color: var(--sidebar-bg); +} + +.previous { + float: left; +} + +.next { + float: right; + right: var(--page-padding); +} + +@media only screen and (max-width: 1080px) { + .nav-wide-wrapper { display: none; } + .nav-wrapper { display: block; } +} + +@media only screen and (max-width: 1380px) { + .sidebar-visible .nav-wide-wrapper { display: none; } + .sidebar-visible .nav-wrapper { display: block; } +} + +/* Inline code */ + +:not(pre) > .hljs { + display: inline; + padding: 0.1em 0.3em; + border-radius: 3px; +} + +:not(pre):not(a) > .hljs { + color: var(--inline-code-color); + overflow-x: initial; +} + +a:hover > .hljs { + text-decoration: underline; +} + +pre { + position: relative; +} +pre > .buttons { + position: absolute; + z-index: 100; + right: 0px; + top: 2px; + margin: 0px; + padding: 2px 0px; + + color: var(--sidebar-fg); + cursor: pointer; + visibility: hidden; + opacity: 0; + transition: visibility 0.1s linear, opacity 0.1s linear; +} +pre:hover > .buttons { + visibility: visible; + opacity: 1 +} +pre > .buttons :hover { + color: var(--sidebar-active); + border-color: var(--icons-hover); + background-color: var(--theme-hover); +} +pre > .buttons i { + margin-left: 8px; +} +pre > .buttons button { + cursor: inherit; + margin: 0px 5px; + padding: 3px 5px; + font-size: 14px; + + border-style: solid; + border-width: 1px; + border-radius: 4px; + border-color: var(--icons); + background-color: var(--theme-popup-bg); + transition: 100ms; + transition-property: color,border-color,background-color; + color: var(--icons); +} +@media (pointer: coarse) { + pre > .buttons button { + /* On mobile, make it easier to tap buttons. */ + padding: 0.3rem 1rem; + } +} +pre > code { + padding: 1rem; +} + +/* FIXME: ACE editors overlap their buttons because ACE does absolute + positioning within the code block which breaks padding. The only solution I + can think of is to move the padding to the outer pre tag (or insert a div + wrapper), but that would require fixing a whole bunch of CSS rules. +*/ +.hljs.ace_editor { + padding: 0rem 0rem; +} + +pre > .result { + margin-top: 10px; +} + +/* Search */ + +#searchresults a { + text-decoration: none; +} + +mark { + border-radius: 2px; + padding: 0 3px 1px 3px; + margin: 0 -3px -1px -3px; + background-color: var(--search-mark-bg); + transition: background-color 300ms linear; + cursor: pointer; +} + +mark.fade-out { + background-color: rgba(0,0,0,0) !important; + cursor: auto; +} + +.searchbar-outer { + margin-left: auto; + margin-right: auto; + max-width: var(--content-max-width); +} + +#searchbar { + width: 100%; + margin: 5px auto 0px auto; + padding: 10px 16px; + transition: box-shadow 300ms ease-in-out; + border: 1px solid var(--searchbar-border-color); + border-radius: 3px; + background-color: var(--searchbar-bg); + color: var(--searchbar-fg); +} +#searchbar:focus, +#searchbar.active { + box-shadow: 0 0 3px var(--searchbar-shadow-color); +} + +.searchresults-header { + font-weight: bold; + font-size: 1em; + padding: 18px 0 0 5px; + color: var(--searchresults-header-fg); +} + +.searchresults-outer { + margin-left: auto; + margin-right: auto; + max-width: var(--content-max-width); + border-bottom: 1px dashed var(--searchresults-border-color); +} + +ul#searchresults { + list-style: none; + padding-left: 20px; +} +ul#searchresults li { + margin: 10px 0px; + padding: 2px; + border-radius: 2px; +} +ul#searchresults li.focus { + background-color: var(--searchresults-li-bg); +} +ul#searchresults span.teaser { + display: block; + clear: both; + margin: 5px 0 0 20px; + font-size: 0.8em; +} +ul#searchresults span.teaser em { + font-weight: bold; + font-style: normal; +} + +/* Sidebar */ + +.sidebar { + position: fixed; + left: 0; + top: 0; + bottom: 0; + width: var(--sidebar-width); + font-size: 1em; + box-sizing: border-box; + -webkit-overflow-scrolling: touch; + overscroll-behavior-y: contain; + background-color: var(--sidebar-bg); + color: var(--sidebar-fg); +} +.sidebar-resizing { + -moz-user-select: none; + -webkit-user-select: none; + -ms-user-select: none; + user-select: none; +} +.js:not(.sidebar-resizing) .sidebar { + transition: transform 0.3s; /* Animation: slide away */ +} +.sidebar code { + line-height: 2em; +} +.sidebar .sidebar-scrollbox { + overflow-y: auto; + position: absolute; + top: 0; + bottom: 0; + left: 0; + right: 0; + padding: 10px 10px; +} +.sidebar .sidebar-resize-handle { + position: absolute; + cursor: col-resize; + width: 0; + right: 0; + top: 0; + bottom: 0; +} +.js .sidebar .sidebar-resize-handle { + cursor: col-resize; + width: 5px; +} +.sidebar-hidden .sidebar { + transform: translateX(calc(0px - var(--sidebar-width))); +} +.sidebar::-webkit-scrollbar { + background: var(--sidebar-bg); +} +.sidebar::-webkit-scrollbar-thumb { + background: var(--scrollbar); +} + +.sidebar-visible .page-wrapper { + transform: translateX(var(--sidebar-width)); +} +@media only screen and (min-width: 620px) { + .sidebar-visible .page-wrapper { + transform: none; + margin-left: var(--sidebar-width); + } +} + +.chapter { + list-style: none outside none; + padding-left: 0; + line-height: 2em; +} + +.chapter ol { + width: 100%; +} + +.chapter li { + display: flex; + color: var(--sidebar-non-existant); +} +.chapter li a { + display: block; + padding: 0; + text-decoration: none; + color: var(--sidebar-fg); +} + +.chapter li a:hover { + color: var(--sidebar-active); +} + +.chapter li a.active { + color: var(--sidebar-active); +} + +.chapter li > a.toggle { + cursor: pointer; + display: block; + margin-left: auto; + padding: 0 10px; + user-select: none; + opacity: 0.68; +} + +.chapter li > a.toggle div { + transition: transform 0.5s; +} + +/* collapse the section */ +.chapter li:not(.expanded) + li > ol { + display: none; +} + +.chapter li.chapter-item { + line-height: 1.5em; + margin-top: 0.6em; +} + +.chapter li.expanded > a.toggle div { + transform: rotate(90deg); +} + +.spacer { + width: 100%; + height: 3px; + margin: 5px 0px; +} +.chapter .spacer { + background-color: var(--sidebar-spacer); +} + +@media (-moz-touch-enabled: 1), (pointer: coarse) { + .chapter li a { padding: 5px 0; } + .spacer { margin: 10px 0; } +} + +.section { + list-style: none outside none; + padding-left: 20px; + line-height: 1.5em; +} + +/* Theme Menu Popup */ + +.theme-popup { + position: absolute; + left: 10px; + top: var(--menu-bar-height); + z-index: 1000; + border-radius: 4px; + font-size: 0.7em; + color: var(--fg); + background: var(--theme-popup-bg); + border: 1px solid var(--theme-popup-border); + margin: 0; + padding: 0; + list-style: none; + display: none; + /* Don't let the children's background extend past the rounded corners. */ + overflow: hidden; +} +.theme-popup .default { + color: var(--icons); +} +.theme-popup .theme { + width: 100%; + border: 0; + margin: 0; + padding: 2px 20px; + line-height: 25px; + white-space: nowrap; + text-align: left; + cursor: pointer; + color: inherit; + background: inherit; + font-size: inherit; +} +.theme-popup .theme:hover { + background-color: var(--theme-hover); +} + +.theme-selected::before { + display: inline-block; + content: "✓"; + margin-left: -14px; + width: 14px; +} diff --git a/documentation/operators/theme/css/general.css b/documentation/operators/theme/css/general.css new file mode 100644 index 0000000000..a06db43965 --- /dev/null +++ b/documentation/operators/theme/css/general.css @@ -0,0 +1,203 @@ +/* Base styles and content styles */ + +@import 'variables.css'; + +:root { + /* Browser default font-size is 16px, this way 1 rem = 10px */ + font-size: 70%; +} + +html { + font-family: "Open Sans", sans-serif; + color: var(--fg); + background-color: var(--bg); + text-size-adjust: none; + -webkit-text-size-adjust: none; +} + +body { + margin: 0; + font-size: 1.5rem; + overflow-x: hidden; +} + +code { + font-family: var(--mono-font) !important; + font-size: 0.9em; +} + +/* make long words/inline code not x overflow */ +main { + overflow-wrap: break-word; +} + +/* make wide tables scroll if they overflow */ +.table-wrapper { + overflow-x: auto; +} + +/* Don't change font size in headers. */ +h1 code, h2 code, h3 code, h4 code, h5 code, h6 code { + font-size: unset; +} + +.left { float: left; } +.right { float: right; } +.boring { opacity: 0.6; } +.hide-boring .boring { display: none; } +.hidden { display: none !important; } + +h2, h3 { margin-top: 2.5em; } +h4, h5 { margin-top: 2em; } + +.header + .header h3, +.header + .header h4, +.header + .header h5 { + margin-top: 1em; +} + +h1:target::before, +h2:target::before, +h3:target::before, +h4:target::before, +h5:target::before, +h6:target::before { + display: inline-block; + content: "»"; + margin-left: -30px; + width: 30px; +} + +/* This is broken on Safari as of version 14, but is fixed + in Safari Technology Preview 117 which I think will be Safari 14.2. + https://bugs.webkit.org/show_bug.cgi?id=218076 +*/ +:target { + scroll-margin-top: calc(var(--menu-bar-height) + 0.5em); +} + +.page { + outline: 0; + padding: 0 var(--page-padding); + margin-top: calc(0px - var(--menu-bar-height)); /* Compensate for the #menu-bar-hover-placeholder */ +} +.page-wrapper { + box-sizing: border-box; +} +.js:not(.sidebar-resizing) .page-wrapper { + transition: margin-left 0.3s ease, transform 0.3s ease; /* Animation: slide away */ +} + +.content { + overflow-y: auto; + padding: 0 10px; +} +.content main { + margin-left: 10%; + margin-right: 10%; + max-width: var(--content-max-width); +} +.content p { line-height: 1.45em; } +.content ol { line-height: 1.45em; } +.content ul { line-height: 1.45em; } +.content a { text-decoration: none; } +.content a:hover { text-decoration: underline; } +.content img, .content video { max-width: 100%; } +.content .header:link, +.content .header:visited { + color: var(--fg); +} +.content .header:link, +.content .header:visited:hover { + text-decoration: none; +} + +table { + margin: 0 auto; + border-collapse: collapse; +} +table td { + padding: 3px 20px; + border: 1px var(--table-border-color) solid; +} +table thead { + background: var(--table-header-bg); +} +table thead td { + font-weight: 700; + border: none; +} +table thead th { + padding: 3px 20px; +} +table thead tr { + border: 1px var(--table-header-bg) solid; +} +/* Alternate background colors for rows */ +table tbody tr:nth-child(2n) { + background: var(--table-alternate-bg); +} + + +blockquote { + margin: 20px 0; + padding: 0 20px; + color: var(--fg); + background-color: var(--quote-bg); + border-top: .1em solid var(--quote-border); + border-bottom: .1em solid var(--quote-border); +} + +kbd { + background-color: var(--table-border-color); + border-radius: 4px; + border: solid 1px var(--theme-popup-border); + box-shadow: inset 0 -1px 0 var(--theme-hover); + display: inline-block; + font-size: var(--code-font-size); + font-family: var(--mono-font); + line-height: 10px; + padding: 4px 5px; + vertical-align: middle; +} + +:not(.footnote-definition) + .footnote-definition, +.footnote-definition + :not(.footnote-definition) { + margin-top: 2em; +} +.footnote-definition { + font-size: 0.9em; + margin: 0.5em 0; +} +.footnote-definition p { + display: inline; +} + +.tooltiptext { + position: absolute; + visibility: hidden; + color: #fff; + background-color: #333; + transform: translateX(-50%); /* Center by moving tooltip 50% of its width left */ + left: -8px; /* Half of the width of the icon */ + top: -35px; + font-size: 0.8em; + text-align: center; + border-radius: 6px; + padding: 5px 8px; + margin: 5px; + z-index: 1000; +} +.tooltipped .tooltiptext { + visibility: visible; +} + +.chapter li.part-title { + color: var(--sidebar-fg); + margin: 5px 0px; + font-weight: bold; +} + +.result-no-output { + font-style: italic; +} diff --git a/documentation/operators/theme/css/variables.css b/documentation/operators/theme/css/variables.css new file mode 100644 index 0000000000..8dfcf92239 --- /dev/null +++ b/documentation/operators/theme/css/variables.css @@ -0,0 +1,3257 @@ + +/* Globals */ + +:root { + --sidebar-width: 300px; + --page-padding: 20px; + --content-max-width: 70%; + --menu-bar-height: 40px; + --mono-font: "Source Code Pro", Consolas, "Ubuntu Mono", Menlo, "DejaVu Sans Mono", monospace, monospace; + --code-font-size: 0.875em /* please adjust the ace font size accordingly in editor.js */ + --pagetoc-width: 13%; + --pagetoc-fontsize: 14.5px; +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +/* Themes */ + +.ayu { + --bg: hsl(210, 25%, 8%); + --fg: #c5c5c5; + + --sidebar-bg: #14191f; + --sidebar-fg: #c8c9db; + --sidebar-non-existant: #5c6773; + --sidebar-active: #ffb454; + --sidebar-spacer: #2d334f; + + --scrollbar: var(--sidebar-fg); + + --icons: #737480; + --icons-hover: #b7b9cc; + + --links: #0096cf; + + --inline-code-color: #ffb454; + + --theme-popup-bg: #14191f; + --theme-popup-border: #5c6773; + --theme-hover: #191f26; + + --quote-bg: hsl(226, 15%, 17%); + --quote-border: hsl(226, 15%, 22%); + + --table-border-color: hsl(210, 25%, 13%); + --table-header-bg: hsl(210, 25%, 28%); + --table-alternate-bg: hsl(210, 25%, 11%); + + --searchbar-border-color: #848484; + --searchbar-bg: #424242; + --searchbar-fg: #fff; + --searchbar-shadow-color: #d4c89f; + --searchresults-header-fg: #666; + --searchresults-border-color: #888; + --searchresults-li-bg: #252932; + --search-mark-bg: #e3b171; +} + +.coal { + --bg: hsl(200, 7%, 8%); + --fg: #98a3ad; + + --sidebar-bg: #292c2f; + --sidebar-fg: #a1adb8; + --sidebar-non-existant: #505254; + --sidebar-active: #3473ad; + --sidebar-spacer: #393939; + + --scrollbar: var(--sidebar-fg); + + --icons: #43484d; + --icons-hover: #b3c0cc; + + --links: #2b79a2; + + --inline-code-color: #c5c8c6; + + --theme-popup-bg: #141617; + --theme-popup-border: #43484d; + --theme-hover: #1f2124; + + --quote-bg: hsl(234, 21%, 18%); + --quote-border: hsl(234, 21%, 23%); + + --table-border-color: hsl(200, 7%, 13%); + --table-header-bg: hsl(200, 7%, 28%); + --table-alternate-bg: hsl(200, 7%, 11%); + + --searchbar-border-color: #aaa; + --searchbar-bg: #b7b7b7; + --searchbar-fg: #000; + --searchbar-shadow-color: #aaa; + --searchresults-header-fg: #666; + --searchresults-border-color: #98a3ad; + --searchresults-li-bg: #2b2b2f; + --search-mark-bg: #355c7d; +} + +.light { + --bg: hsl(0, 0%, 100%); + --fg: hsl(0, 0%, 0%); + + --sidebar-bg: #fafafa; + --sidebar-fg: hsl(0, 0%, 0%); + --sidebar-non-existant: #aaaaaa; + --sidebar-active: #1f1fff; + --sidebar-spacer: #f4f4f4; + + --scrollbar: #8F8F8F; + + --icons: #747474; + --icons-hover: #000000; + + --links: #1f1fff; + + --inline-code-color: #F42C4C; + + --theme-popup-bg: #fafafa; + --theme-popup-border: #cccccc; + --theme-hover: #e6e6e6; + + --quote-bg: hsl(197, 37%, 96%); + --quote-border: hsl(197, 37%, 91%); + + --table-border-color: hsl(0, 0%, 95%); + --table-header-bg: hsl(0, 0%, 80%); + --table-alternate-bg: hsl(0, 0%, 97%); + + --searchbar-border-color: #aaa; + --searchbar-bg: #fafafa; + --searchbar-fg: #000; + --searchbar-shadow-color: #aaa; + --searchresults-header-fg: #666; + --searchresults-border-color: #888; + --searchresults-li-bg: #e4f2fe; + --search-mark-bg: #a2cff5; +} + +.navy { + --bg: hsl(226, 23%, 11%); + --fg: #bcbdd0; + + --sidebar-bg: #282d3f; + --sidebar-fg: #c8c9db; + --sidebar-non-existant: #505274; + --sidebar-active: #2b79a2; + --sidebar-spacer: #2d334f; + + --scrollbar: var(--sidebar-fg); + + --icons: #737480; + --icons-hover: #b7b9cc; + + --links: #2b79a2; + + --inline-code-color: #c5c8c6; + + --theme-popup-bg: #161923; + --theme-popup-border: #737480; + --theme-hover: #282e40; + + --quote-bg: hsl(226, 15%, 17%); + --quote-border: hsl(226, 15%, 22%); + + --table-border-color: hsl(226, 23%, 16%); + --table-header-bg: hsl(226, 23%, 31%); + --table-alternate-bg: hsl(226, 23%, 14%); + + --searchbar-border-color: #aaa; + --searchbar-bg: #aeaec6; + --searchbar-fg: #000; + --searchbar-shadow-color: #aaa; + --searchresults-header-fg: #5f5f71; + --searchresults-border-color: #5c5c68; + --searchresults-li-bg: #242430; + --search-mark-bg: #a2cff5; +} + +.rust { + --bg: hsl(60, 9%, 87%); + --fg: #262625; + + --sidebar-bg: #3b2e2a; + --sidebar-fg: #c8c9db; + --sidebar-non-existant: #505254; + --sidebar-active: #e69f67; + --sidebar-spacer: #45373a; + + --scrollbar: var(--sidebar-fg); + + --icons: #737480; + --icons-hover: #262625; + + --links: #2b79a2; + + --inline-code-color: #6e6b5e; + + --theme-popup-bg: #e1e1db; + --theme-popup-border: #b38f6b; + --theme-hover: #99908a; + + --quote-bg: hsl(60, 5%, 75%); + --quote-border: hsl(60, 5%, 70%); + + --table-border-color: hsl(60, 9%, 82%); + --table-header-bg: #b3a497; + --table-alternate-bg: hsl(60, 9%, 84%); + + --searchbar-border-color: #aaa; + --searchbar-bg: #fafafa; + --searchbar-fg: #000; + --searchbar-shadow-color: #aaa; + --searchresults-header-fg: #666; + --searchresults-border-color: #888; + --searchresults-li-bg: #dec2a2; + --search-mark-bg: #e69f67; +} + +@media (prefers-color-scheme: dark) { + .light.no-js { + --bg: hsl(200, 7%, 8%); + --fg: #98a3ad; + + --sidebar-bg: #292c2f; + --sidebar-fg: #a1adb8; + --sidebar-non-existant: #505254; + --sidebar-active: #3473ad; + --sidebar-spacer: #393939; + + --scrollbar: var(--sidebar-fg); + + --icons: #43484d; + --icons-hover: #b3c0cc; + + --links: #2b79a2; + + --inline-code-color: #c5c8c6; + + --theme-popup-bg: #141617; + --theme-popup-border: #43484d; + --theme-hover: #1f2124; + + --quote-bg: hsl(234, 21%, 18%); + --quote-border: hsl(234, 21%, 23%); + + --table-border-color: hsl(200, 7%, 13%); + --table-header-bg: hsl(200, 7%, 28%); + --table-alternate-bg: hsl(200, 7%, 11%); + + --searchbar-border-color: #aaa; + --searchbar-bg: #b7b7b7; + --searchbar-fg: #000; + --searchbar-shadow-color: #aaa; + --searchresults-header-fg: #666; + --searchresults-border-color: #98a3ad; + --searchresults-li-bg: #2b2b2f; + --search-mark-bg: #355c7d; + } +} diff --git a/documentation/operators/theme/index.hbs b/documentation/operators/theme/index.hbs new file mode 100644 index 0000000000..06a3731f33 --- /dev/null +++ b/documentation/operators/theme/index.hbs @@ -0,0 +1,1063 @@ + + + + + + {{ title }} + {{#if is_print }} + + {{/if}} + {{#if base_url}} + + {{/if}} + + + + {{> head}} + + + + + + {{#if favicon_svg}} + + {{/if}} + {{#if favicon_png}} + + {{/if}} + + + + {{#if print_enable}} + + {{/if}} + + + + {{#if copy_fonts}} + + {{/if}} + + + + + + + + {{#each additional_css}} + + {{/each}} + + {{#if mathjax_support}} + + + {{/if}} + + + + + + + + + + + + + + + + +
+ +
+ {{> header}} + + + + {{#if search_enabled}} + + {{/if}} + + + + +
+
+ +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + {{{ content }}} +
+ + +
+
+ + + +
+ + {{#if live_reload_endpoint}} + + + {{/if}} + + {{#if google_analytics}} + + + {{/if}} + + {{#if playground_line_numbers}} + + {{/if}} + + {{#if playground_copyable}} + + {{/if}} + + {{#if playground_js}} + + + + + + {{/if}} + + {{#if search_js}} + + + + {{/if}} + + + + + + + {{#each additional_js}} + + {{/each}} + + {{#if is_print}} + {{#if mathjax_support}} + + {{else}} + + {{/if}} + {{/if}} + + + diff --git a/documentation/operators/theme/pagetoc.css b/documentation/operators/theme/pagetoc.css new file mode 100644 index 0000000000..a68e5b8e21 --- /dev/null +++ b/documentation/operators/theme/pagetoc.css @@ -0,0 +1,47 @@ +/* src: https://github.com/JorelAli/mdBook-pagetoc */ + +@media only screen and (max-width:1439px) { + .sidetoc { + display: none; + } +} + +@media only screen and (min-width:1440px) { + main { + position: relative; + } + .sidetoc { + margin-left: auto; + margin-right: auto; + /* left: calc(90% + (var(--content-min-width))/4 - 110px); */ + left: 101%; + position: absolute; + font-size: var(--pagetoc-fontsize); + } + .pagetoc { + position: fixed; + width: var(--pagetoc-width); + } + .pagetoc a { + border-left: 1px solid var(--sidebar-bg); + /* color: var(--fg); */ + /* color: var(--sidebar-fg); */ + color: var(--links); + display: block; + padding-bottom: 5px; + padding-top: 5px; + padding-left: 10px; + text-align: left; + text-decoration: none; + font-weight: normal; + background: var(--sidebar-bg); + } + .pagetoc a:hover, + .pagetoc a.active { + background: var(--sidebar-bg); + /* color: var(--sidebar-fg); */ + color: var(--sidebar-active); + font-weight: bold; + font-size: var(--pagetoc-fontsize); + } +} diff --git a/documentation/operators/theme/pagetoc.js b/documentation/operators/theme/pagetoc.js new file mode 100644 index 0000000000..b11052427e --- /dev/null +++ b/documentation/operators/theme/pagetoc.js @@ -0,0 +1,68 @@ +// src: https://github.com/JorelAli/mdBook-pagetoc + +// Un-active everything when you click it +Array.prototype.forEach.call(document.getElementsByClassName("pagetoc")[0].children, function(el, i) { + el.addEventHandler("click", function() { + Array.prototype.forEach.call(document.getElementsByClassName("pagetoc")[0].children, function(el, i) { + el.classList.remove("active"); + }); + el.classList.add("active"); + }); +}); + +var updateFunction = function() { + + var id; + var elements = document.getElementsByClassName("header"); + Array.prototype.forEach.call(elements, function(el, i) { + if (window.pageYOffset >= el.offsetTop) { + id = el; + } + }); + + Array.prototype.forEach.call(document.getElementsByClassName("pagetoc")[0].children, function(el, i) { + el.classList.remove("active"); + }); + + Array.prototype.forEach.call(document.getElementsByClassName("pagetoc")[0].children, function(el, i) { + if (id.href.localeCompare(el.href) == 0) { + el.classList.add("active"); + } + }); +}; + +// Populate sidebar on load +window.addEventListener('load', function() { + var pagetoc = document.getElementsByClassName("pagetoc")[0]; + var elements = document.getElementsByClassName("header"); + Array.prototype.forEach.call(elements, function(el, i) { + var link = document.createElement("a"); + + // Indent shows hierarchy + var indent = ""; + switch (el.parentElement.tagName) { + case "H2": + indent = "20px"; + break; + case "H3": + indent = "40px"; + break; + case "H4": + indent = "60px"; + break; + default: + break; + } + + link.appendChild(document.createTextNode(el.text)); + link.style.paddingLeft = indent; + link.href = el.href; + pagetoc.appendChild(link); + }); + updateFunction.call(); +}); + + + +// Handle active elements on scroll +window.addEventListener("scroll", updateFunction); From 502a2d6a23c492197b9c9152a2521d2b5eb14c69 Mon Sep 17 00:00:00 2001 From: serinko <97586125+serinko@users.noreply.github.com> Date: Wed, 2 Aug 2023 08:04:57 +0200 Subject: [PATCH 043/361] operators: plugins work, ready for final review --- documentation/operators/book.toml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/documentation/operators/book.toml b/documentation/operators/book.toml index 3a0a9f722b..28efe62c57 100644 --- a/documentation/operators/book.toml +++ b/documentation/operators/book.toml @@ -53,9 +53,9 @@ platform_release_version = "v1.1.22" wallet_release_version = "v1.2.5" -# [preprocessor.last-changed] -# command = "mdbook-last-changed" -# renderer = ["html"] +[preprocessor.last-changed] +command = "mdbook-last-changed" +renderer = ["html"] # used for grabbing output of binary commands for automation From 40ffb6b65d75c1892e8711af3677efd1a06842ca Mon Sep 17 00:00:00 2001 From: mfahampshire Date: Wed, 2 Aug 2023 08:59:37 +0200 Subject: [PATCH 044/361] continued working on dev portal tutorial --- .../src/tutorials/cosmos-service/client.md | 22 ++++++ .../src/tutorials/cosmos-service/lib.md | 71 +++++++++++++++++-- 2 files changed, 88 insertions(+), 5 deletions(-) diff --git a/documentation/dev-portal/src/tutorials/cosmos-service/client.md b/documentation/dev-portal/src/tutorials/cosmos-service/client.md index c6db01fb6a..c3f6c6ee10 100644 --- a/documentation/dev-portal/src/tutorials/cosmos-service/client.md +++ b/documentation/dev-portal/src/tutorials/cosmos-service/client.md @@ -1 +1,23 @@ # Preparing Your Client + +Start by creating the startup logic of your `client` - creating a Nym client and connecting to the mixnet (or just connecting if your client has been started before and config already exists for it), and defining commands. + +Start in `bin/client.rs`. + +## Dependencies +Import the following dependencies: +``` +use clap::{Args, Parser, Subcommand}; +use nym_sdk::mixnet::Recipient; +use nym_validator_client::nyxd::AccountId; +use nym_cosmos_service::create_client; +use nym_bin_common::logging::setup_logging; +``` + +`clap` is used so different commands can be passed to the `client` (even though we're only defining one function in this first part of the tutorial, more will be added in subsequent chapters). `nym_sdk::mixnet::Recipient` is the type used to define the recipient of a mixnet message, `nym_bin_common::logging::setup_logging` is the logging setup for `client`'s Nym client, and `nym_cosmos_service::create_client` imports the `create_client` function created on the previous page. + +## CLI Command with Clap + +< simple account query first - then maybe a contract query > + +## diff --git a/documentation/dev-portal/src/tutorials/cosmos-service/lib.md b/documentation/dev-portal/src/tutorials/cosmos-service/lib.md index ef041b1838..37a2f46a22 100644 --- a/documentation/dev-portal/src/tutorials/cosmos-service/lib.md +++ b/documentation/dev-portal/src/tutorials/cosmos-service/lib.md @@ -17,7 +17,7 @@ pub mod client; pub mod service; ``` -Since this is the file where client creation and message parsing are handled, the various `nym_sdk` imports, as well as `serde`'s (de)serialisation functionality, are required. This file also imports the `client` and `service` specific logic not found in `bin/`, as well as `PathBuf` to read filepaths, and `cosmrs` types for defining Nyx blockchain accounts. +Since this is the file where client creation and message parsing are handled, the various `nym_sdk` imports, as well as `serde`'s (de)serialisation functionality, are required. `PathBuf` is for reading filepaths, and `cosmrs` types are required for defining Nyx blockchain accounts. ## Constants Below this are the chain-related `const` variables. These have been hardcoded for this demo. @@ -44,7 +44,7 @@ pub struct SequenceRequest { pub struct SequenceRequestResponse { pub account_number: u64, pub sequence: u64, - pub chain_id: tendermint::chain::Id, + pub chain_id: [tendermint](tendermint)::chain::Id, } #[derive(Debug, Deserialize, Serialize)] @@ -60,7 +60,11 @@ pub enum ResponseTypes { } ``` -TODO explainer of each +The above data types are pretty straightforward. Even though there are only one instance of a request type (sent from client to service) and one of a response type (service -> client) so far, a pair of enums has been defined to contain additional response or request types that will be added in the future. + +`SequenceRequest` will be used when requesting the service to query the chain on the client's behalf for an address' sequence information (used for offline signing). You can see the information that will be returned from the chain to the service, and from the service to the client, in `SequenceRequestResponse`. + +> Although `SequenceResponse` would have been more succinct, there is already a `cosmrs` type with this name. As such the response type was given a different name to avoid confusion. ## Shared Functions Now to define functions shared by the `client` and `service` binaries. @@ -87,6 +91,63 @@ pub async fn create_client(config_path: PathBuf) -> MixnetClient { > If keys and config already exist at this location, re-running this function **will not** overwrite them. ### Parsing Incoming messages -TODO smoosh them into one function with the return type being an `Option` instead - Next to define two functions: one for listening _for_ messages from the mixnet (used by our `service`), and one for listening out for a _reply_ after sending a message to another Nym client (in this case, when sending a message from the `client` to the `service`). + +```rust +pub async fn listen_and_parse_response(client: &mut MixnetClient) -> anyhow::Result { + let mut message: Vec = Vec::new(); + + // get the actual message - discard the empty vec sent along with the SURB topup request + while let Some(new_message) = client.wait_for_messages().await { + if new_message.is_empty() { + println!("got a request for more SURBs from service - sending additional SURBs to sender"); + continue; + } + message = new_message; + break; + } + + // parse vec -> JSON String + let mut parsed = String::new(); + if let Some(r) = message.iter().next() { + parsed = String::from_utf8(r.message.clone())?; + } + let sp_response: crate::ResponseTypes = serde_json::from_str(&parsed)?; + Ok(sp_response) +} + +pub async fn listen_and_parse_request( + client: &mut MixnetClient, +) -> anyhow::Result<(RequestTypes, AnonymousSenderTag)> { + let mut message: Vec = Vec::new(); + + while let Some(new_message) = client.wait_for_messages().await { + if new_message.is_empty() { + println!("got empty vec - probably the SURBs sent along with the request"); + continue; + } + message = new_message; + break; + } + + // parse vec -> JSON String + let mut parsed = String::new(); + if let Some(r) = message.iter().next() { + parsed = String::from_utf8(r.message.clone())?; + } + let client_request: crate::RequestTypes = serde_json::from_str(&parsed)?; + + // get the sender_tag for anon reply + let return_recipient = message[0].sender_tag.unwrap(); + + Ok((client_request, return_recipient)) +} +``` + +Aside from the return types that each function has, the main difference is that any incoming requests to the `service` will also have SURB packets attached to it. + +< smol explanation of SURBs + link > + +As such, this function returns a tuple containing the `RequestType` _and_ the `sender_tag` used by the `service` to identify which bucket of pre-addressed replies it will use to respond to a request. + +< note concerning parsing out the empty vecs > From 60264486fd6000787329b2bf36454d3fbdc1721c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 2 Aug 2023 08:37:23 +0000 Subject: [PATCH 045/361] Bump semver from 6.3.0 to 6.3.1 in /nym-api/tests Bumps [semver](https://github.com/npm/node-semver) from 6.3.0 to 6.3.1. - [Release notes](https://github.com/npm/node-semver/releases) - [Changelog](https://github.com/npm/node-semver/blob/v6.3.1/CHANGELOG.md) - [Commits](https://github.com/npm/node-semver/compare/v6.3.0...v6.3.1) --- updated-dependencies: - dependency-name: semver dependency-type: indirect ... Signed-off-by: dependabot[bot] --- nym-api/tests/package-lock.json | 60 ++++++++++++++++----------------- nym-api/tests/yarn.lock | 12 +++---- 2 files changed, 36 insertions(+), 36 deletions(-) diff --git a/nym-api/tests/package-lock.json b/nym-api/tests/package-lock.json index 020a0426be..f08dccfcf4 100644 --- a/nym-api/tests/package-lock.json +++ b/nym-api/tests/package-lock.json @@ -103,9 +103,9 @@ } }, "node_modules/@babel/core/node_modules/semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "dev": true, "bin": { "semver": "bin/semver.js" @@ -158,9 +158,9 @@ } }, "node_modules/@babel/helper-compilation-targets/node_modules/semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "dev": true, "bin": { "semver": "bin/semver.js" @@ -2883,9 +2883,9 @@ } }, "node_modules/istanbul-lib-instrument/node_modules/semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "dev": true, "bin": { "semver": "bin/semver.js" @@ -3653,9 +3653,9 @@ } }, "node_modules/make-dir/node_modules/semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "dev": true, "bin": { "semver": "bin/semver.js" @@ -4273,9 +4273,9 @@ "dev": true }, "node_modules/semver": { - "version": "7.3.7", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz", - "integrity": "sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==", + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", "dev": true, "dependencies": { "lru-cache": "^6.0.0" @@ -4918,9 +4918,9 @@ }, "dependencies": { "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "dev": true } } @@ -4962,9 +4962,9 @@ }, "dependencies": { "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "dev": true } } @@ -6970,9 +6970,9 @@ }, "dependencies": { "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "dev": true } } @@ -7559,9 +7559,9 @@ }, "dependencies": { "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "dev": true } } @@ -7984,9 +7984,9 @@ "dev": true }, "semver": { - "version": "7.3.7", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz", - "integrity": "sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==", + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", "dev": true, "requires": { "lru-cache": "^6.0.0" diff --git a/nym-api/tests/yarn.lock b/nym-api/tests/yarn.lock index 9e5476cf80..4fc3d5aa56 100644 --- a/nym-api/tests/yarn.lock +++ b/nym-api/tests/yarn.lock @@ -2566,16 +2566,16 @@ safe-buffer@~5.1.1: integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== semver@7.x, semver@^7.3.5, semver@^7.3.7: - version "7.3.7" - resolved "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz" - integrity sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g== + version "7.5.4" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.5.4.tgz#483986ec4ed38e1c6c48c34894a9182dbff68a6e" + integrity sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA== dependencies: lru-cache "^6.0.0" semver@^6.0.0, semver@^6.3.0: - version "6.3.0" - resolved "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz" - integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw== + version "6.3.1" + resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4" + integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA== shebang-command@^2.0.0: version "2.0.0" From 6395303466174a4c13e4aaad08db8fa56385c66a Mon Sep 17 00:00:00 2001 From: serinko <97586125+serinko@users.noreply.github.com> Date: Wed, 2 Aug 2023 10:40:26 +0200 Subject: [PATCH 046/361] #3674 feedback implemented --- .../docs/src/binaries/building-nym.html | 350 ------------------ .../docs/src/binaries/init-and-config.html | 290 --------------- .../docs/src/binaries/pre-built-binaries.html | 286 -------------- documentation/docs/src/introduction.md | 3 - documentation/operators/src/introduction.md | 15 +- .../operators/src/nodes/troubleshooting.md | 8 +- .../operators/src/preliminary-steps.md | 8 +- 7 files changed, 7 insertions(+), 953 deletions(-) delete mode 100644 documentation/docs/src/binaries/building-nym.html delete mode 100644 documentation/docs/src/binaries/init-and-config.html delete mode 100644 documentation/docs/src/binaries/pre-built-binaries.html diff --git a/documentation/docs/src/binaries/building-nym.html b/documentation/docs/src/binaries/building-nym.html deleted file mode 100644 index 2f73af4b4f..0000000000 --- a/documentation/docs/src/binaries/building-nym.html +++ /dev/null @@ -1,350 +0,0 @@ - - - - - - Building from Source - Nym Operators Guides - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - -
- -
- - - - - - - - -
-
- {comment} -
- - {comment} -
- - {comment} -
- - {comment} -
- - {comment} -
- - {comment} -
- - {comment} -
- - {comment} -
- - {comment} -
- - {comment} -
- - {comment} -
- - {comment} -
- - {comment} -
- - {comment} -
- - {comment} -
- - {comment} -
- - {comment} -
- - {comment} -
- - {comment} -
- - {comment} -
- - {comment} -
- -

Building from Source

-
-

Nym runs on Mac OS X, Linux, and Windows. All nodes except the Desktop Wallet and NymConnect on Windows should be considered experimental - it works fine if you’re an app developer but isn’t recommended for running nodes.

-
-

Building Nym

-

Nym has two main codebases:

-
    -
  • the Nym platform, written in Rust. This contains all of our code except for the validators.
  • -
  • the Nym validators, written in Go.
  • -
-
-

This page details how to build the main Nym platform code. If you want to build and run a validator, go here instead.

-
-

Prerequisites

-
    -
  • Debian/Ubuntu: pkg-config, build-essential, libssl-dev, curl, jq, git
  • -
-
apt install pkg-config build-essential libssl-dev curl jq git
-
-
    -
  • Arch/Manjaro: base-devel
  • -
-
pacman -S base-devel
-
-
    -
  • Mac OS X: pkg-config , brew, openss1, protobuf, curl, git -Running the following the script installs Homebrew and the above dependencies:
  • -
-
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
-
-
    -
  • Rust & cargo >= 1.66
  • -
-

We recommend using the Rust shell script installer. Installing cargo from your package manager (e.g. apt) is not recommended as the packaged versions are usually too old.

-

If you really don’t want to use the shell script installer, the Rust installation docs contain instructions for many platforms.

-

Download and build Nym binaries

-

The following commands will compile binaries into the nym/target/release directory:

-
rustup update
-git clone https://github.com/nymtech/nym.git
-cd nym
-
-git reset --hard # in case you made any changes on your branch
-git pull # in case you've checked it out before
-
-git checkout release/v1.1.22 # checkout to the latest release branch: `develop` will most likely be incompatible with deployed public networks
-
-cargo build --release # build your binaries with **mainnet** configuration
-NETWORK=sandbox cargo build --release # build your binaries with **sandbox** configuration
-
-

Quite a bit of stuff gets built. The key working parts are:

- -

The repository also contains Typescript applications which aren’t built in this process. These can be built by following the instructions on their respective docs pages.

- -
-

You cannot build from GitHub’s .zip or .tar.gz archive files on the releases page - the Nym build scripts automatically include the current git commit hash in the built binary during compilation, so the build will fail if you use the archive code (which isn’t a Git repository). Check the code out from github using git clone instead.

-
- -
- - -
-
- - - -
- - - - - - - - - - - - - - - - - - - - - -
- - diff --git a/documentation/docs/src/binaries/init-and-config.html b/documentation/docs/src/binaries/init-and-config.html deleted file mode 100644 index 147c89cc2d..0000000000 --- a/documentation/docs/src/binaries/init-and-config.html +++ /dev/null @@ -1,290 +0,0 @@ - - - - - - Binary Initialisation and Configuration - Nym Operators Guides - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - -
- -
- - - - - - - - -
-
- {comment} -
- - {comment} -
- - {comment} -
- - {comment} -
- - {comment} -
- - {comment} -
- - {comment} -
- - {comment} -
- - {comment} -
- - {comment} -
- - {comment} -
- - {comment} -
- - {comment} -
- - {comment} -
- - {comment} -
- - {comment} -
- - {comment} -
- - {comment} -
- - {comment} -
- - {comment} -
- - {comment} -
- -

Binary Initialisation and Configuration

-

All Nym binaries must first be initialised with init before being run.

-

The init command is usually where you pass flags specifying configuration arguments such as the gateway you wish to communicate with, the ports you wish your binary to listen on, etc.

-

The init command will also create the necessary keypairs and configuration files at ~/.nym/<BINARY_TYPE>/<BINARY_ID>/ if these files do not already exist. It will not overwrite existing keypairs if they are present.

-

You can reconfigure your binaries at any time by editing the config file located at ~/.nym/<BINARY_TYPE>/<BINARY_ID>/config/config.toml and restarting the binary process.

-

Once you have run init, you can start your binary with the run command, usually only accompanied by the id of the binary that you specified.

-

This id is never transmitted over the network, and is used to select which local config and key files to use for startup.

- -
- - -
-
- - - -
- - - - - - - - - - - - - - - - - - - - - -
- - diff --git a/documentation/docs/src/binaries/pre-built-binaries.html b/documentation/docs/src/binaries/pre-built-binaries.html deleted file mode 100644 index 3497c15d85..0000000000 --- a/documentation/docs/src/binaries/pre-built-binaries.html +++ /dev/null @@ -1,286 +0,0 @@ - - - - - - Pre-built Binaries - Nym Operators Guides - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - -
- -
- - - - - - - - -
-
- {comment} -
- - {comment} -
- - {comment} -
- - {comment} -
- - {comment} -
- - {comment} -
- - {comment} -
- - {comment} -
- - {comment} -
- - {comment} -
- - {comment} -
- - {comment} -
- - {comment} -
- - {comment} -
- - {comment} -
- - {comment} -
- - {comment} -
- - {comment} -
- - {comment} -
- - {comment} -
- - {comment} -
- -

Pre-built Binaries

-

The Github releases page has pre-built binaries which should work on Ubuntu 20.04 and other Debian-based systems, but at this stage cannot be guaranteed to work everywhere.

-

If the pre-built binaries don’t work or are unavailable for your system, you will need to build the platform yourself.

- -
- - -
-
- - - -
- - - - - - - - - - - - - - - - - - - - - -
- - diff --git a/documentation/docs/src/introduction.md b/documentation/docs/src/introduction.md index a6cf38aae7..9e245497f4 100644 --- a/documentation/docs/src/introduction.md +++ b/documentation/docs/src/introduction.md @@ -4,9 +4,6 @@ This is Nym's technical documentation, containing information and setup guides a If you are new to Nym and want to learn about the mixnet, how to integrate with the network, developer tutorials and quickstart guides, check out the [Developer Portal](https://nymtech.net/developers/). -If you are looking for information on grants and beta-release Nym apps, check out the [Shipyard](https://shipyard.nymtech.net) site. - - ## Popular pages **Network Architecture:** * [Network Overview](./architecture/network-overview.md) diff --git a/documentation/operators/src/introduction.md b/documentation/operators/src/introduction.md index 48b5984eef..acf29f9363 100644 --- a/documentation/operators/src/introduction.md +++ b/documentation/operators/src/introduction.md @@ -4,9 +4,7 @@ This is Nym's operators guide, containing information and setup tutorials about If you are new to Nym and want to learn about the mixnet, how to integrate with the network, developer tutorials and quickstart guides, check out the [Developer Portal](https://nymtech.net/developers/). -If you want to go deeper into Nym architecture and various software such as different, application clients, existing applications like the desktop wallet, mixnet explorer or SDKs, visit [Nym's technical documentation](https://nymtech.net/docs/). - -If you are looking for information on grants and beta-release Nym apps, check out the [Shipyard](https://shipyard.nymtech.net) site. +If you want to dive deeper into Nym architecture and such as different clients, existing applications like the desktop wallet, explorers or SDKs, visit [Nym's technical documentation](https://nymtech.net/docs/). ## Popular pages @@ -28,14 +26,3 @@ If you are looking for information on grants and beta-release Nym apps, check ou * [Troubleshooting](./nodes/troubleshooting.md) * [FAQ](./faq.md) - diff --git a/documentation/operators/src/nodes/troubleshooting.md b/documentation/operators/src/nodes/troubleshooting.md index e5333bcfe4..f063d985e0 100644 --- a/documentation/operators/src/nodes/troubleshooting.md +++ b/documentation/operators/src/nodes/troubleshooting.md @@ -1,9 +1,3 @@ - - # Troubleshooting ## Binary Build Problems @@ -73,6 +67,8 @@ There are 2 community explorers currently, which have been created by [Nodes Gur * [Mainnet](https://mixnet.explorers.guru/) * [Sandbox testnet](https://sandbox.mixnet.explorers.guru/) +[Here](https://github.com/cosmos/chain-registry/blob/master/nyx/chain.json#L158-L187) is a dictionary with Nyx chain registry entry regarding all explorers. + If you want more information, or if your node isn't showing up on the explorer of your choice and you want to double-check, here are some examples on how to check if the node is configured properly. #### Check from your VPS diff --git a/documentation/operators/src/preliminary-steps.md b/documentation/operators/src/preliminary-steps.md index 5cb76d8e19..53f5383838 100644 --- a/documentation/operators/src/preliminary-steps.md +++ b/documentation/operators/src/preliminary-steps.md @@ -1,12 +1,12 @@ # Preliminary Steps -> The Nym mixnode, gateway and network requester binaries were built in the [building nym](../binaries/building-nym.md) section. If you haven't yet built Nym and want to run the code, go there first. +> The Nym `mixnode`, `gateway` and `network-requester` binaries were built in the [building nym](../binaries/building-nym.md) section. If you haven't yet built Nym and want to run the code, go there first. ## Preliminary steps There are a couple of steps that need completing before starting to set up your mixnode, gateway or a network requester: -- preparing your [wallet](https://nymtech.net/docs/wallet/desktop-wallet.html) +- preparing your [desktop wallet](https://nymtech.net/docs/wallet/desktop-wallet.html) or [CLI wallet](https://nymtech.net/docs/wallet/cli-wallet.html). - requisitioning a VPS (Virtual Private Server) ### Wallet preparation @@ -32,12 +32,12 @@ For now, see the below rough specs: - Processors: 2 cores are fine. Get the fastest CPUs you can afford. -#### For Nym Mixnode +#### For Mixnode - RAM: Memory requirements are very low - typically a mix node may use only a few hundred MB of RAM. - Disks: The mixnodes require no disk space beyond a few bytes for the configuration files. -#### For Nym Gateway +#### For Gateway - RAM: Memory requirements depend on the amount of users your Gateway will be serving at any one time. If you're just going to be using it yourself, then minimal RAM is fine. **If you're running your Gateway as part of a Service Grant, get something with at least 4GB RAM.** - Disks: much like the amount of RAM your Gateway could use, the amount of disk space required will vary with the amount of users your Gateway is serving. **If you're running your Gateway as part of a Service Grant, get something with at least 40GB storage.** From cd70b0de75870a51969d84dbaabffc10fa0d1a9d Mon Sep 17 00:00:00 2001 From: pierre Date: Wed, 2 Aug 2023 11:18:23 +0200 Subject: [PATCH 047/361] build(nc-android): disable sentry upload proguard mapping files --- nym-connect/native/android/app/build.gradle.kts | 6 ++++++ .../android/app/src/main/java/net/nymtech/nyms5/App.kt | 1 - 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/nym-connect/native/android/app/build.gradle.kts b/nym-connect/native/android/app/build.gradle.kts index b33dd6d715..decadfc6dc 100644 --- a/nym-connect/native/android/app/build.gradle.kts +++ b/nym-connect/native/android/app/build.gradle.kts @@ -101,6 +101,12 @@ android { } } +sentry { + // TODO disable auto upload of mapping files for now to ease FDroid submission + // (avoiding to have to provide a sentry auth token during compile time) + autoUploadProguardMapping.set(false) +} + dependencies { implementation("androidx.core:core-ktx:1.10.1") diff --git a/nym-connect/native/android/app/src/main/java/net/nymtech/nyms5/App.kt b/nym-connect/native/android/app/src/main/java/net/nymtech/nyms5/App.kt index 105270644c..6da2011ffc 100644 --- a/nym-connect/native/android/app/src/main/java/net/nymtech/nyms5/App.kt +++ b/nym-connect/native/android/app/src/main/java/net/nymtech/nyms5/App.kt @@ -19,7 +19,6 @@ import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.map import kotlinx.coroutines.runBlocking - val Context.dataStore: DataStore by preferencesDataStore(name = "settings") val monitoringKey = booleanPreferencesKey("monitoring") From 5ab2c738dfaafcf090c9ffa282c0a271c48544ee Mon Sep 17 00:00:00 2001 From: Tommy Verrall Date: Wed, 2 Aug 2023 12:54:09 +0200 Subject: [PATCH 048/361] fix api tests for no blacklisted nodes --- .../gateway/contract-cache-gateway.test.ts | 11 +- .../mixnode/contract-cache-mixnode.test.ts | 167 +++++++++++++----- 2 files changed, 134 insertions(+), 44 deletions(-) diff --git a/nym-api/tests/functional_test/contract_cache/gateway/contract-cache-gateway.test.ts b/nym-api/tests/functional_test/contract_cache/gateway/contract-cache-gateway.test.ts index 465b6a9a12..992e35c89a 100644 --- a/nym-api/tests/functional_test/contract_cache/gateway/contract-cache-gateway.test.ts +++ b/nym-api/tests/functional_test/contract_cache/gateway/contract-cache-gateway.test.ts @@ -40,8 +40,13 @@ describe("Get gateway data", (): void => { it("Get blacklisted gateways", async (): Promise => { const response = await contract.getBlacklistedGateways(); - response.forEach(function (value) { - expect(typeof value).toBe("string"); - }); + if (response === null) { + console.log("No blacklisted gateways"); + expect(response).toBeNull(); + } else { + response.forEach(function (value) { + expect(typeof value).toBe("string"); + }); + } }); }); diff --git a/nym-api/tests/functional_test/contract_cache/mixnode/contract-cache-mixnode.test.ts b/nym-api/tests/functional_test/contract_cache/mixnode/contract-cache-mixnode.test.ts index 31fb8d959d..9cb66539a9 100644 --- a/nym-api/tests/functional_test/contract_cache/mixnode/contract-cache-mixnode.test.ts +++ b/nym-api/tests/functional_test/contract_cache/mixnode/contract-cache-mixnode.test.ts @@ -16,8 +16,12 @@ describe("Get mixnode data", (): void => { //bond information overview expect(typeof mixnode.bond_information.mix_id).toBe("number"); expect(typeof mixnode.bond_information.owner).toBe("string"); - expect(typeof mixnode.bond_information.original_pledge.amount).toBe("string"); - expect(typeof mixnode.bond_information.original_pledge.denom).toBe("string"); + expect(typeof mixnode.bond_information.original_pledge.amount).toBe( + "string" + ); + expect(typeof mixnode.bond_information.original_pledge.denom).toBe( + "string" + ); expect(typeof mixnode.bond_information.layer).toBe("number"); expect(typeof mixnode.bond_information.bonding_height).toBe("number"); expect(typeof mixnode.bond_information.is_unbonding).toBe("boolean"); @@ -30,11 +34,17 @@ describe("Get mixnode data", (): void => { //mixnode expect(typeof mixnode.bond_information.mix_node.host).toBe("string"); - expect(mixnode.bond_information.mix_node.http_api_port).toStrictEqual(8000); - expect(typeof mixnode.bond_information.mix_node.verloc_port).toBe("number"); + expect(mixnode.bond_information.mix_node.http_api_port).toStrictEqual( + 8000 + ); + expect(typeof mixnode.bond_information.mix_node.verloc_port).toBe( + "number" + ); expect(typeof mixnode.bond_information.mix_node.mix_port).toBe("number"); expect(mixnode.bond_information.mix_node.mix_port).toStrictEqual(1789); - expect(typeof mixnode.bond_information.mix_node.verloc_port).toBe("number"); + expect(typeof mixnode.bond_information.mix_node.verloc_port).toBe( + "number" + ); const identitykey = mixnode.bond_information.mix_node.identity_key; if (typeof identitykey === "string") { @@ -51,15 +61,27 @@ describe("Get mixnode data", (): void => { } //rewarding details - expect(typeof mixnode.rewarding_details.cost_params.profit_margin_percent).toBe("string"); - expect(typeof mixnode.rewarding_details.cost_params.interval_operating_cost.denom).toBe("string"); - expect(typeof mixnode.rewarding_details.cost_params.interval_operating_cost.amount).toBe("string"); + expect( + typeof mixnode.rewarding_details.cost_params.profit_margin_percent + ).toBe("string"); + expect( + typeof mixnode.rewarding_details.cost_params.interval_operating_cost + .denom + ).toBe("string"); + expect( + typeof mixnode.rewarding_details.cost_params.interval_operating_cost + .amount + ).toBe("string"); expect(typeof mixnode.rewarding_details.operator).toBe("string"); expect(typeof mixnode.rewarding_details.delegates).toBe("string"); expect(typeof mixnode.rewarding_details.total_unit_reward).toBe("string"); expect(typeof mixnode.rewarding_details.unit_delegation).toBe("string"); - expect(typeof mixnode.rewarding_details.last_rewarded_epoch).toBe("number"); - expect(typeof mixnode.rewarding_details.unique_delegations).toBe("number"); + expect(typeof mixnode.rewarding_details.last_rewarded_epoch).toBe( + "number" + ); + expect(typeof mixnode.rewarding_details.unique_delegations).toBe( + "number" + ); }); }); @@ -76,36 +98,66 @@ describe("Get mixnode data", (): void => { // expect(typeof mixnode.family).toBe("string"); //mixnode details bond info - expect(typeof mixnode.mixnode_details.bond_information.mix_id).toBe("number") - expect(typeof mixnode.mixnode_details.bond_information.owner).toBe("string"); - expect(typeof mixnode.mixnode_details.bond_information.original_pledge.amount).toBe("string"); - expect(typeof mixnode.mixnode_details.bond_information.original_pledge.denom).toBe("string"); - expect(typeof mixnode.mixnode_details.bond_information.layer).toBe("number"); - expect(typeof mixnode.mixnode_details.bond_information.bonding_height).toBe("number"); - expect(typeof mixnode.mixnode_details.bond_information.is_unbonding).toBe("boolean"); + expect(typeof mixnode.mixnode_details.bond_information.mix_id).toBe( + "number" + ); + expect(typeof mixnode.mixnode_details.bond_information.owner).toBe( + "string" + ); + expect( + typeof mixnode.mixnode_details.bond_information.original_pledge.amount + ).toBe("string"); + expect( + typeof mixnode.mixnode_details.bond_information.original_pledge.denom + ).toBe("string"); + expect(typeof mixnode.mixnode_details.bond_information.layer).toBe( + "number" + ); + expect( + typeof mixnode.mixnode_details.bond_information.bonding_height + ).toBe("number"); + expect(typeof mixnode.mixnode_details.bond_information.is_unbonding).toBe( + "boolean" + ); if (mixnode.mixnode_details.bond_information.proxy === null) { return true; } else { - expect(typeof mixnode.mixnode_details.bond_information.proxy).toBe("string"); + expect(typeof mixnode.mixnode_details.bond_information.proxy).toBe( + "string" + ); } //mixnode - expect(typeof mixnode.mixnode_details.bond_information.mix_node.host).toBe("string"); - expect(mixnode.mixnode_details.bond_information.mix_node.http_api_port).toStrictEqual(8000); - expect(typeof mixnode.mixnode_details.bond_information.mix_node.verloc_port).toBe("number"); - expect(typeof mixnode.mixnode_details.bond_information.mix_node.mix_port).toBe("number"); - expect(mixnode.mixnode_details.bond_information.mix_node.mix_port).toStrictEqual(1789); - expect(typeof mixnode.mixnode_details.bond_information.mix_node.verloc_port).toBe("number"); + expect( + typeof mixnode.mixnode_details.bond_information.mix_node.host + ).toBe("string"); + expect( + mixnode.mixnode_details.bond_information.mix_node.http_api_port + ).toStrictEqual(8000); + expect( + typeof mixnode.mixnode_details.bond_information.mix_node.verloc_port + ).toBe("number"); + expect( + typeof mixnode.mixnode_details.bond_information.mix_node.mix_port + ).toBe("number"); + expect( + mixnode.mixnode_details.bond_information.mix_node.mix_port + ).toStrictEqual(1789); + expect( + typeof mixnode.mixnode_details.bond_information.mix_node.verloc_port + ).toBe("number"); - const identitykey2 = mixnode.mixnode_details.bond_information.mix_node.identity_key + const identitykey2 = + mixnode.mixnode_details.bond_information.mix_node.identity_key; if (typeof identitykey2 === "string") { if (identitykey2.length === 43) { return true; } else expect(identitykey2).toHaveLength(44); } - const sphinx2 = mixnode.mixnode_details.bond_information.mix_node.sphinx_key + const sphinx2 = + mixnode.mixnode_details.bond_information.mix_node.sphinx_key; if (typeof sphinx2 === "string") { if (sphinx2.length === 43) { return true; @@ -113,22 +165,45 @@ describe("Get mixnode data", (): void => { } //mixnode rewarding info - expect(typeof mixnode.mixnode_details.rewarding_details.cost_params.profit_margin_percent).toBe("string"); - expect(typeof mixnode.mixnode_details.rewarding_details.cost_params.interval_operating_cost.denom).toBe("string"); - expect(typeof mixnode.mixnode_details.rewarding_details.cost_params.interval_operating_cost.amount).toBe("string"); - expect(typeof mixnode.mixnode_details.rewarding_details.operator).toBe("string"); - expect(typeof mixnode.mixnode_details.rewarding_details.delegates).toBe("string"); - expect(typeof mixnode.mixnode_details.rewarding_details.total_unit_reward).toBe("string"); - expect(typeof mixnode.mixnode_details.rewarding_details.unit_delegation).toBe("string"); - expect(typeof mixnode.mixnode_details.rewarding_details.last_rewarded_epoch).toBe("number"); - expect(typeof mixnode.mixnode_details.rewarding_details.unique_delegations).toBe("number"); + expect( + typeof mixnode.mixnode_details.rewarding_details.cost_params + .profit_margin_percent + ).toBe("string"); + expect( + typeof mixnode.mixnode_details.rewarding_details.cost_params + .interval_operating_cost.denom + ).toBe("string"); + expect( + typeof mixnode.mixnode_details.rewarding_details.cost_params + .interval_operating_cost.amount + ).toBe("string"); + expect(typeof mixnode.mixnode_details.rewarding_details.operator).toBe( + "string" + ); + expect(typeof mixnode.mixnode_details.rewarding_details.delegates).toBe( + "string" + ); + expect( + typeof mixnode.mixnode_details.rewarding_details.total_unit_reward + ).toBe("string"); + expect( + typeof mixnode.mixnode_details.rewarding_details.unit_delegation + ).toBe("string"); + expect( + typeof mixnode.mixnode_details.rewarding_details.last_rewarded_epoch + ).toBe("number"); + expect( + typeof mixnode.mixnode_details.rewarding_details.unique_delegations + ).toBe("number"); }); }); it("Get active mixnodes", async (): Promise => { const response = await contract.getActiveMixnodes(); response.forEach(function (mixnode) { - expect(mixnode.rewarding_details.cost_params.profit_margin_percent).toBeTruthy(); + expect( + mixnode.rewarding_details.cost_params.profit_margin_percent + ).toBeTruthy(); expect(typeof mixnode.bond_information.layer).toBe("number"); }); }); @@ -136,7 +211,10 @@ describe("Get mixnode data", (): void => { it("Get active mixnodes detailed", async (): Promise => { const response = await contract.getActiveMixnodesDetailed(); response.forEach(function (mixnode) { - expect(mixnode.mixnode_details.rewarding_details.cost_params.profit_margin_percent).toBeTruthy(); + expect( + mixnode.mixnode_details.rewarding_details.cost_params + .profit_margin_percent + ).toBeTruthy(); }); }); @@ -150,14 +228,21 @@ describe("Get mixnode data", (): void => { it("Get rewarded mixnodes detailed", async (): Promise => { const response = await contract.getRewardedMixnodesDetailed(); response.forEach(function (mixnode) { - expect(mixnode.mixnode_details.rewarding_details.last_rewarded_epoch).toBeTruthy(); + expect( + mixnode.mixnode_details.rewarding_details.last_rewarded_epoch + ).toBeTruthy(); }); }); it("Get blacklisted mixnodes", async (): Promise => { const response = await contract.getBlacklistedMixnodes(); - response.forEach(function (value) { - expect(typeof value).toBe("number"); - }); + if (response === null) { + console.log("No blacklisted mixnodes"); + expect(response).toBeNull();; + } else { + response.forEach(function (value) { + expect(typeof value).toBe("string"); + }); + } }); }); From c725ae4e2bcd06007839650da2f2e698428c3b42 Mon Sep 17 00:00:00 2001 From: Tommy Verrall Date: Wed, 2 Aug 2023 12:55:46 +0200 Subject: [PATCH 049/361] remove console --- .../contract_cache/gateway/contract-cache-gateway.test.ts | 2 +- .../contract_cache/mixnode/contract-cache-mixnode.test.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/nym-api/tests/functional_test/contract_cache/gateway/contract-cache-gateway.test.ts b/nym-api/tests/functional_test/contract_cache/gateway/contract-cache-gateway.test.ts index 992e35c89a..d83cdc20cd 100644 --- a/nym-api/tests/functional_test/contract_cache/gateway/contract-cache-gateway.test.ts +++ b/nym-api/tests/functional_test/contract_cache/gateway/contract-cache-gateway.test.ts @@ -41,7 +41,7 @@ describe("Get gateway data", (): void => { it("Get blacklisted gateways", async (): Promise => { const response = await contract.getBlacklistedGateways(); if (response === null) { - console.log("No blacklisted gateways"); + // no blacklisted gateways returns an empty array expect(response).toBeNull(); } else { response.forEach(function (value) { diff --git a/nym-api/tests/functional_test/contract_cache/mixnode/contract-cache-mixnode.test.ts b/nym-api/tests/functional_test/contract_cache/mixnode/contract-cache-mixnode.test.ts index 9cb66539a9..33d6f0e97f 100644 --- a/nym-api/tests/functional_test/contract_cache/mixnode/contract-cache-mixnode.test.ts +++ b/nym-api/tests/functional_test/contract_cache/mixnode/contract-cache-mixnode.test.ts @@ -237,7 +237,7 @@ describe("Get mixnode data", (): void => { it("Get blacklisted mixnodes", async (): Promise => { const response = await contract.getBlacklistedMixnodes(); if (response === null) { - console.log("No blacklisted mixnodes"); + // no blacklisted mixnodes returns an empty array expect(response).toBeNull();; } else { response.forEach(function (value) { From 2ed3e646bef77945fb3998667c79fb31d84bde90 Mon Sep 17 00:00:00 2001 From: Tommy Verrall Date: Wed, 2 Aug 2023 13:02:29 +0200 Subject: [PATCH 050/361] linting and removing unused imports --- .../circulating-supply.test.ts | 3 -- .../contract-cache-epochs.test.ts | 3 -- .../gateway/contract-cache-gateway.test.ts | 5 +-- .../mixnode/contract-cache-mixnode.test.ts | 2 +- .../contract_cache/services.test.ts | 42 +++++++++---------- .../status/status-gateway.test.ts | 8 +++- .../status/status-mixnode.test.ts | 24 ++++++++--- nym-api/tests/src/types/ContractCacheTypes.ts | 3 +- 8 files changed, 46 insertions(+), 44 deletions(-) diff --git a/nym-api/tests/functional_test/circulating_supply/circulating-supply.test.ts b/nym-api/tests/functional_test/circulating_supply/circulating-supply.test.ts index c74d039175..b09d56df75 100644 --- a/nym-api/tests/functional_test/circulating_supply/circulating-supply.test.ts +++ b/nym-api/tests/functional_test/circulating_supply/circulating-supply.test.ts @@ -1,12 +1,9 @@ -import ConfigHandler from "../../src/config/configHandler"; import ContractCache from "../../src/endpoints/CirculatingSupply"; let contract: ContractCache; -let config: ConfigHandler; describe("Get circulating supply", (): void => { beforeAll(async (): Promise => { contract = new ContractCache(); - config = ConfigHandler.getInstance(); }); it("Get circulating supply amounts", async (): Promise => { diff --git a/nym-api/tests/functional_test/contract_cache/contract-cache-epochs.test.ts b/nym-api/tests/functional_test/contract_cache/contract-cache-epochs.test.ts index 482817f669..a2923e9c52 100644 --- a/nym-api/tests/functional_test/contract_cache/contract-cache-epochs.test.ts +++ b/nym-api/tests/functional_test/contract_cache/contract-cache-epochs.test.ts @@ -1,13 +1,10 @@ import ContractCache from "../../src/endpoints/ContractCache"; -import ConfigHandler from "../../src/config/configHandler"; let contract: ContractCache; -let config: ConfigHandler; describe("Get epoch info", (): void => { beforeAll(async (): Promise => { contract = new ContractCache(); - config = ConfigHandler.getInstance(); }); it("Get epoch reward params", async (): Promise => { diff --git a/nym-api/tests/functional_test/contract_cache/gateway/contract-cache-gateway.test.ts b/nym-api/tests/functional_test/contract_cache/gateway/contract-cache-gateway.test.ts index d83cdc20cd..e1dc896ad9 100644 --- a/nym-api/tests/functional_test/contract_cache/gateway/contract-cache-gateway.test.ts +++ b/nym-api/tests/functional_test/contract_cache/gateway/contract-cache-gateway.test.ts @@ -1,13 +1,10 @@ import ContractCache from "../../../src/endpoints/ContractCache"; -import ConfigHandler from "../../../src/config/configHandler"; let contract: ContractCache; -let config: ConfigHandler; describe("Get gateway data", (): void => { beforeAll(async (): Promise => { contract = new ContractCache(); - config = ConfigHandler.getInstance(); }); it("Get all gateways", async (): Promise => { @@ -42,7 +39,7 @@ describe("Get gateway data", (): void => { const response = await contract.getBlacklistedGateways(); if (response === null) { // no blacklisted gateways returns an empty array - expect(response).toBeNull(); + expect(response).toBeNull(); } else { response.forEach(function (value) { expect(typeof value).toBe("string"); diff --git a/nym-api/tests/functional_test/contract_cache/mixnode/contract-cache-mixnode.test.ts b/nym-api/tests/functional_test/contract_cache/mixnode/contract-cache-mixnode.test.ts index 33d6f0e97f..af1a40b297 100644 --- a/nym-api/tests/functional_test/contract_cache/mixnode/contract-cache-mixnode.test.ts +++ b/nym-api/tests/functional_test/contract_cache/mixnode/contract-cache-mixnode.test.ts @@ -238,7 +238,7 @@ describe("Get mixnode data", (): void => { const response = await contract.getBlacklistedMixnodes(); if (response === null) { // no blacklisted mixnodes returns an empty array - expect(response).toBeNull();; + expect(response).toBeNull(); } else { response.forEach(function (value) { expect(typeof value).toBe("string"); diff --git a/nym-api/tests/functional_test/contract_cache/services.test.ts b/nym-api/tests/functional_test/contract_cache/services.test.ts index d38adf9d9d..711aaa0246 100644 --- a/nym-api/tests/functional_test/contract_cache/services.test.ts +++ b/nym-api/tests/functional_test/contract_cache/services.test.ts @@ -1,28 +1,24 @@ import ContractCache from "../../src/endpoints/ContractCache"; -import ConfigHandler from "../../src/config/configHandler"; - let contract: ContractCache; -let config: ConfigHandler; describe("Get service provider info", (): void => { - beforeAll(async (): Promise => { - contract = new ContractCache(); - config = ConfigHandler.getInstance(); - }); + beforeAll(async (): Promise => { + contract = new ContractCache(); + }); - it("Get service providers", async (): Promise => { - const response = await contract.getServiceProviders(); - if ("[service_id]" in response) { - response.services.forEach((x) => { - expect(typeof x.service.nym_address.address).toBe("string"); - expect(typeof x.service.service_type).toBe("string"); - expect(typeof x.service.block_height).toBe("number"); - expect(typeof x.service.announcer).toBe("string"); - expect(typeof x.service.deposit.amount).toBe("string"); - expect(typeof x.service.deposit.denom).toBe("string"); - }); - } else if ("[ ]" in response) { - return; - } - }); -}); \ No newline at end of file + it("Get service providers", async (): Promise => { + const response = await contract.getServiceProviders(); + if ("[service_id]" in response) { + response.services.forEach((x) => { + expect(typeof x.service.nym_address.address).toBe("string"); + expect(typeof x.service.service_type).toBe("string"); + expect(typeof x.service.block_height).toBe("number"); + expect(typeof x.service.announcer).toBe("string"); + expect(typeof x.service.deposit.amount).toBe("string"); + expect(typeof x.service.deposit.denom).toBe("string"); + }); + } else if ("[ ]" in response) { + return; + } + }); +}); diff --git a/nym-api/tests/functional_test/status/status-gateway.test.ts b/nym-api/tests/functional_test/status/status-gateway.test.ts index 92435e12d3..71f2becc6d 100644 --- a/nym-api/tests/functional_test/status/status-gateway.test.ts +++ b/nym-api/tests/functional_test/status/status-gateway.test.ts @@ -40,7 +40,9 @@ describe("Get gateway data", (): void => { expect(identity_key).toStrictEqual(response.identity); expect(typeof response.owner).toBe("string"); } else if ("message" in response) { - expect(response.message).toContain("could not find uptime history associated with gateway"); + expect(response.message).toContain( + "could not find uptime history associated with gateway" + ); } }); @@ -58,7 +60,9 @@ describe("Get gateway data", (): void => { expect(identity_key).toStrictEqual(response.identity); expect(typeof response.count).toBe("number"); } else if ("message" in response) { - expect(response.message).toContain("could not find uptime history associated with mixnode"); + expect(response.message).toContain( + "could not find uptime history associated with mixnode" + ); } }); diff --git a/nym-api/tests/functional_test/status/status-mixnode.test.ts b/nym-api/tests/functional_test/status/status-mixnode.test.ts index aaa8054057..6590843f02 100644 --- a/nym-api/tests/functional_test/status/status-mixnode.test.ts +++ b/nym-api/tests/functional_test/status/status-mixnode.test.ts @@ -55,7 +55,9 @@ describe("Get mixnode data", (): void => { expect(identity_key).toStrictEqual(response.mix_id); expect(typeof response.owner).toBe("string"); } else if ("message" in response) { - expect(response.message).toContain("could not find uptime history associated with mixnode"); + expect(response.message).toContain( + "could not find uptime history associated with mixnode" + ); } }); @@ -70,7 +72,9 @@ describe("Get mixnode data", (): void => { const identity_key = config.environmnetConfig.mix_id; const response = await status.getMixnodeRewardComputation(identity_key); if ("estimation" in response) { - expect(response.reward_params.interval.sybil_resistance).toStrictEqual("0.3"); + expect(response.reward_params.interval.sybil_resistance).toStrictEqual( + "0.3" + ); expect(response.reward_params.active_set_size).toStrictEqual(240); expect(typeof response.reward_params.interval.reward_pool).toBe("string"); } else if ("message" in response) { @@ -108,7 +112,9 @@ describe("Get mixnode data", (): void => { const response = await status.getUnfilteredMixnodes(); response.forEach((x) => { expect(typeof x.stake_saturation).toBe("string"); - expect(typeof x.mixnode_details.rewarding_details.last_rewarded_epoch).toBe("number"); + expect( + typeof x.mixnode_details.rewarding_details.last_rewarded_epoch + ).toBe("number"); }); }); @@ -116,7 +122,9 @@ describe("Get mixnode data", (): void => { const identity_key = config.environmnetConfig.mix_id; const response = await status.getMixnodeStatus(identity_key); const unfiltered_mixnodes_response = await status.getUnfilteredMixnodes(); - const mixnode = unfiltered_mixnodes_response.find(x => x.mixnode_details.bond_information.mix_id === identity_key); + const mixnode = unfiltered_mixnodes_response.find( + (x) => x.mixnode_details.bond_information.mix_id === identity_key + ); if (mixnode) { expect(response.status).toStrictEqual("active"); } else { @@ -127,7 +135,9 @@ describe("Get mixnode data", (): void => { it("Get all rewarded mixnodes", async (): Promise => { const response = await status.getDetailedRewardedMixnodes(); response.forEach((x) => { - expect(typeof x.mixnode_details.rewarding_details.last_rewarded_epoch).toBe("number"); + expect( + typeof x.mixnode_details.rewarding_details.last_rewarded_epoch + ).toBe("number"); }); }); @@ -146,7 +156,9 @@ describe("Get mixnode data", (): void => { it("with correct data", async (): Promise => { const mix_id = config.environmnetConfig.mix_id; - const response = await status.sendMixnodeRewardEstimatedComputation(mix_id); + const response = await status.sendMixnodeRewardEstimatedComputation( + mix_id + ); expect(typeof response.estimation.delegates).toBe("string"); }); }); diff --git a/nym-api/tests/src/types/ContractCacheTypes.ts b/nym-api/tests/src/types/ContractCacheTypes.ts index f3cf31e401..e66c3018a8 100644 --- a/nym-api/tests/src/types/ContractCacheTypes.ts +++ b/nym-api/tests/src/types/ContractCacheTypes.ts @@ -125,7 +125,7 @@ export interface EpochLength { } export interface ServiceProviders { - services: (Services)[]; + services: Services[]; } export interface Services { service_id: number; @@ -145,4 +145,3 @@ export interface Deposit { denom: string; amount: string; } - From cbafd37102bc7793464f27f41c47f1f8eef6671c Mon Sep 17 00:00:00 2001 From: Tommy Verrall Date: Wed, 2 Aug 2023 13:22:02 +0200 Subject: [PATCH 051/361] fix config --- .../contract_cache/mixnode/contract-cache-mixnode.test.ts | 2 +- nym-api/tests/src/config/config.yaml | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/nym-api/tests/functional_test/contract_cache/mixnode/contract-cache-mixnode.test.ts b/nym-api/tests/functional_test/contract_cache/mixnode/contract-cache-mixnode.test.ts index af1a40b297..6182f8a63c 100644 --- a/nym-api/tests/functional_test/contract_cache/mixnode/contract-cache-mixnode.test.ts +++ b/nym-api/tests/functional_test/contract_cache/mixnode/contract-cache-mixnode.test.ts @@ -241,7 +241,7 @@ describe("Get mixnode data", (): void => { expect(response).toBeNull(); } else { response.forEach(function (value) { - expect(typeof value).toBe("string"); + expect(typeof value).toBe("number"); }); } }); diff --git a/nym-api/tests/src/config/config.yaml b/nym-api/tests/src/config/config.yaml index 83a9d3fa92..d0f51eff0c 100644 --- a/nym-api/tests/src/config/config.yaml +++ b/nym-api/tests/src/config/config.yaml @@ -10,7 +10,8 @@ qa: log_level: error prod: api_base_url: https://validator.nymtech.net/api/v1 - mixnode_identity: DLdMKLPywEy1vnu3yPrtXvzY7fw1puiiHpA9n9UQatiQ - gateway_identity: CgQrYP8etksSBf4nALNqp93SHPpgFwEUyTsjBNNLj5WM + mix_id: 730 + mixnode_identity: 3pMCJswCyA19MGYWGDWT5fBk2M8ybSZGXttyAoNY5gBB + gateway_identity: 2BuMSfMW3zpeAjKXyKLhmY4QW1DXurrtSPEJ6CjX3SEh log_level: error time_zone: utc From 4696470b19a711abc0a1e5a116067a357d549edf Mon Sep 17 00:00:00 2001 From: mfahampshire Date: Wed, 2 Aug 2023 16:26:41 +0200 Subject: [PATCH 052/361] rephrased intro --- documentation/dev-portal/src/SUMMARY.md | 2 ++ documentation/dev-portal/src/introduction.md | 4 ++-- documentation/dev-portal/src/quickstart/chat-demo.md | 5 +++++ .../dev-portal/src/quickstart/cred-playground.md | 5 +++++ documentation/dev-portal/src/quickstart/overview.md | 12 +++--------- 5 files changed, 17 insertions(+), 11 deletions(-) create mode 100644 documentation/dev-portal/src/quickstart/chat-demo.md create mode 100644 documentation/dev-portal/src/quickstart/cred-playground.md diff --git a/documentation/dev-portal/src/SUMMARY.md b/documentation/dev-portal/src/SUMMARY.md index aeae804662..0e0c02bc58 100644 --- a/documentation/dev-portal/src/SUMMARY.md +++ b/documentation/dev-portal/src/SUMMARY.md @@ -11,6 +11,8 @@ # Quickstart - [Overview](quickstart/overview.md) +- [Chat demo (webapp)](quickstart/chat-demo.md) +- [Coconut Credential Playground (webapp)](quickstart/cred-playground.md) - [SOCKS Proxy (CLI)](quickstart/socks-proxy.md) - [NymConnect Beta (GUI)](quickstart/nymconnect-gui.md) diff --git a/documentation/dev-portal/src/introduction.md b/documentation/dev-portal/src/introduction.md index d679a77c53..7335fab32b 100644 --- a/documentation/dev-portal/src/introduction.md +++ b/documentation/dev-portal/src/introduction.md @@ -1,5 +1,5 @@ # Introduction -Welcome to the Nym Developer Portal, containing the resources and tools needed to build privacy enhanced apps (PEApps) with Nym's technology. Whether you're a seasoned developer or just getting started, our portal is designed to help you create decentralized applications and services utilising the Nym mixnet. +Welcome to the Nym Developer Portal, containing quickstart resources, user manuals, integration information, and tutorials outlining to start building privacy enhanced apps. -Join our community of developers and start building the decentralized future today! +For more in-depth information about nodes, network traffic flows, etc check out the [docs](https://nymtech.net/docs). diff --git a/documentation/dev-portal/src/quickstart/chat-demo.md b/documentation/dev-portal/src/quickstart/chat-demo.md new file mode 100644 index 0000000000..6e2f5c244a --- /dev/null +++ b/documentation/dev-portal/src/quickstart/chat-demo.md @@ -0,0 +1,5 @@ +# Chat demo (webapp) + +You can find a browser-based 'hello world' chat app [here](https://chat-demo.nymtech.net). + +Either open in two browser windows and send messages to yourself, or share with a friend and send messages to each other through the mixnet. diff --git a/documentation/dev-portal/src/quickstart/cred-playground.md b/documentation/dev-portal/src/quickstart/cred-playground.md new file mode 100644 index 0000000000..aa2220940c --- /dev/null +++ b/documentation/dev-portal/src/quickstart/cred-playground.md @@ -0,0 +1,5 @@ +# Coconut Credential Playground (webapp) + +There is a coconut-scheme based Credential Library playground [here](https://coco-demo.nymtech.net/). This is a WASM implementation of our Coconut libraries which generate raw Coconut credentials. Test it to create and re-randomize your own credentials. + +For more information on what is happening here check out the [Coconut docs](https://nymtech.net/docs/coconut.html). diff --git a/documentation/dev-portal/src/quickstart/overview.md b/documentation/dev-portal/src/quickstart/overview.md index b2ac4199c9..534ed60fe5 100644 --- a/documentation/dev-portal/src/quickstart/overview.md +++ b/documentation/dev-portal/src/quickstart/overview.md @@ -1,13 +1,7 @@ # Overview -There are multiple options to quickly connect to Nym and see the network in action without the need for any code changes to your application. At most, these involve running Nym as a second process alongside an existing application in order to send traffic through the mixnet. +There are multiple options to quickly connect to Nym and play with both the mixnet and credentials on the Sandbox testnet. -Demo applications: -* a browser-based 'hello world' [chat application](https://chat-demo.nymtech.net). Either open in two browser windows and send messages to yourself, or share with a friend and send messages to each other through the mixnet! -* a Coconut-scheme based [Credential Library](https://coco-demo.nymtech.net/). This is a WASM implementation of our Coconut libraries which generate raw Coconut credentials. Test it to create and re-randomize your own credentials! -
-Proxy traffic with the Nym Socks5 client: -* set up a plug-and-play connection with the [NymConnect](./nymconnect-gui.md) GUI for proxying Telegram, Electrum, Keybase or Blockstream Green traffic through the mixnet (~2 minutes). -* [Download and run](./socks-proxy.md) the Nym Socks5 client via the CLI, for other desktop applications with SOCKS5 connection options (~30 minutes). +At most, these involve running Nym as a second process alongside an existing application in order to send traffic through the mixnet, most are either interact webpages or a standalone app. -If you've already covered the information in this section, or want to jump straight into integrating/ a Nym connection into an existing application, head to the [Integrations](../integrations/integration-options.md) section. +If you've already covered the information in this section, or want to jump straight into integrating/ a Nym connection into an existing application, head to the [Integrations](../integrations/integration-options.md) section. From 464984a83c6ae85a152fa0974b7dfadd3be8dc85 Mon Sep 17 00:00:00 2001 From: mfahampshire Date: Wed, 2 Aug 2023 23:29:33 +0200 Subject: [PATCH 053/361] continued working on tutorial; finished bin/service --- .../tutorials/cosmos-service/client-src.md | 53 +++++++ .../src/tutorials/cosmos-service/client.md | 70 ++++++++- .../src/tutorials/cosmos-service/intro.md | 2 +- .../src/tutorials/cosmos-service/lib.md | 133 +++++++++--------- .../src/tutorials/cosmos-service/overview.md | 4 +- .../tutorials/cosmos-service/preparing-env.md | 13 +- .../tutorials/cosmos-service/service-src.md | 1 + .../src/tutorials/cosmos-service/service.md | 70 +++++++++ 8 files changed, 260 insertions(+), 86 deletions(-) create mode 100644 documentation/dev-portal/src/tutorials/cosmos-service/client-src.md create mode 100644 documentation/dev-portal/src/tutorials/cosmos-service/service-src.md diff --git a/documentation/dev-portal/src/tutorials/cosmos-service/client-src.md b/documentation/dev-portal/src/tutorials/cosmos-service/client-src.md new file mode 100644 index 0000000000..7b8085fc54 --- /dev/null +++ b/documentation/dev-portal/src/tutorials/cosmos-service/client-src.md @@ -0,0 +1,53 @@ +# Preparing Your Client pt2 + +Open `src/client.rs`. This is where the logic of the command from the `match` statement in `bin/client.rs` is defined. + +# Dependencies +```rust +use crate::{handle_response, wait_for_non_empty_message, RequestTypes, DEFAULT_VALIDATOR_RPC}; +use cosmrs::AccountId; +use nym_sdk::mixnet::MixnetClient; +use nym_sphinx_addressing::clients::Recipient; +use nym_validator_client::nyxd::Coin +``` + +# Querying via the Mixnet +The following is used to construct a `BalanceRequest`, send this to the supplied `service` address, and then handle the response, matching it to a `ResponseType`, in this case the only expected response, a `BalanceResponse`. + +The actual sending of the request is performed by `client.send_bytes`: sending the serialised `BalanceRequest` to the supplied Nym address (the `Recipient` imported from the `nym_sphinx_addressing` crate). It is sending the default number of SURBs along with the message, defined [here](LINK_TO_SURB_DEFAULT_NUMBEr). + +```rust +pub async fn query_balance( + account: AccountId, + client: &mut MixnetClient, + sp_address: Recipient, +) -> anyhow::Result { + // construct balance request + let message = RequestTypes::Balance(crate::BalanceRequest { + validator: DEFAULT_VALIDATOR_RPC.to_owned(), // rpc endpoint for broadcaster to use + account, + }); + + // send serialised request to service via mixnet + client + .send_bytes(sp_address, message.serialize(), Default::default()) + .await; + + let received = wait_for_non_empty_message(client).await?; + + // listen for response from service + let sp_response = handle_response(received)?; + + // match JSON -> ResponseType + let res = match sp_response { + crate::ResponseTypes::Balance(response) => { + println!("{:#?}", response); + response.balance + } + }; + + Ok(res) +} +``` + +That is all of the client code written: now to move on to the `service` that will be interacting with the blockchain on behalf of the `client`. diff --git a/documentation/dev-portal/src/tutorials/cosmos-service/client.md b/documentation/dev-portal/src/tutorials/cosmos-service/client.md index c3f6c6ee10..df3751c4dd 100644 --- a/documentation/dev-portal/src/tutorials/cosmos-service/client.md +++ b/documentation/dev-portal/src/tutorials/cosmos-service/client.md @@ -1,23 +1,79 @@ # Preparing Your Client -Start by creating the startup logic of your `client` - creating a Nym client and connecting to the mixnet (or just connecting if your client has been started before and config already exists for it), and defining commands. - -Start in `bin/client.rs`. +Start by creating the startup logic of your `client` in `bin/client.rs` - creating a Nym client and connecting to the mixnet (or just connecting if your client has been started before and config already exists for it), and defining and running commands. ## Dependencies Import the following dependencies: ``` use clap::{Args, Parser, Subcommand}; +use chain_query::{client::query_balance, create_client}; use nym_sdk::mixnet::Recipient; use nym_validator_client::nyxd::AccountId; -use nym_cosmos_service::create_client; use nym_bin_common::logging::setup_logging; ``` -`clap` is used so different commands can be passed to the `client` (even though we're only defining one function in this first part of the tutorial, more will be added in subsequent chapters). `nym_sdk::mixnet::Recipient` is the type used to define the recipient of a mixnet message, `nym_bin_common::logging::setup_logging` is the logging setup for `client`'s Nym client, and `nym_cosmos_service::create_client` imports the `create_client` function created on the previous page. +`clap` is used so different commands can be passed to the `client` (even though we're only defining one function in this first part of the tutorial, more will be added in subsequent chapters). `nym_sdk::mixnet::Recipient` is the type used to define the recipient of a mixnet message, `nym_bin_common::logging::setup_logging` is the logging setup for `client`'s Nym client, and `chain_query` imports the `create_client` and `query_balance` functions created on the previous page. ## CLI Command with Clap +The following simply defines the commands that the client can perform. For the moment, there is only one: the `query_balance` function created in the previous section. -< simple account query first - then maybe a contract query > +As with the data structures, this structure is being used for ease of adding future commands in subsequent tutorials. -## +```rust +#[derive(Debug, Parser)] +#[clap(name = "rust sdk demo - chain query service")] +#[clap(about = "query the sandbox testnet blockchain via the mixnet... part 2 coming soon")] +struct Cli { + #[clap(subcommand)] + command: Option, +} + +#[derive(Debug, Subcommand)] +enum Commands { + QueryBalance(QueryBalance), +} + +#[derive(Debug, Args)] +struct QueryBalance { + /// the account we want to query + account: AccountId, + /// the address of the broadcaster service - this submits txs and queries the chain on our behalf + sp_address: String, +} +``` + +## `main()` +This is the root logic of the `client`. Using `[tokio](URL_TO_DO)` for the async runtime, this function performs the following functions: +* If not already existing, creates a Nym client with config at `/tmp/client`. Otherwise it loads the already existing client from this config. +* Matches the command from the CLI - in this instance, the `QueryBalance` function which will be defined in the next section. This will create a `BalanceRequest` and send this to the `service`, before returning the response sent back to it to the main thread and printing this to the console. +* Performs a proper shutdown of the running Nym client. + +```rust +#[tokio::main] +async fn main() -> anyhow::Result<()> { + setup_logging(); + let cli = Cli::parse(); + let mut client = create_client("/tmp/client2".into()).await; + let our_address = client.nym_address(); + println!("\nclient's nym address: {our_address}"); + + match cli.command { + Some(Commands::QueryBalance(QueryBalance { + account, + sp_address, + })) => { + println!("\nsending bank balance request to service via mixnet"); + let sp_address = Recipient::try_from_base58_string(sp_address).unwrap(); + let returned_balance = query_balance(account, &mut client, sp_address).await?; + println!("\nreturned balance is: {}", returned_balance); + } + None => { + println!("\nno command specified - nothing to do") + } + } + println!("\ndisconnecting client"); + client.disconnect().await; + println!("client disconnected"); + Ok(()) +} +``` diff --git a/documentation/dev-portal/src/tutorials/cosmos-service/intro.md b/documentation/dev-portal/src/tutorials/cosmos-service/intro.md index 1510d31e2e..342bca9132 100644 --- a/documentation/dev-portal/src/tutorials/cosmos-service/intro.md +++ b/documentation/dev-portal/src/tutorials/cosmos-service/intro.md @@ -4,4 +4,4 @@ This tutorial is for Rust developers wanting to interact with the Rust SDK and t The key here is to think of the service as a proxy: it interacts with the blockchain _on the client's behalf_, shielding it from the Validator it interacts with, whilst also being shielded from the client by the mixnet. -> This service also nicely highlights the limitations of the mixnet - even though with this code your metadata is shielded from the Validator, and even the service does not know your Nym address, application-level information such as a blockchain address is not made private, in virtue of the fact that using the mixnet provides solely network-level privacy. For information on what application-level privacy Nym offers, check out the [coconut credential SDK example](). +> This service also nicely highlights the limitations of the mixnet - even though with this code your metadata is shielded from the Validator, and even the service does not know your Nym address, application-level information such as a blockchain address is not made private, in virtue of the fact that using the mixnet provides solely network-level privacy. For information on what application-level privacy Nym offers, check out the [coconut credential SDK example](RELATIVE_PATH). diff --git a/documentation/dev-portal/src/tutorials/cosmos-service/lib.md b/documentation/dev-portal/src/tutorials/cosmos-service/lib.md index 37a2f46a22..13caff08aa 100644 --- a/documentation/dev-portal/src/tutorials/cosmos-service/lib.md +++ b/documentation/dev-portal/src/tutorials/cosmos-service/lib.md @@ -7,17 +7,20 @@ These include the request and response types the client and the service will be ## Dependencies The dependecies for the shared `lib` file are the following: ```rust -use cosmrs::{tendermint, AccountId}; +use anyhow::bail; +use cosmrs::AccountId; use nym_sdk::mixnet::{ AnonymousSenderTag, MixnetClient, MixnetClientBuilder, ReconstructedMessage, StoragePaths, }; +use nym_validator_client::nyxd::Coin; use serde::{Deserialize, Serialize}; use std::path::PathBuf; + pub mod client; pub mod service; ``` -Since this is the file where client creation and message parsing are handled, the various `nym_sdk` imports, as well as `serde`'s (de)serialisation functionality, are required. `PathBuf` is for reading filepaths, and `cosmrs` types are required for defining Nyx blockchain accounts. +Since this is the file where client creation and message parsing are handled, the various `nym_sdk` imports, as well as `serde`'s (de)serialisation functionality, are required. `PathBuf` is for reading filepaths, `cosmrs` types are required for defining Nyx blockchain accounts, and the `Coin` type from the `nyxd_validator_client` is for our Coin balance request and response. `anyhow` is for easy error handing. ## Constants Below this are the chain-related `const` variables. These have been hardcoded for this demo. @@ -34,37 +37,53 @@ These define the RPC endpoint your service will use to interact with the blockch Define the following structs for our different request and responses that will be serialised and sent through the mixnet between your client and service binaries: ```rust -#[derive(Deserialize, Serialize, Debug)] -pub struct SequenceRequest { +#[derive(Debug, Deserialize, Serialize, PartialEq)] +pub struct BalanceRequest { pub validator: String, - pub signer_address: AccountId, + pub account: AccountId, } -#[derive(Deserialize, Serialize, Debug)] -pub struct SequenceRequestResponse { - pub account_number: u64, - pub sequence: u64, - pub chain_id: [tendermint](tendermint)::chain::Id, +#[derive(Debug, Deserialize, Serialize, PartialEq)] +pub struct BalanceResponse { + pub balance: Coin, } -#[derive(Debug, Deserialize, Serialize)] -#[serde(untagged)] +#[derive(Debug, Deserialize, Serialize, PartialEq)] pub enum RequestTypes { - Sequence(SequenceRequest), + Balance(BalanceRequest), } -#[derive(Debug, Deserialize, Serialize)] -#[serde(untagged)] +impl RequestTypes { + pub fn serialize(&self) -> Vec { + serde_json::to_vec(self).expect("serde failure") + } + + pub fn try_deserialize>(raw: M) -> anyhow::Result { + serde_json::from_slice(raw.as_ref()).map_err(Into::into) + } +} + +#[derive(Debug, Deserialize, Serialize, PartialEq)] pub enum ResponseTypes { - Sequence(SequenceRequestResponse), + Balance(BalanceResponse), +} + +impl ResponseTypes { + pub fn serialize(&self) -> Vec { + serde_json::to_vec(self).expect("serde failure") + } + + pub fn try_deserialize>(raw: M) -> anyhow::Result { + serde_json::from_slice(raw.as_ref()).map_err(Into::into) + } } ``` The above data types are pretty straightforward. Even though there are only one instance of a request type (sent from client to service) and one of a response type (service -> client) so far, a pair of enums has been defined to contain additional response or request types that will be added in the future. -`SequenceRequest` will be used when requesting the service to query the chain on the client's behalf for an address' sequence information (used for offline signing). You can see the information that will be returned from the chain to the service, and from the service to the client, in `SequenceRequestResponse`. +`BalanceRequest` will be used when requesting the service to query the token balance of the supplied address on the client's behalf. You can see the information that will be returned from the chain to the service, and from the service to the client, in `BalanceResponse`. -> Although `SequenceResponse` would have been more succinct, there is already a `cosmrs` type with this name. As such the response type was given a different name to avoid confusion. +Custom serialistion and deserialisation have been implemented for each enum for ease of future modification if required, and testing. ## Shared Functions Now to define functions shared by the `client` and `service` binaries. @@ -90,64 +109,38 @@ pub async fn create_client(config_path: PathBuf) -> MixnetClient { > If keys and config already exist at this location, re-running this function **will not** overwrite them. -### Parsing Incoming messages -Next to define two functions: one for listening _for_ messages from the mixnet (used by our `service`), and one for listening out for a _reply_ after sending a message to another Nym client (in this case, when sending a message from the `client` to the `service`). +### Listening for & Parsing Incoming messages +Next to define two functions: one for listening _for_ messages from the mixnet (used by `service`), and one for handling a _response_ to a request (used by `client`). + +Both functions attempt to deserialise the vec of `ReconstructedMessages` that are reconstructed by the client from delivered Sphinx packets after decryption. + +`handle_request` performs one additional function - parsing the `sender_tag` from the incoming reconstructed message. This is the randomised alphanumeric string used to identify a bucket of _SURBs_ (_S_ingle _U_se _R_eply _B_locks) that are sent along with any outgoing message by default. More information about them can be found [here](LINK_TO_SURBS_DOCS) but all that is necessary to know for now is that these are pre-addressed packets that clients send out with their messages. Any reply to their message that is to be sent back to them back be written to the payload of these packets, but without the entity seeing the destination address. This allows for services to _anonymously reply_ to clients without being able to doxx them. ```rust -pub async fn listen_and_parse_response(client: &mut MixnetClient) -> anyhow::Result { - let mut message: Vec = Vec::new(); - - // get the actual message - discard the empty vec sent along with the SURB topup request - while let Some(new_message) = client.wait_for_messages().await { - if new_message.is_empty() { - println!("got a request for more SURBs from service - sending additional SURBs to sender"); - continue; - } - message = new_message; - break; - } - - // parse vec -> JSON String - let mut parsed = String::new(); - if let Some(r) = message.iter().next() { - parsed = String::from_utf8(r.message.clone())?; - } - let sp_response: crate::ResponseTypes = serde_json::from_str(&parsed)?; - Ok(sp_response) +pub fn handle_response(message: ReconstructedMessage) -> anyhow::Result { + ResponseTypes::try_deserialize(message.message) } -pub async fn listen_and_parse_request( - client: &mut MixnetClient, -) -> anyhow::Result<(RequestTypes, AnonymousSenderTag)> { - let mut message: Vec = Vec::new(); - - while let Some(new_message) = client.wait_for_messages().await { - if new_message.is_empty() { - println!("got empty vec - probably the SURBs sent along with the request"); - continue; - } - message = new_message; - break; - } - - // parse vec -> JSON String - let mut parsed = String::new(); - if let Some(r) = message.iter().next() { - parsed = String::from_utf8(r.message.clone())?; - } - let client_request: crate::RequestTypes = serde_json::from_str(&parsed)?; - - // get the sender_tag for anon reply - let return_recipient = message[0].sender_tag.unwrap(); - - Ok((client_request, return_recipient)) +pub fn handle_request( + message: ReconstructedMessage, +) -> anyhow::Result<(RequestTypes, Option)> { + let request = RequestTypes::try_deserialize(message.message)?; + Ok((request, message.sender_tag)) } ``` -Aside from the return types that each function has, the main difference is that any incoming requests to the `service` will also have SURB packets attached to it. +Before moving on to the `client` and `service` code, one more function is needed. This allows for both binaries to parse empty incoming messages that they might receive. This is necessary as incoming SURBs as well as requests for more SURBs contain empty data fields for the moment. -< smol explanation of SURBs + link > +```rust +pub async fn wait_for_non_empty_message( + client: &mut MixnetClient, +) -> anyhow::Result { + while let Some(mut new_message) = client.wait_for_messages().await { + if !new_message.is_empty() { + return Ok(new_message.pop().unwrap()); + } + } -As such, this function returns a tuple containing the `RequestType` _and_ the `sender_tag` used by the `service` to identify which bucket of pre-addressed replies it will use to respond to a request. - -< note concerning parsing out the empty vecs > + bail!("did not receive any non-empty message") +} +``` diff --git a/documentation/dev-portal/src/tutorials/cosmos-service/overview.md b/documentation/dev-portal/src/tutorials/cosmos-service/overview.md index 13acb800c9..d9f365d72c 100644 --- a/documentation/dev-portal/src/tutorials/cosmos-service/overview.md +++ b/documentation/dev-portal/src/tutorials/cosmos-service/overview.md @@ -2,7 +2,7 @@ This tutorial involves writing two pieces of code in Rust: -- A client side binary used to construct a blockchain query and send this query to a service, which will interact with a Cosmos SDK blockchain on our behalf (bear in mind this principle works for all blockchains - we're just utilising the `cosmrs` library to interact with the Nyx blockchain in this tutorial). +- A client side binary used to (for now) construct a blockchain query and send this query to a service, which will interact with a Cosmos SDK blockchain on our behalf (bear in mind this principle works for all blockchains - we're just utilising the `cosmrs` library to interact with the Nyx blockchain in this tutorial). This query will be to query the balance of an account, in preparation for spending these tokens on a credential in a subsequent tutorial. - A service which will listen out for requests from the mixnet, act on those requests, and anonymously reply to the client sending the requests. You will learn how to do the following with the Rust SDK: @@ -44,7 +44,7 @@ You can find the code for these components [here](). You can use it as a referen Notice that this tutorial attempts to use very few external libraries. This tutorial is not showing you how to build production-grade code, but **to understand how to connect and send messages to, as well as recieve messages from, the mixnet.** ```admonish note title="Sidenote: What is a Service / Service Provider?" -'Service' or 'Service Provider' are catchall names used to refer to any type of app that can communicate with the mixnet via a Nym client - in this case, one embedded in your app process via the Rust SDK. +'Service' or 'Service Provider' are catchall names used to refer to any type of app that can communicate with the mixnet via a Nym client - in this case, one embedded in its app process via the Rust SDK. The first SP to have been released is the [Network Requester](https://nymtech.net/docs/nodes/network-requester-setup.html) - a binary that receives a network request from the mixnet, performs that request (e.g. authenticating with a message server and receiving new messages for a user) and then passes the response back to the user who requested it anonymously, shielding their metadata from the message server. diff --git a/documentation/dev-portal/src/tutorials/cosmos-service/preparing-env.md b/documentation/dev-portal/src/tutorials/cosmos-service/preparing-env.md index 341c89a216..474a3add08 100644 --- a/documentation/dev-portal/src/tutorials/cosmos-service/preparing-env.md +++ b/documentation/dev-portal/src/tutorials/cosmos-service/preparing-env.md @@ -28,16 +28,17 @@ cargo new nym-cosmos-service * Add the following dependencies to your `Cargo.toml` file: ``` [dependencies] -anyhow = "1.0.72" clap = { version = "4.0", features = ["derive"] } -bip39 = { version = "2.0.0", features = ["zeroize"] } cosmrs = "=0.14.0" -TODO -# tokio = { workspace = true, features = ["rt-multi-thread", "macros"] } -tokio = "1.24.1" -bs58 = "0.5.0" +tokio = { version = "1.24.1", features = ["rt-multi-thread", "macros"] } serde = "1.0.152" serde_json = "1.0.91" +nym-sdk = { git = "https://github.com/nymtech/nym", rev = "5dacf0c8f8775de6168d4da808fdce56e1ac2706" } +nym-sphinx-addressing = { git = "https://github.com/nymtech/nym", rev = "5dacf0c8f8775de6168d4da808fdce56e1ac2706" } +nym-validator-client = { git = "https://github.com/nymtech/nym", rev = "5dacf0c8f8775de6168d4da808fdce56e1ac2706" } +nym-bin-common = { git = "https://github.com/nymtech/nym", rev = "5dacf0c8f8775de6168d4da808fdce56e1ac2706" } +nym-sphinx-anonymous-replies = { git = "https://github.com/nymtech/nym", rev = "5dacf0c8f8775de6168d4da808fdce56e1ac2706" } +anyhow = "1.0.72" ``` These are non Nym-specific dependencies for the project. `anyhow` is for catch-all error handling, `clap` is for setting up the CLI commands, `cosmrs` for cosmos-specific types and functionality, `tokio` for the async/await environment, and `serde` for (de)serialisation. diff --git a/documentation/dev-portal/src/tutorials/cosmos-service/service-src.md b/documentation/dev-portal/src/tutorials/cosmos-service/service-src.md new file mode 100644 index 0000000000..cc140cc11f --- /dev/null +++ b/documentation/dev-portal/src/tutorials/cosmos-service/service-src.md @@ -0,0 +1 @@ +# Preparing Your Service pt2 diff --git a/documentation/dev-portal/src/tutorials/cosmos-service/service.md b/documentation/dev-portal/src/tutorials/cosmos-service/service.md index 1090443702..8b4c3377bd 100644 --- a/documentation/dev-portal/src/tutorials/cosmos-service/service.md +++ b/documentation/dev-portal/src/tutorials/cosmos-service/service.md @@ -1 +1,71 @@ # Preparing Your Service +In `bin/src.rs` define the startup and response logic of the `service`. Client connection / config reading happens as it does in `bin/client.rs`. + +## Dependencies +``` +use chain_query::{ + create_client, handle_request, + service::{create_broadcaster, get_balance}, + BalanceResponse, RequestTypes, ResponseTypes, +}; +use nym_sphinx_anonymous_replies::{self, requests::AnonymousSenderTag}; +use nym_bin_common::logging::setup_logging +``` + +The imports from `chain_query` are most of the data types and functions defined in the previous sections of this tutorial. + +The `AnonymousSenderTag` type is used for SURBs. + +## main() +Also using [tokio](URL) for the async runtime, `main` does the following: +* If not already existing, creates a Nym client with config at `/tmp/client`. Otherwise it loads the already existing client from this config. +* Create a `broadcaster` - this is used by the service to interact with the blockchain, using the consts defined in `src/lib.rs` as chain config. +* Listen out for incoming messages, and in much the same way as the `client`, handle and match the incoming request. +* Using the `sender_tag`, anonymously reply to the Nym client in `client` with the response from the blockchain. + +```rust +#[tokio::main] +async fn main() -> anyhow::Result<()> { + setup_logging(); + let mut client = create_client("/tmp/service2".into()).await; + let our_address = client.nym_address(); + println!("\nservice's nym address: {our_address}"); + // the httpclient we will use to broadcast our query to the blockchain + let broadcaster = create_broadcaster().await?; + println!("listening for messages, press CTRL-C to exit"); + + while let Some(received) = client.wait_for_messages().await { + for msg in received { + let request = match handle_request(msg) { + Ok(request) => request, + Err(err) => { + eprintln!("failed to handle received request: {err}"); + continue; + } + }; + + let return_recipient: AnonymousSenderTag = request.1.expect("no sender tag received"); + match request.0 { + RequestTypes::Balance(request) => { + println!("\nincoming balance request for: {}\n", request.account); + + let balance: BalanceResponse = + get_balance(broadcaster.clone(), request.account).await?; + + let response = ResponseTypes::Balance(balance); + + println!("response from chain: {:#?}", response); + + println!("\nreturn recipient surb bucket: {}", &return_recipient); + println!("\nsending response to {}", &return_recipient); + // send response back to anon requesting client via mixnet + client + .send_str_reply(return_recipient, &serde_json::to_string(&response)?) + .await; + } + } + } + } + + Ok(()) +``` From c04993e49c18f1038b3acc7fa6063293ce700150 Mon Sep 17 00:00:00 2001 From: mfahampshire Date: Wed, 2 Aug 2023 23:29:56 +0200 Subject: [PATCH 054/361] added new pages for client and service /src/ files --- documentation/dev-portal/src/SUMMARY.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/documentation/dev-portal/src/SUMMARY.md b/documentation/dev-portal/src/SUMMARY.md index 3fd7145ff3..308cca8453 100644 --- a/documentation/dev-portal/src/SUMMARY.md +++ b/documentation/dev-portal/src/SUMMARY.md @@ -33,7 +33,9 @@ - [Preparing Your Environment](tutorials/cosmos-service/preparing-env.md) - [Preparing Your Lib](tutorials/cosmos-service/lib.md) - [Preparing Your Client](tutorials/cosmos-service/client.md) + - [Preparing Your Client pt2](tutorials/cosmos-service/client-src.md) - [Preparing Your Service](tutorials/cosmos-service/service.md) + - [Preparing Your Service pt2](tutorials/cosmos-service/service-src.md) - [(Typescript) Simple Service Provider](tutorials/simple-service-provider/simple-service-provider.md) - [Tutorial Overview](tutorials/simple-service-provider/overview.md) - [Preparing Your User Client Environment](tutorials/simple-service-provider/preparating-env.md) From dd4f4c44c30f85c3772c45dcc61bf81aba156c1b Mon Sep 17 00:00:00 2001 From: mfahampshire Date: Thu, 3 Aug 2023 10:53:23 +0200 Subject: [PATCH 055/361] finished first pass at tutorial --- documentation/dev-portal/src/SUMMARY.md | 33 +++++++------- .../tutorials/cosmos-service/client-src.md | 2 +- .../src/tutorials/cosmos-service/intro.md | 2 +- .../src/tutorials/cosmos-service/lib.md | 6 +-- .../src/tutorials/cosmos-service/overview.md | 39 +++++++--------- .../src/tutorials/cosmos-service/querying.md | 43 ++++++++++++++++++ .../tutorials/cosmos-service/service-src.md | 44 +++++++++++++++++++ .../src/tutorials/cosmos-service/service.md | 2 +- .../dev-portal/src/tutorials/rust-sdk.md | 5 +++ .../dev-portal/src/tutorials/rust.md | 1 + .../dev-portal/src/tutorials/typescript.md | 3 ++ 11 files changed, 135 insertions(+), 45 deletions(-) create mode 100644 documentation/dev-portal/src/tutorials/cosmos-service/querying.md create mode 100644 documentation/dev-portal/src/tutorials/rust-sdk.md create mode 100644 documentation/dev-portal/src/tutorials/rust.md create mode 100644 documentation/dev-portal/src/tutorials/typescript.md diff --git a/documentation/dev-portal/src/SUMMARY.md b/documentation/dev-portal/src/SUMMARY.md index 308cca8453..cff0fe87d9 100644 --- a/documentation/dev-portal/src/SUMMARY.md +++ b/documentation/dev-portal/src/SUMMARY.md @@ -28,21 +28,24 @@ # Tutorials -- [(Rust SDK) Cosmos Blockchain Service](tutorials/cosmos-service/intro.md) - - [Tutorial Overview](tutorials/cosmos-service/overview.md) - - [Preparing Your Environment](tutorials/cosmos-service/preparing-env.md) - - [Preparing Your Lib](tutorials/cosmos-service/lib.md) - - [Preparing Your Client](tutorials/cosmos-service/client.md) - - [Preparing Your Client pt2](tutorials/cosmos-service/client-src.md) - - [Preparing Your Service](tutorials/cosmos-service/service.md) - - [Preparing Your Service pt2](tutorials/cosmos-service/service-src.md) -- [(Typescript) Simple Service Provider](tutorials/simple-service-provider/simple-service-provider.md) - - [Tutorial Overview](tutorials/simple-service-provider/overview.md) - - [Preparing Your User Client Environment](tutorials/simple-service-provider/preparating-env.md) - - [Building Your User Client](tutorials/simple-service-provider/user-client.md) - - [Preparing Your Service Provider Environment](tutorials/simple-service-provider/preparating-env2.md) - - [Building Your Service Provider](tutorials/simple-service-provider/service-provider.md) - - [Sending a Message Through the Mixnet](tutorials/simple-service-provider/sending-message.md) +- [Rust SDK](tutorials/rust-sdk.md) + - [Blockchain Service pt1](tutorials/cosmos-service/intro.md) + - [Tutorial Overview](tutorials/cosmos-service/overview.md) + - [Preparing Your Environment](tutorials/cosmos-service/preparing-env.md) + - [Preparing Your Lib](tutorials/cosmos-service/lib.md) + - [Preparing Your Client](tutorials/cosmos-service/client.md) + - [Preparing Your Client pt2](tutorials/cosmos-service/client-src.md) + - [Preparing Your Service](tutorials/cosmos-service/service.md) + - [Preparing Your Service pt2](tutorials/cosmos-service/service-src.md) + - [Querying the Chain](tutorials/cosmos-service/querying.md) +- [Typescript](tutorials/typescript.md) + - [Simple Service Provider](tutorials/simple-service-provider/simple-service-provider.md) + - [Tutorial Overview](tutorials/simple-service-provider/overview.md) + - [Preparing Your User Client Environment](tutorials/simple-service-provider/preparating-env.md) + - [Building Your User Client](tutorials/simple-service-provider/user-client.md) + - [Preparing Your Service Provider Environment](tutorials/simple-service-provider/preparating-env2.md) + - [Building Your Service Provider](tutorials/simple-service-provider/service-provider.md) + - [Sending a Message Through the Mixnet](tutorials/simple-service-provider/sending-message.md) # Community Resources diff --git a/documentation/dev-portal/src/tutorials/cosmos-service/client-src.md b/documentation/dev-portal/src/tutorials/cosmos-service/client-src.md index 7b8085fc54..b8b223357b 100644 --- a/documentation/dev-portal/src/tutorials/cosmos-service/client-src.md +++ b/documentation/dev-portal/src/tutorials/cosmos-service/client-src.md @@ -14,7 +14,7 @@ use nym_validator_client::nyxd::Coin # Querying via the Mixnet The following is used to construct a `BalanceRequest`, send this to the supplied `service` address, and then handle the response, matching it to a `ResponseType`, in this case the only expected response, a `BalanceResponse`. -The actual sending of the request is performed by `client.send_bytes`: sending the serialised `BalanceRequest` to the supplied Nym address (the `Recipient` imported from the `nym_sphinx_addressing` crate). It is sending the default number of SURBs along with the message, defined [here](LINK_TO_SURB_DEFAULT_NUMBEr). +The actual sending of the request is performed by `client.send_bytes`: sending the serialised `BalanceRequest` to the supplied Nym address (the `Recipient` imported from the `nym_sphinx_addressing` crate). It is sending the default number of SURBs along with the message, defined [here](https://github.com/nymtech/nym/blob/develop/sdk/rust/nym-sdk/src/mixnet/client.rs#L34). ```rust pub async fn query_balance( diff --git a/documentation/dev-portal/src/tutorials/cosmos-service/intro.md b/documentation/dev-portal/src/tutorials/cosmos-service/intro.md index 342bca9132..5b08d0ecfe 100644 --- a/documentation/dev-portal/src/tutorials/cosmos-service/intro.md +++ b/documentation/dev-portal/src/tutorials/cosmos-service/intro.md @@ -4,4 +4,4 @@ This tutorial is for Rust developers wanting to interact with the Rust SDK and t The key here is to think of the service as a proxy: it interacts with the blockchain _on the client's behalf_, shielding it from the Validator it interacts with, whilst also being shielded from the client by the mixnet. -> This service also nicely highlights the limitations of the mixnet - even though with this code your metadata is shielded from the Validator, and even the service does not know your Nym address, application-level information such as a blockchain address is not made private, in virtue of the fact that using the mixnet provides solely network-level privacy. For information on what application-level privacy Nym offers, check out the [coconut credential SDK example](RELATIVE_PATH). +> This service also nicely highlights the limitations of the mixnet - even though with this code your metadata is shielded from the Validator, and even the service does not know your Nym address, application-level information such as a blockchain address is not made private, in virtue of the fact that using the mixnet provides solely network-level privacy. For information on what application-level privacy Nym offers, check out the [coconut credential SDK example](https://nymtech.net/docs/sdk/rust.html#coconut-credential-generation). diff --git a/documentation/dev-portal/src/tutorials/cosmos-service/lib.md b/documentation/dev-portal/src/tutorials/cosmos-service/lib.md index 13caff08aa..8adb147bd4 100644 --- a/documentation/dev-portal/src/tutorials/cosmos-service/lib.md +++ b/documentation/dev-portal/src/tutorials/cosmos-service/lib.md @@ -79,7 +79,7 @@ impl ResponseTypes { } ``` -The above data types are pretty straightforward. Even though there are only one instance of a request type (sent from client to service) and one of a response type (service -> client) so far, a pair of enums has been defined to contain additional response or request types that will be added in the future. +The above data types are pretty straightforward. Even though there are only one instance of a request type (sent from client -> service) and one of a response type (service -> client) so far, a pair of enums has been defined to contain additional response or request types that will be added in the future. `BalanceRequest` will be used when requesting the service to query the token balance of the supplied address on the client's behalf. You can see the information that will be returned from the chain to the service, and from the service to the client, in `BalanceResponse`. @@ -114,7 +114,7 @@ Next to define two functions: one for listening _for_ messages from the mixnet ( Both functions attempt to deserialise the vec of `ReconstructedMessages` that are reconstructed by the client from delivered Sphinx packets after decryption. -`handle_request` performs one additional function - parsing the `sender_tag` from the incoming reconstructed message. This is the randomised alphanumeric string used to identify a bucket of _SURBs_ (_S_ingle _U_se _R_eply _B_locks) that are sent along with any outgoing message by default. More information about them can be found [here](LINK_TO_SURBS_DOCS) but all that is necessary to know for now is that these are pre-addressed packets that clients send out with their messages. Any reply to their message that is to be sent back to them back be written to the payload of these packets, but without the entity seeing the destination address. This allows for services to _anonymously reply_ to clients without being able to doxx them. +`handle_request` performs one additional function - parsing the `sender_tag` from the incoming reconstructed message. This is the randomised alphanumeric string used to identify a bucket of _SURBs_ (Single Use Reply Blocks) that are sent along with any outgoing message by default. More information about them can be found [here](https://nymtech.net/docs/architecture/traffic-flow.html#private-replies-using-surbs) but all that is necessary to know for now is that these are pre-addressed packets that clients send out with their messages. Any reply to their message that is to be sent back to them back be written to the payload of these packets, but without the entity seeing the destination address. This allows for services to _anonymously reply_ to clients without being able to doxx them. ```rust pub fn handle_response(message: ReconstructedMessage) -> anyhow::Result { @@ -129,7 +129,7 @@ pub fn handle_request( } ``` -Before moving on to the `client` and `service` code, one more function is needed. This allows for both binaries to parse empty incoming messages that they might receive. This is necessary as incoming SURBs as well as requests for more SURBs contain empty data fields for the moment. +Before moving on to the `client` and `service` code, one more function is needed. This allows for both binaries to parse empty incoming messages that they might receive. This is necessary as incoming SURBs as well as requests for more SURBs contain empty data fields. ```rust pub async fn wait_for_non_empty_message( diff --git a/documentation/dev-portal/src/tutorials/cosmos-service/overview.md b/documentation/dev-portal/src/tutorials/cosmos-service/overview.md index d9f365d72c..a422ea4047 100644 --- a/documentation/dev-portal/src/tutorials/cosmos-service/overview.md +++ b/documentation/dev-portal/src/tutorials/cosmos-service/overview.md @@ -13,30 +13,21 @@ You will learn how to do the following with the Rust SDK: ``` - +----------+ +----------+ +----------+ - | Mix Node |<-----------> | Mix Node |<----------->| Mix Node | - | Layer 1 | | Layer 2 | | Layer 3 | - +----------+ +----------+ +----------+ - ^ ^ - | | - |<--------------------------------------------------+ - | - v - +--------------+ - | Your gateway | - +--------------+ - ^ - | - | - v -+-------------------------------------------+ -| | -| +-------------+ +--------------+ | -| | Client code | | Service code | | -| +-------------+ +--------------+ | -| | -+-------------------------------------------+ - Your Local Machine + +TODO + +REDRAW + +ASCII + +DIAGRAM + +FROM + +PREVIOUS + +TUTORIAL + ``` You can find the code for these components [here](). You can use it as a reference while building or simply download it and follow along as you progress through the tutorial. diff --git a/documentation/dev-portal/src/tutorials/cosmos-service/querying.md b/documentation/dev-portal/src/tutorials/cosmos-service/querying.md new file mode 100644 index 0000000000..04a6f92832 --- /dev/null +++ b/documentation/dev-portal/src/tutorials/cosmos-service/querying.md @@ -0,0 +1,43 @@ +# Querying the Chain +Now that all the code has been written, time to query the blockchain. + +To test against the account operating one of the mix nodes on the testnet, use `n1lcutqz94k739s39u26rvexql40ehf42zd27fwe` in the following instructions: + +```sh +# build the binaries +cargo build --release + +# open two console windows. In one run the following +./target/release/service + +# copy the printed Nym address from the console - this is used by the client when running the chain query + +# in the other run +./target/release/client query-balance n1lcutqz94k739s39u26rvexql40ehf42zd27fwe +``` + +The following happens: +* `client` and `service` both start their Nym clients and log the address. `service` is listening for incoming messages from the mixnet. +* `client` sends a request to `service` using the supplied `n1...` address as the Nyx account to query the balance of, and the supplied Nym address to communicate with this instance of `service`. +* `service` queries the Sandbox testnet blockchain using the `broadcaster` http client. It then serialises the response and returns it using SURBs to the `client`. + +All in all, quite simple. By using the service as a proxy, the client never interacts with the blockchain, thus is not revealing metadata to the operator of the chain or anyone watching incoming traffic to it. Furthermore, the client doesn't even need to share its Nym address with the service, as the service is able to reply via SURBs. + +## Creating Sandbox Account +If you wish to create an account on Sandbox to use instead of the supplied account above, the easiest way is by building the `nym-cli` tool and using that to create one: + +```sh +# start from the root of the nym monorepo +cd tools/nym-cli +# build +cargo build --release +# create account using Sandbox testnet environment +../../target/release/nym-cli --config-env-file ../../envs/sandbox.env account create +``` + +However, since this account is fresh, it won't have any tokens. Querying the balance will still work obviously, it will just return `0`. + +## Get Tokens +We're working on getting the faucet up and running again in preparation for part 2 of this tutorial: using the tokens you have privately checked the balance of to generate a [bandwidth credential](https://nymtech.net/docs/bandwidth-credentials.html). + +If you wish to get testnet tokens already then feel free to ask in the [Dev channel](https://matrix.to/#/#dev:nymtech.chat) on Matrix. \ No newline at end of file diff --git a/documentation/dev-portal/src/tutorials/cosmos-service/service-src.md b/documentation/dev-portal/src/tutorials/cosmos-service/service-src.md index cc140cc11f..6fb3324726 100644 --- a/documentation/dev-portal/src/tutorials/cosmos-service/service-src.md +++ b/documentation/dev-portal/src/tutorials/cosmos-service/service-src.md @@ -1 +1,45 @@ # Preparing Your Service pt2 + +Now to define the logic of creating the `broadcaster` for interacting with the blockchain, and querying it in `src/service.rs`. + +## Dependencies +The following dependencies are for creating a client to interact with the blockchain, and deal with the returned `Coin` type expected from the balance query. + +```rust +use crate::{BalanceResponse, DEFAULT_DENOM, DEFAULT_VALIDATOR_RPC}; +use cosmrs::rpc::HttpClient; +use cosmrs::AccountId; +use nym_validator_client::nyxd::{Coin, CosmWasmClient}; +``` + +## Creating a `broadcaster` +The `broadcaster` is an `HttpClient` taken from `cosmrs`, created with the `DEFAULT_VALIDATOR_RPC` as its default endpoint. The service will use this to query the Sandbox testnet chain. + +```rust +pub async fn create_broadcaster() -> anyhow::Result { + let broadcaster: HttpClient = HttpClient::new(DEFAULT_VALIDATOR_RPC)?; + Ok(broadcaster) +} +``` + +## Querying the Chain +Now to write the logic for querying the chain, using the `broadcaster` created in the previous step to query for the balance of the `account` that the client sent via the mixnet in the `BalanceRequest`. This function returns a `BalanceResponse` containing a `Coin` type, denoting the `balance` and `denom` returned by `get_balance()`. + +```rust +pub async fn get_balance( + broadcaster: HttpClient, + account: AccountId, +) -> anyhow::Result { + let balance = broadcaster + .get_balance(&account, DEFAULT_DENOM.to_string()) + .await + .unwrap() + .unwrap(); + Ok(BalanceResponse { + balance: Coin { + amount: balance.amount, + denom: balance.denom, + }, + }) +} +``` \ No newline at end of file diff --git a/documentation/dev-portal/src/tutorials/cosmos-service/service.md b/documentation/dev-portal/src/tutorials/cosmos-service/service.md index 8b4c3377bd..f8313bed8b 100644 --- a/documentation/dev-portal/src/tutorials/cosmos-service/service.md +++ b/documentation/dev-portal/src/tutorials/cosmos-service/service.md @@ -17,7 +17,7 @@ The imports from `chain_query` are most of the data types and functions defined The `AnonymousSenderTag` type is used for SURBs. ## main() -Also using [tokio](URL) for the async runtime, `main` does the following: +Also using tokio for the async runtime, `main` does the following: * If not already existing, creates a Nym client with config at `/tmp/client`. Otherwise it loads the already existing client from this config. * Create a `broadcaster` - this is used by the service to interact with the blockchain, using the consts defined in `src/lib.rs` as chain config. * Listen out for incoming messages, and in much the same way as the `client`, handle and match the incoming request. diff --git a/documentation/dev-portal/src/tutorials/rust-sdk.md b/documentation/dev-portal/src/tutorials/rust-sdk.md new file mode 100644 index 0000000000..f8413de28b --- /dev/null +++ b/documentation/dev-portal/src/tutorials/rust-sdk.md @@ -0,0 +1,5 @@ +# Rust SDK + +The Rust SDK allows developers building applications in Rust to import and interact with Nym clients as they would any other dependency, instead of running the client as a seperate process on their machine. + +[Read the docs](https://nymtech.net/docs/sdk/rust.html). \ No newline at end of file diff --git a/documentation/dev-portal/src/tutorials/rust.md b/documentation/dev-portal/src/tutorials/rust.md new file mode 100644 index 0000000000..5aef071e46 --- /dev/null +++ b/documentation/dev-portal/src/tutorials/rust.md @@ -0,0 +1 @@ +# Rust SDK diff --git a/documentation/dev-portal/src/tutorials/typescript.md b/documentation/dev-portal/src/tutorials/typescript.md new file mode 100644 index 0000000000..8c6e758ff4 --- /dev/null +++ b/documentation/dev-portal/src/tutorials/typescript.md @@ -0,0 +1,3 @@ +# Typescript + +Tutorial code in this section is built to interact with a standalone Nym client. You can read about interacting with standalone clients [here](https://nymtech.net/docs/clients/websocket-client.html#connecting-to-the-local-websocket), although it is usually preferable to use the [Typescript SDK](https://nymtech.net/docs/sdk/typescript.html). \ No newline at end of file From b66c4cf0bb9b29b0b326ce7a5dd748fe724ea3ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C4=99drzej=20Stuczy=C5=84ski?= Date: Thu, 3 Aug 2023 11:01:03 +0100 Subject: [PATCH 056/361] removed dependency on wasm-timer fork (#3733) --- Cargo.lock | 39 +- Cargo.toml | 3 + clients/webassembly/Cargo.lock | 53 +- clients/webassembly/Cargo.toml | 2 +- .../src/tester/ephemeral_receiver.rs | 3 +- common/client-core/Cargo.toml | 6 +- .../src/client/cover_traffic_stream.rs | 37 +- common/client-core/src/client/helpers/wasm.rs | 5 +- .../real_traffic_stream.rs | 32 +- common/client-core/src/init/helpers.rs | 16 +- common/client-libs/gateway-client/Cargo.toml | 6 +- .../client-libs/gateway-client/src/client.rs | 25 +- common/nonexhaustive-delayqueue/Cargo.toml | 18 +- common/nonexhaustive-delayqueue/src/lib.rs | 12 +- .../src/wasm_delay_queue/delay_queue.rs | 1234 ----------------- .../src/wasm_delay_queue/mod.rs | 42 - .../src/wasm_delay_queue/wheel/level.rs | 253 ---- .../src/wasm_delay_queue/wheel/mod.rs | 315 ----- .../src/wasm_delay_queue/wheel/stack.rs | 28 - 19 files changed, 102 insertions(+), 2027 deletions(-) delete mode 100644 common/nonexhaustive-delayqueue/src/wasm_delay_queue/delay_queue.rs delete mode 100644 common/nonexhaustive-delayqueue/src/wasm_delay_queue/mod.rs delete mode 100644 common/nonexhaustive-delayqueue/src/wasm_delay_queue/wheel/level.rs delete mode 100644 common/nonexhaustive-delayqueue/src/wasm_delay_queue/wheel/mod.rs delete mode 100644 common/nonexhaustive-delayqueue/src/wasm_delay_queue/wheel/stack.rs diff --git a/Cargo.lock b/Cargo.lock index d576e05c0c..a823625624 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2924,9 +2924,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" dependencies = [ "cfg-if", - "js-sys", - "wasm-bindgen", - "web-sys", ] [[package]] @@ -3808,8 +3805,8 @@ dependencies = [ "url", "wasm-bindgen", "wasm-bindgen-futures", - "wasm-timer", "wasm-utils", + "wasmtimer", "zeroize", ] @@ -4082,8 +4079,8 @@ dependencies = [ "url", "wasm-bindgen", "wasm-bindgen-futures", - "wasm-timer", "wasm-utils", + "wasmtimer", ] [[package]] @@ -4361,12 +4358,10 @@ dependencies = [ name = "nym-nonexhaustive-delayqueue" version = "0.1.0" dependencies = [ - "futures-core", - "slab", "tokio", "tokio-stream", "tokio-util", - "wasm-timer", + "wasmtimer", ] [[package]] @@ -8131,20 +8126,6 @@ version = "0.2.83" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1c38c045535d93ec4f0b4defec448e4291638ee608530863b1e2ba115d4fff7f" -[[package]] -name = "wasm-timer" -version = "0.2.5" -source = "git+https://github.com/mmsinclair/wasm-timer?rev=b9d1a54ad514c2f230a026afe0dde341e98cd7b6#b9d1a54ad514c2f230a026afe0dde341e98cd7b6" -dependencies = [ - "futures", - "js-sys", - "parking_lot 0.11.2", - "pin-utils", - "wasm-bindgen", - "wasm-bindgen-futures", - "web-sys", -] - [[package]] name = "wasm-utils" version = "0.1.0" @@ -8163,6 +8144,20 @@ dependencies = [ "web-sys", ] +[[package]] +name = "wasmtimer" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f656cd8858a5164932d8a90f936700860976ec21eb00e0fe2aa8cab13f6b4cf" +dependencies = [ + "futures", + "js-sys", + "parking_lot 0.12.1", + "pin-utils", + "slab", + "wasm-bindgen", +] + [[package]] name = "web-sys" version = "0.3.60" diff --git a/Cargo.toml b/Cargo.toml index fd137c6bdf..22b4cb99ac 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -146,3 +146,6 @@ thiserror = "1.0.38" tokio = "1.24.1" url = "2.2" zeroize = "1.6.0" + +# wasm-related dependencies +wasmtimer = "0.2.0" \ No newline at end of file diff --git a/clients/webassembly/Cargo.lock b/clients/webassembly/Cargo.lock index f0733eac1b..330f31c4d5 100644 --- a/clients/webassembly/Cargo.lock +++ b/clients/webassembly/Cargo.lock @@ -1539,7 +1539,7 @@ checksum = "a604f7a68fbf8103337523b1fadc8ade7361ee3f112f7c680ad179651616aed5" dependencies = [ "futures-core", "lock_api", - "parking_lot", + "parking_lot 0.11.2", ] [[package]] @@ -2114,9 +2114,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" dependencies = [ "cfg-if 1.0.0", - "js-sys", - "wasm-bindgen", - "web-sys", ] [[package]] @@ -2501,8 +2498,8 @@ dependencies = [ "url", "wasm-bindgen", "wasm-bindgen-futures", - "wasm-timer", "wasm-utils", + "wasmtimer", "zeroize", ] @@ -2539,8 +2536,8 @@ dependencies = [ "wasm-bindgen", "wasm-bindgen-futures", "wasm-bindgen-test", - "wasm-timer", "wasm-utils", + "wasmtimer", "wee_alloc", "zeroize", ] @@ -2719,8 +2716,8 @@ dependencies = [ "url", "wasm-bindgen", "wasm-bindgen-futures", - "wasm-timer", "wasm-utils", + "wasmtimer", ] [[package]] @@ -2836,12 +2833,10 @@ dependencies = [ name = "nym-nonexhaustive-delayqueue" version = "0.1.0" dependencies = [ - "futures-core", - "slab", "tokio", "tokio-stream", "tokio-util", - "wasm-timer", + "wasmtimer", ] [[package]] @@ -3252,6 +3247,16 @@ dependencies = [ "parking_lot_core 0.8.6", ] +[[package]] +name = "parking_lot" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" +dependencies = [ + "lock_api", + "parking_lot_core 0.9.7", +] + [[package]] name = "parking_lot_core" version = "0.8.6" @@ -5233,20 +5238,6 @@ dependencies = [ "quote", ] -[[package]] -name = "wasm-timer" -version = "0.2.5" -source = "git+https://github.com/mmsinclair/wasm-timer?rev=b9d1a54ad514c2f230a026afe0dde341e98cd7b6#b9d1a54ad514c2f230a026afe0dde341e98cd7b6" -dependencies = [ - "futures", - "js-sys", - "parking_lot", - "pin-utils", - "wasm-bindgen", - "wasm-bindgen-futures", - "web-sys", -] - [[package]] name = "wasm-utils" version = "0.1.0" @@ -5265,6 +5256,20 @@ dependencies = [ "web-sys", ] +[[package]] +name = "wasmtimer" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f656cd8858a5164932d8a90f936700860976ec21eb00e0fe2aa8cab13f6b4cf" +dependencies = [ + "futures", + "js-sys", + "parking_lot 0.12.1", + "pin-utils", + "slab", + "wasm-bindgen", +] + [[package]] name = "web-sys" version = "0.3.60" diff --git a/clients/webassembly/Cargo.toml b/clients/webassembly/Cargo.toml index 11d1cb131a..7405002cea 100644 --- a/clients/webassembly/Cargo.toml +++ b/clients/webassembly/Cargo.toml @@ -33,7 +33,7 @@ wasm-bindgen-futures = "0.4" thiserror = "1.0.40" zeroize = "1.6.0" -wasm-timer = { git = "https://github.com/mmsinclair/wasm-timer", rev = "b9d1a54ad514c2f230a026afe0dde341e98cd7b6"} +wasmtimer = { version = "0.2.0", features = ["tokio"] } # internal nym-node-tester-utils = { path = "../../common/node-tester-utils" } diff --git a/clients/webassembly/src/tester/ephemeral_receiver.rs b/clients/webassembly/src/tester/ephemeral_receiver.rs index 4f2ce3cd8a..826bfa07b2 100644 --- a/clients/webassembly/src/tester/ephemeral_receiver.rs +++ b/clients/webassembly/src/tester/ephemeral_receiver.rs @@ -10,6 +10,7 @@ use std::collections::HashSet; use std::time::Duration; use tokio::sync::MutexGuard as AsyncMutexGuard; use wasm_utils::{console_error, console_log, console_warn}; +use wasmtimer::tokio::sleep; pub(crate) struct EphemeralTestReceiver<'a> { sent_packets: u32, @@ -90,7 +91,7 @@ impl<'a> EphemeralTestReceiver<'a> { } pub(crate) async fn perform_test(mut self) -> NodeTestResult { - let mut timeout_fut = wasm_timer::Delay::new(self.timeout_duration); + let mut timeout_fut = sleep(self.timeout_duration); loop { tokio::select! { diff --git a/common/client-core/Cargo.toml b/common/client-core/Cargo.toml index d5ae32dbd2..ab1c9b8c04 100644 --- a/common/client-core/Cargo.toml +++ b/common/client-core/Cargo.toml @@ -69,9 +69,9 @@ version = "0.4" [target."cfg(target_arch = \"wasm32\")".dependencies.wasm-bindgen] version = "0.2.83" -[target."cfg(target_arch = \"wasm32\")".dependencies.wasm-timer] -git = "https://github.com/mmsinclair/wasm-timer" -rev = "b9d1a54ad514c2f230a026afe0dde341e98cd7b6" +[target."cfg(target_arch = \"wasm32\")".dependencies.wasmtimer] +workspace = true +features = ["tokio"] [target."cfg(target_arch = \"wasm32\")".dependencies.gloo-timers] version = "0.2.4" diff --git a/common/client-core/src/client/cover_traffic_stream.rs b/common/client-core/src/client/cover_traffic_stream.rs index 8784bfb7ba..c603926933 100644 --- a/common/client-core/src/client/cover_traffic_stream.rs +++ b/common/client-core/src/client/cover_traffic_stream.rs @@ -19,10 +19,10 @@ use std::time::Duration; use tokio::sync::mpsc::error::TrySendError; #[cfg(not(target_arch = "wasm32"))] -use tokio::time; +use tokio::time::{sleep, Sleep}; #[cfg(target_arch = "wasm32")] -use wasm_timer; +use wasmtimer::tokio::{sleep, Sleep}; pub struct LoopCoverTrafficStream where @@ -39,11 +39,7 @@ where /// Internal state, determined by `average_message_sending_delay`, /// used to keep track of when a next packet should be sent out. - #[cfg(not(target_arch = "wasm32"))] - next_delay: Pin>, - - #[cfg(target_arch = "wasm32")] - next_delay: Pin>, + next_delay: Pin>, /// Channel used for sending prepared nym packets to `MixTrafficController` that sends them /// out to the network without any further delays. @@ -90,17 +86,9 @@ where // The next interval value is `next_poisson_delay` after the one that just // yielded. - #[cfg(not(target_arch = "wasm32"))] - { - let now = self.next_delay.deadline(); - let next = now + next_poisson_delay; - self.next_delay.as_mut().reset(next); - } - - #[cfg(target_arch = "wasm32")] - { - self.next_delay.as_mut().reset(next_poisson_delay); - } + let now = self.next_delay.deadline(); + let next = now + next_poisson_delay; + self.next_delay.as_mut().reset(next); Poll::Ready(Some(())) } @@ -120,11 +108,7 @@ impl LoopCoverTrafficStream { ) -> Self { let rng = OsRng; - #[cfg(not(target_arch = "wasm32"))] - let next_delay = Box::pin(time::sleep(Default::default())); - - #[cfg(target_arch = "wasm32")] - let next_delay = Box::pin(wasm_timer::Delay::new(Default::default())); + let next_delay = Box::pin(sleep(Default::default())); LoopCoverTrafficStream { ack_key, @@ -142,12 +126,7 @@ impl LoopCoverTrafficStream { } fn set_next_delay(&mut self, amount: Duration) { - #[cfg(not(target_arch = "wasm32"))] - let next_delay = Box::pin(time::sleep(amount)); - - #[cfg(target_arch = "wasm32")] - let next_delay = Box::pin(wasm_timer::Delay::new(amount)); - + let next_delay = Box::pin(sleep(amount)); self.next_delay = next_delay; } diff --git a/common/client-core/src/client/helpers/wasm.rs b/common/client-core/src/client/helpers/wasm.rs index f04dd64028..5046463ee5 100644 --- a/common/client-core/src/client/helpers/wasm.rs +++ b/common/client-core/src/client/helpers/wasm.rs @@ -2,13 +2,12 @@ // SPDX-License-Identifier: Apache-2.0 use std::time::Duration; -use wasm_timer; -pub use wasm_timer::*; +pub use wasmtimer::{std::Instant, tokio::*}; pub type IntervalStream = gloo_timers::future::IntervalStream; pub(crate) fn get_time_now() -> Instant { - wasm_timer::Instant::now() + Instant::now() } pub(crate) fn new_interval_stream(polling_rate: Duration) -> IntervalStream { diff --git a/common/client-core/src/client/real_messages_control/real_traffic_stream.rs b/common/client-core/src/client/real_messages_control/real_traffic_stream.rs index 44c9810e70..a160a51894 100644 --- a/common/client-core/src/client/real_messages_control/real_traffic_stream.rs +++ b/common/client-core/src/client/real_messages_control/real_traffic_stream.rs @@ -27,9 +27,10 @@ use std::sync::Arc; use std::time::Duration; #[cfg(not(target_arch = "wasm32"))] -use tokio::time; +use tokio::time::{sleep, Sleep}; + #[cfg(target_arch = "wasm32")] -use wasm_timer; +use wasmtimer::tokio::{sleep, Sleep}; mod sending_delay_controller; @@ -82,11 +83,7 @@ where /// Internal state, determined by `average_message_sending_delay`, /// used to keep track of when a next packet should be sent out. - #[cfg(not(target_arch = "wasm32"))] - next_delay: Option>>, - - #[cfg(target_arch = "wasm32")] - next_delay: Option>>, + next_delay: Option>>, // To make sure we don't overload the mix_tx channel, we limit the rate we are pushing // messages. @@ -373,17 +370,9 @@ where // The next interval value is `next_poisson_delay` after the one that just // yielded. - #[cfg(not(target_arch = "wasm32"))] - { - let now = next_delay.deadline(); - let next = now + next_poisson_delay; - next_delay.as_mut().reset(next); - } - - #[cfg(target_arch = "wasm32")] - { - next_delay.as_mut().reset(next_poisson_delay); - } + let now = next_delay.deadline(); + let next = now + next_poisson_delay; + next_delay.as_mut().reset(next); // On every iteration we get new messages from upstream. Given that these come bunched // in `Vec`, this ensures that on average we will fetch messages faster than we can @@ -421,12 +410,7 @@ where self.config.traffic.message_sending_average_delay, ); - #[cfg(not(target_arch = "wasm32"))] - let next_delay = Box::pin(time::sleep(sampled)); - - #[cfg(target_arch = "wasm32")] - let next_delay = Box::pin(wasm_timer::Delay::new(sampled)); - + let next_delay = Box::pin(sleep(sampled)); self.next_delay = Some(next_delay); Poll::Pending diff --git a/common/client-core/src/init/helpers.rs b/common/client-core/src/init/helpers.rs index 1c23d562f9..723e9d02a2 100644 --- a/common/client-core/src/init/helpers.rs +++ b/common/client-core/src/init/helpers.rs @@ -27,13 +27,17 @@ use tokio_tungstenite::connect_async; use tokio_tungstenite::{MaybeTlsStream, WebSocketStream}; #[cfg(not(target_arch = "wasm32"))] type WsConn = WebSocketStream>; +#[cfg(not(target_arch = "wasm32"))] +use tokio::time::sleep; #[cfg(target_arch = "wasm32")] use nym_bandwidth_controller::wasm_mockups::DirectSigningNyxdClient; #[cfg(target_arch = "wasm32")] -use wasm_timer::Instant; -#[cfg(target_arch = "wasm32")] use wasm_utils::websocket::JSWebsocket; +#[cfg(target_arch = "wasm32")] +use wasmtimer::std::Instant; +#[cfg(target_arch = "wasm32")] +use wasmtimer::tokio::sleep; #[cfg(target_arch = "wasm32")] type WsConn = JSWebsocket; @@ -125,15 +129,9 @@ async fn measure_latency(gateway: &gateway::Node) -> Result(()) }; - // thanks to wasm we can't use tokio::time::timeout : ( - #[cfg(not(target_arch = "wasm32"))] - let timeout = tokio::time::sleep(PING_TIMEOUT); - #[cfg(not(target_arch = "wasm32"))] + let timeout = sleep(PING_TIMEOUT); tokio::pin!(timeout); - #[cfg(target_arch = "wasm32")] - let mut timeout = wasm_timer::Delay::new(PING_TIMEOUT); - tokio::select! { _ = &mut timeout => { warn!("timed out while trying to perform measurement...") diff --git a/common/client-libs/gateway-client/Cargo.toml b/common/client-libs/gateway-client/Cargo.toml index 20d5c958b0..197acdadef 100644 --- a/common/client-libs/gateway-client/Cargo.toml +++ b/common/client-libs/gateway-client/Cargo.toml @@ -58,9 +58,9 @@ path = "../../wasm-utils" features = ["websocket"] # only import it in wasm. Prefer proper tokio timer in non-wasm -[target."cfg(target_arch = \"wasm32\")".dependencies.wasm-timer] -git = "https://github.com/mmsinclair/wasm-timer" -rev = "b9d1a54ad514c2f230a026afe0dde341e98cd7b6" +[target."cfg(target_arch = \"wasm32\")".dependencies.wasmtimer] +workspace = true +features = ["tokio"] # this is due to tungstenite using `rand` 0.8 and associated changes in `getrandom` crate # which now does not support wasm32-unknown-unknown target by default. diff --git a/common/client-libs/gateway-client/src/client.rs b/common/client-libs/gateway-client/src/client.rs index a6ef774d8f..bdc2c626df 100644 --- a/common/client-libs/gateway-client/src/client.rs +++ b/common/client-libs/gateway-client/src/client.rs @@ -31,14 +31,16 @@ use tungstenite::protocol::Message; #[cfg(not(target_arch = "wasm32"))] use nym_validator_client::nyxd::traits::DkgQueryClient; #[cfg(not(target_arch = "wasm32"))] +use tokio::time::sleep; +#[cfg(not(target_arch = "wasm32"))] use tokio_tungstenite::connect_async; #[cfg(target_arch = "wasm32")] use nym_bandwidth_controller::wasm_mockups::DkgQueryClient; #[cfg(target_arch = "wasm32")] -use wasm_timer; -#[cfg(target_arch = "wasm32")] use wasm_utils::websocket::JSWebsocket; +#[cfg(target_arch = "wasm32")] +use wasmtimer::tokio::sleep; const DEFAULT_RECONNECTION_ATTEMPTS: usize = 10; const DEFAULT_RECONNECTION_BACKOFF: Duration = Duration::from_secs(5); @@ -198,15 +200,7 @@ impl GatewayClient { } #[cfg(not(target_arch = "wasm32"))] - tokio::time::sleep(self.reconnection_backoff).await; - - #[cfg(target_arch = "wasm32")] - if let Err(err) = wasm_timer::Delay::new(self.reconnection_backoff).await { - error!( - "the timer has gone away while in reconnection backoff! - {}", - err - ); - } + sleep(self.reconnection_backoff).await; } // final attempt (done separately to be able to return a proper error) @@ -235,16 +229,9 @@ impl GatewayClient { _ => return Err(GatewayClientError::ConnectionInInvalidState), }; - #[cfg(not(target_arch = "wasm32"))] - let timeout = tokio::time::sleep(self.response_timeout_duration); - #[cfg(not(target_arch = "wasm32"))] + let timeout = sleep(self.response_timeout_duration); tokio::pin!(timeout); - // technically the `wasm_timer` also works outside wasm, but unless required, - // I really prefer to just stick to tokio - #[cfg(target_arch = "wasm32")] - let mut timeout = wasm_timer::Delay::new(self.response_timeout_duration); - loop { tokio::select! { _ = self.shutdown.recv() => { diff --git a/common/nonexhaustive-delayqueue/Cargo.toml b/common/nonexhaustive-delayqueue/Cargo.toml index 975eb631fe..f79284dd50 100644 --- a/common/nonexhaustive-delayqueue/Cargo.toml +++ b/common/nonexhaustive-delayqueue/Cargo.toml @@ -7,16 +7,16 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -tokio = { version = "1.24.1", features = [] } tokio-stream = "0.1.11" # this one seems to be a thing until `Stream` trait is stabilised in stdlib -tokio-util = { version = "0.7.4", features = ["time"] } -[target."cfg(target_arch = \"wasm32\")".dependencies.wasm-timer] -git = "https://github.com/mmsinclair/wasm-timer" -rev = "b9d1a54ad514c2f230a026afe0dde341e98cd7b6" +[target."cfg(not(target_arch = \"wasm32\"))".dependencies.tokio] +workspace = true -[target."cfg(target_arch = \"wasm32\")".dependencies.slab] -version = "0.4.4" +[target."cfg(not(target_arch = \"wasm32\"))".dependencies.tokio-util] +version = "0.7.4" +features = ["time"] + +[target."cfg(target_arch = \"wasm32\")".dependencies.wasmtimer] +workspace = true +features = ["tokio", "tokio-util"] -[target."cfg(target_arch = \"wasm32\")".dependencies.futures-core] -version = "0.3.0" diff --git a/common/nonexhaustive-delayqueue/src/lib.rs b/common/nonexhaustive-delayqueue/src/lib.rs index 4e2329db2b..ee8c9f214c 100644 --- a/common/nonexhaustive-delayqueue/src/lib.rs +++ b/common/nonexhaustive-delayqueue/src/lib.rs @@ -6,12 +6,8 @@ use std::task::{Context, Poll, Waker}; use std::time::Duration; use tokio_stream::Stream; -pub use tokio::time::error::Error as TimerError; - // this is a copy of tokio-util delay_queue with `Sleep` and `Instant` being replaced with // `wasm_timer` equivalents -#[cfg(target_arch = "wasm32")] -mod wasm_delay_queue; #[cfg(not(target_arch = "wasm32"))] type DelayQueue = tokio_util::time::DelayQueue; @@ -23,13 +19,13 @@ pub type QueueKey = tokio_util::time::delay_queue::Key; use tokio::time::Instant; #[cfg(target_arch = "wasm32")] -type DelayQueue = crate::wasm_delay_queue::DelayQueue; +type DelayQueue = wasmtimer::tokio_util::DelayQueue; #[cfg(target_arch = "wasm32")] -pub use crate::wasm_delay_queue::delay_queue::Expired; +pub use wasmtimer::tokio_util::delay_queue::Expired; #[cfg(target_arch = "wasm32")] -pub type QueueKey = crate::wasm_delay_queue::delay_queue::Key; +pub type QueueKey = wasmtimer::tokio_util::delay_queue::Key; #[cfg(target_arch = "wasm32")] -use wasm_timer::Instant; +use wasmtimer::std::Instant; /// A variant of tokio's `DelayQueue`, such that its `Stream` implementation will never return a 'None'. pub struct NonExhaustiveDelayQueue { diff --git a/common/nonexhaustive-delayqueue/src/wasm_delay_queue/delay_queue.rs b/common/nonexhaustive-delayqueue/src/wasm_delay_queue/delay_queue.rs deleted file mode 100644 index 6d18b7b12d..0000000000 --- a/common/nonexhaustive-delayqueue/src/wasm_delay_queue/delay_queue.rs +++ /dev/null @@ -1,1234 +0,0 @@ -//! A queue of delayed elements. -//! -//! See [`DelayQueue`] for more details. -//! -//! [`DelayQueue`]: struct@DelayQueue - -use crate::wasm_delay_queue::sleep_until; -use crate::wasm_delay_queue::wheel::{self, Wheel}; -use core::ops::{Index, IndexMut}; -use futures_core::{ready, Future}; -use slab::Slab; -use std::cmp; -use std::collections::HashMap; -use std::convert::From; -use std::fmt; -use std::fmt::Debug; -use std::marker::PhantomData; -use std::pin::Pin; -use std::task::{self, Poll, Waker}; -use std::time::Duration; -use wasm_timer::{Delay, Instant}; - -/// A queue of delayed elements. -/// -/// Once an element is inserted into the `DelayQueue`, it is yielded once the -/// specified deadline has been reached. -/// -/// # Usage -/// -/// Elements are inserted into `DelayQueue` using the [`insert`] or -/// [`insert_at`] methods. A deadline is provided with the item and a [`Key`] is -/// returned. The key is used to remove the entry or to change the deadline at -/// which it should be yielded back. -/// -/// Once delays have been configured, the `DelayQueue` is used via its -/// [`Stream`] implementation. [`poll_expired`] is called. If an entry has reached its -/// deadline, it is returned. If not, `Poll::Pending` is returned indicating that the -/// current task will be notified once the deadline has been reached. -/// -/// # `Stream` implementation -/// -/// Items are retrieved from the queue via [`DelayQueue::poll_expired`]. If no delays have -/// expired, no items are returned. In this case, `Poll::Pending` is returned and the -/// current task is registered to be notified once the next item's delay has -/// expired. -/// -/// If no items are in the queue, i.e. `is_empty()` returns `true`, then `poll` -/// returns `Poll::Ready(None)`. This indicates that the stream has reached an end. -/// However, if a new item is inserted *after*, `poll` will once again start -/// returning items or `Poll::Pending`. -/// -/// Items are returned ordered by their expirations. Items that are configured -/// to expire first will be returned first. There are no ordering guarantees -/// for items configured to expire at the same instant. Also note that delays are -/// rounded to the closest millisecond. -/// -/// # Implementation -/// -/// The [`DelayQueue`] is backed by a separate instance of a timer wheel similar to that used internally -/// by Tokio's standalone timer utilities such as [`sleep`]. Because of this, it offers the same -/// performance and scalability benefits. -/// -/// State associated with each entry is stored in a [`slab`]. This amortizes the cost of allocation, -/// and allows reuse of the memory allocated for expired entires. -/// -/// Capacity can be checked using [`capacity`] and allocated preemptively by using -/// the [`reserve`] method. -/// -/// # Usage -/// -/// Using `DelayQueue` to manage cache entries. -/// -/// ```rust,no_run -/// use tokio_util::time::{DelayQueue, delay_queue}; -/// -/// use futures::ready; -/// use std::collections::HashMap; -/// use std::task::{Context, Poll}; -/// use std::time::Duration; -/// # type CacheKey = String; -/// # type Value = String; -/// -/// struct Cache { -/// entries: HashMap, -/// expirations: DelayQueue, -/// } -/// -/// const TTL_SECS: u64 = 30; -/// -/// impl Cache { -/// fn insert(&mut self, key: CacheKey, value: Value) { -/// let delay = self.expirations -/// .insert(key.clone(), Duration::from_secs(TTL_SECS)); -/// -/// self.entries.insert(key, (value, delay)); -/// } -/// -/// fn get(&self, key: &CacheKey) -> Option<&Value> { -/// self.entries.get(key) -/// .map(|&(ref v, _)| v) -/// } -/// -/// fn remove(&mut self, key: &CacheKey) { -/// if let Some((_, cache_key)) = self.entries.remove(key) { -/// self.expirations.remove(&cache_key); -/// } -/// } -/// -/// fn poll_purge(&mut self, cx: &mut Context<'_>) -> Poll<()> { -/// while let Some(entry) = ready!(self.expirations.poll_expired(cx)) { -/// self.entries.remove(entry.get_ref()); -/// } -/// -/// Poll::Ready(()) -/// } -/// } -/// ``` -/// -/// [`insert`]: method@Self::insert -/// [`insert_at`]: method@Self::insert_at -/// [`Key`]: struct@Key -/// [`Stream`]: https://docs.rs/futures/0.1/futures/stream/trait.Stream.html -/// [`poll_expired`]: method@Self::poll_expired -/// [`Stream::poll_expired`]: method@Self::poll_expired -/// [`DelayQueue`]: struct@DelayQueue -/// [`sleep`]: fn@tokio::time::sleep -/// [`slab`]: slab -/// [`capacity`]: method@Self::capacity -/// [`reserve`]: method@Self::reserve -#[derive(Debug)] -pub struct DelayQueue { - /// Stores data associated with entries - slab: SlabStorage, - - /// Lookup structure tracking all delays in the queue - wheel: Wheel>, - - /// Delays that were inserted when already expired. These cannot be stored - /// in the wheel - expired: Stack, - - /// Delay expiring when the *first* item in the queue expires - delay: Option>>, - - /// Wheel polling state - wheel_now: u64, - - /// Instant at which the timer starts - start: Instant, - - /// Waker that is invoked when we potentially need to reset the timer. - /// Because we lazily create the timer when the first entry is created, we - /// need to awaken any poller that polled us before that point. - waker: Option, -} - -#[derive(Default)] -struct SlabStorage { - inner: Slab>, - - // A `compact` call requires a re-mapping of the `Key`s that were changed - // during the `compact` call of the `slab`. Since the keys that were given out - // cannot be changed retroactively we need to keep track of these re-mappings. - // The keys of `key_map` correspond to the old keys that were given out and - // the values to the `Key`s that were re-mapped by the `compact` call. - key_map: HashMap, - - // Index used to create new keys to hand out. - next_key_index: usize, - - // Whether `compact` has been called, necessary in order to decide whether - // to include keys in `key_map`. - compact_called: bool, -} - -impl SlabStorage { - pub(crate) fn with_capacity(capacity: usize) -> SlabStorage { - SlabStorage { - inner: Slab::with_capacity(capacity), - key_map: HashMap::new(), - next_key_index: 0, - compact_called: false, - } - } - - // Inserts data into the inner slab and re-maps keys if necessary - pub(crate) fn insert(&mut self, val: Data) -> Key { - let mut key = KeyInternal::new(self.inner.insert(val)); - let key_contained = self.key_map.contains_key(&key.into()); - - if key_contained { - // It's possible that a `compact` call creates capacity in `self.inner` in - // such a way that a `self.inner.insert` call creates a `key` which was - // previously given out during an `insert` call prior to the `compact` call. - // If `key` is contained in `self.key_map`, we have encountered this exact situation, - // We need to create a new key `key_to_give_out` and include the relation - // `key_to_give_out` -> `key` in `self.key_map`. - let key_to_give_out = self.create_new_key(); - assert!(!self.key_map.contains_key(&key_to_give_out.into())); - self.key_map.insert(key_to_give_out.into(), key); - key = key_to_give_out; - } else if self.compact_called { - // Include an identity mapping in `self.key_map` in order to allow us to - // panic if a key that was handed out is removed more than once. - self.key_map.insert(key.into(), key); - } - - key.into() - } - - // Re-map the key in case compact was previously called. - // Note: Since we include identity mappings in key_map after compact was called, - // we have information about all keys that were handed out. In the case in which - // compact was called and we try to remove a Key that was previously removed - // we can detect invalid keys if no key is found in `key_map`. This is necessary - // in order to prevent situations in which a previously removed key - // corresponds to a re-mapped key internally and which would then be incorrectly - // removed from the slab. - // - // Example to illuminate this problem: - // - // Let's assume our `key_map` is {1 -> 2, 2 -> 1} and we call remove(1). If we - // were to remove 1 again, we would not find it inside `key_map` anymore. - // If we were to imply from this that no re-mapping was necessary, we would - // incorrectly remove 1 from `self.slab.inner`, which corresponds to the - // handed-out key 2. - pub(crate) fn remove(&mut self, key: &Key) -> Data { - let remapped_key = if self.compact_called { - match self.key_map.remove(key) { - Some(key_internal) => key_internal, - None => panic!("invalid key"), - } - } else { - (*key).into() - }; - - self.inner.remove(remapped_key.index) - } - - pub(crate) fn shrink_to_fit(&mut self) { - self.inner.shrink_to_fit(); - self.key_map.shrink_to_fit(); - } - - pub(crate) fn compact(&mut self) { - if !self.compact_called { - for (key, _) in self.inner.iter() { - self.key_map.insert(Key::new(key), KeyInternal::new(key)); - } - } - - let mut remapping = HashMap::new(); - self.inner.compact(|_, from, to| { - remapping.insert(from, to); - true - }); - - // At this point `key_map` contains a mapping for every element. - for internal_key in self.key_map.values_mut() { - if let Some(new_internal_key) = remapping.get(&internal_key.index) { - *internal_key = KeyInternal::new(*new_internal_key); - } - } - - if self.key_map.capacity() > 2 * self.key_map.len() { - self.key_map.shrink_to_fit(); - } - - self.compact_called = true; - } - - // Tries to re-map a `Key` that was given out to the user to its - // corresponding internal key. - fn remap_key(&self, key: &Key) -> Option { - let key_map = &self.key_map; - if self.compact_called { - key_map.get(&*key).copied() - } else { - Some((*key).into()) - } - } - - fn create_new_key(&mut self) -> KeyInternal { - while self.key_map.contains_key(&Key::new(self.next_key_index)) { - self.next_key_index = self.next_key_index.wrapping_add(1); - } - - KeyInternal::new(self.next_key_index) - } - - pub(crate) fn len(&self) -> usize { - self.inner.len() - } - - pub(crate) fn capacity(&self) -> usize { - self.inner.capacity() - } - - pub(crate) fn clear(&mut self) { - self.inner.clear(); - self.key_map.clear(); - self.compact_called = false; - } - - pub(crate) fn reserve(&mut self, additional: usize) { - self.inner.reserve(additional); - - if self.compact_called { - self.key_map.reserve(additional); - } - } - - pub(crate) fn is_empty(&self) -> bool { - self.inner.is_empty() - } - - pub(crate) fn contains(&self, key: &Key) -> bool { - let remapped_key = self.remap_key(key); - - match remapped_key { - Some(internal_key) => self.inner.contains(internal_key.index), - None => false, - } - } -} - -impl fmt::Debug for SlabStorage -where - T: fmt::Debug, -{ - fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { - if fmt.alternate() { - fmt.debug_map().entries(self.inner.iter()).finish() - } else { - fmt.debug_struct("Slab") - .field("len", &self.len()) - .field("cap", &self.capacity()) - .finish() - } - } -} - -impl Index for SlabStorage { - type Output = Data; - - fn index(&self, key: Key) -> &Self::Output { - let remapped_key = self.remap_key(&key); - - match remapped_key { - Some(internal_key) => &self.inner[internal_key.index], - None => panic!("Invalid index {}", key.index), - } - } -} - -impl IndexMut for SlabStorage { - fn index_mut(&mut self, key: Key) -> &mut Data { - let remapped_key = self.remap_key(&key); - - match remapped_key { - Some(internal_key) => &mut self.inner[internal_key.index], - None => panic!("Invalid index {}", key.index), - } - } -} - -/// An entry in `DelayQueue` that has expired and been removed. -/// -/// Values are returned by [`DelayQueue::poll_expired`]. -/// -/// [`DelayQueue::poll_expired`]: method@DelayQueue::poll_expired -#[derive(Debug)] -pub struct Expired { - /// The data stored in the queue - data: T, - - /// The expiration time - deadline: Instant, - - /// The key associated with the entry - key: Key, -} - -/// Token to a value stored in a `DelayQueue`. -/// -/// Instances of `Key` are returned by [`DelayQueue::insert`]. See [`DelayQueue`] -/// documentation for more details. -/// -/// [`DelayQueue`]: struct@DelayQueue -/// [`DelayQueue::insert`]: method@DelayQueue::insert -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] -pub struct Key { - index: usize, -} - -// Whereas `Key` is given out to users that use `DelayQueue`, internally we use -// `KeyInternal` as the key type in order to make the logic of mapping between keys -// as a result of `compact` calls clearer. -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] -struct KeyInternal { - index: usize, -} - -#[derive(Debug)] -struct Stack { - /// Head of the stack - head: Option, - _p: PhantomData T>, -} - -#[derive(Debug)] -struct Data { - /// The data being stored in the queue and will be returned at the requested - /// instant. - inner: T, - - /// The instant at which the item is returned. - when: u64, - - /// Set to true when stored in the `expired` queue - expired: bool, - - /// Next entry in the stack - next: Option, - - /// Previous entry in the stack - prev: Option, -} - -/// Maximum number of entries the queue can handle -const MAX_ENTRIES: usize = (1 << 30) - 1; - -impl DelayQueue { - /// Creates a new, empty, `DelayQueue`. - /// - /// The queue will not allocate storage until items are inserted into it. - /// - /// # Examples - /// - /// ```rust - /// # use tokio_util::time::DelayQueue; - /// let delay_queue: DelayQueue = DelayQueue::new(); - /// ``` - pub fn new() -> DelayQueue { - DelayQueue::with_capacity(0) - } - - /// Creates a new, empty, `DelayQueue` with the specified capacity. - /// - /// The queue will be able to hold at least `capacity` elements without - /// reallocating. If `capacity` is 0, the queue will not allocate for - /// storage. - /// - /// # Examples - /// - /// ```rust - /// # use tokio_util::time::DelayQueue; - /// # use std::time::Duration; - /// - /// # #[tokio::main] - /// # async fn main() { - /// let mut delay_queue = DelayQueue::with_capacity(10); - /// - /// // These insertions are done without further allocation - /// for i in 0..10 { - /// delay_queue.insert(i, Duration::from_secs(i)); - /// } - /// - /// // This will make the queue allocate additional storage - /// delay_queue.insert(11, Duration::from_secs(11)); - /// # } - /// ``` - pub fn with_capacity(capacity: usize) -> DelayQueue { - DelayQueue { - wheel: Wheel::new(), - slab: SlabStorage::with_capacity(capacity), - expired: Stack::default(), - delay: None, - wheel_now: 0, - start: Instant::now(), - waker: None, - } - } - - /// Inserts `value` into the queue set to expire at a specific instant in - /// time. - /// - /// This function is identical to `insert`, but takes an `Instant` instead - /// of a `Duration`. - /// - /// `value` is stored in the queue until `when` is reached. At which point, - /// `value` will be returned from [`poll_expired`]. If `when` has already been - /// reached, then `value` is immediately made available to poll. - /// - /// The return value represents the insertion and is used as an argument to - /// [`remove`] and [`reset`]. Note that [`Key`] is a token and is reused once - /// `value` is removed from the queue either by calling [`poll_expired`] after - /// `when` is reached or by calling [`remove`]. At this point, the caller - /// must take care to not use the returned [`Key`] again as it may reference - /// a different item in the queue. - /// - /// See [type] level documentation for more details. - /// - /// # Panics - /// - /// This function panics if `when` is too far in the future. - /// - /// # Examples - /// - /// Basic usage - /// - /// ```rust - /// use tokio::time::{Duration, Instant}; - /// use tokio_util::time::DelayQueue; - /// - /// # #[tokio::main] - /// # async fn main() { - /// let mut delay_queue = DelayQueue::new(); - /// let key = delay_queue.insert_at( - /// "foo", Instant::now() + Duration::from_secs(5)); - /// - /// // Remove the entry - /// let item = delay_queue.remove(&key); - /// assert_eq!(*item.get_ref(), "foo"); - /// # } - /// ``` - /// - /// [`poll_expired`]: method@Self::poll_expired - /// [`remove`]: method@Self::remove - /// [`reset`]: method@Self::reset - /// [`Key`]: struct@Key - /// [type]: # - #[track_caller] - pub fn insert_at(&mut self, value: T, when: Instant) -> Key { - assert!(self.slab.len() < MAX_ENTRIES, "max entries exceeded"); - - // Normalize the deadline. Values cannot be set to expire in the past. - let when = self.normalize_deadline(when); - - // Insert the value in the store - let key = self.slab.insert(Data { - inner: value, - when, - expired: false, - next: None, - prev: None, - }); - - self.insert_idx(when, key); - - // Set a new delay if the current's deadline is later than the one of the new item - let should_set_delay = if let Some(ref delay) = self.delay { - let current_exp = self.normalize_deadline(delay.deadline()); - current_exp > when - } else { - true - }; - - if should_set_delay { - if let Some(waker) = self.waker.take() { - waker.wake(); - } - - let delay_time = self.start + Duration::from_millis(when); - if let Some(ref mut delay) = &mut self.delay { - delay.as_mut().reset_at(delay_time); - } else { - self.delay = Some(Box::pin(sleep_until(delay_time))); - } - } - - key - } - - /// Attempts to pull out the next value of the delay queue, registering the - /// current task for wakeup if the value is not yet available, and returning - /// `None` if the queue is exhausted. - pub fn poll_expired(&mut self, cx: &mut task::Context<'_>) -> Poll>> { - if !self - .waker - .as_ref() - .map(|w| w.will_wake(cx.waker())) - .unwrap_or(false) - { - self.waker = Some(cx.waker().clone()); - } - - let item = ready!(self.poll_idx(cx)); - Poll::Ready(item.map(|key| { - let data = self.slab.remove(&key); - debug_assert!(data.next.is_none()); - debug_assert!(data.prev.is_none()); - - Expired { - key, - data: data.inner, - deadline: self.start + Duration::from_millis(data.when), - } - })) - } - - /// Inserts `value` into the queue set to expire after the requested duration - /// elapses. - /// - /// This function is identical to `insert_at`, but takes a `Duration` - /// instead of an `Instant`. - /// - /// `value` is stored in the queue until `timeout` duration has - /// elapsed after `insert` was called. At that point, `value` will - /// be returned from [`poll_expired`]. If `timeout` is a `Duration` of - /// zero, then `value` is immediately made available to poll. - /// - /// The return value represents the insertion and is used as an - /// argument to [`remove`] and [`reset`]. Note that [`Key`] is a - /// token and is reused once `value` is removed from the queue - /// either by calling [`poll_expired`] after `timeout` has elapsed - /// or by calling [`remove`]. At this point, the caller must not - /// use the returned [`Key`] again as it may reference a different - /// item in the queue. - /// - /// See [type] level documentation for more details. - /// - /// # Panics - /// - /// This function panics if `timeout` is greater than the maximum - /// duration supported by the timer in the current `Runtime`. - /// - /// # Examples - /// - /// Basic usage - /// - /// ```rust - /// use tokio_util::time::DelayQueue; - /// use std::time::Duration; - /// - /// # #[tokio::main] - /// # async fn main() { - /// let mut delay_queue = DelayQueue::new(); - /// let key = delay_queue.insert("foo", Duration::from_secs(5)); - /// - /// // Remove the entry - /// let item = delay_queue.remove(&key); - /// assert_eq!(*item.get_ref(), "foo"); - /// # } - /// ``` - /// - /// [`poll_expired`]: method@Self::poll_expired - /// [`remove`]: method@Self::remove - /// [`reset`]: method@Self::reset - /// [`Key`]: struct@Key - /// [type]: # - #[track_caller] - pub fn insert(&mut self, value: T, timeout: Duration) -> Key { - self.insert_at(value, Instant::now() + timeout) - } - - #[track_caller] - fn insert_idx(&mut self, when: u64, key: Key) { - use self::wheel::{InsertError, Stack}; - - // Register the deadline with the timer wheel - match self.wheel.insert(when, key, &mut self.slab) { - Ok(_) => {} - Err((_, InsertError::Elapsed)) => { - self.slab[key].expired = true; - // The delay is already expired, store it in the expired queue - self.expired.push(key, &mut self.slab); - } - Err((_, err)) => panic!("invalid deadline; err={:?}", err), - } - } - - /// Removes the key from the expired queue or the timer wheel - /// depending on its expiration status. - /// - /// # Panics - /// - /// Panics if the key is not contained in the expired queue or the wheel. - #[track_caller] - fn remove_key(&mut self, key: &Key) { - use crate::wasm_delay_queue::wheel::Stack; - - // Special case the `expired` queue - if self.slab[*key].expired { - self.expired.remove(key, &mut self.slab); - } else { - self.wheel.remove(key, &mut self.slab); - } - } - - /// Removes the item associated with `key` from the queue. - /// - /// There must be an item associated with `key`. The function returns the - /// removed item as well as the `Instant` at which it will the delay will - /// have expired. - /// - /// # Panics - /// - /// The function panics if `key` is not contained by the queue. - /// - /// # Examples - /// - /// Basic usage - /// - /// ```rust - /// use tokio_util::time::DelayQueue; - /// use std::time::Duration; - /// - /// # #[tokio::main] - /// # async fn main() { - /// let mut delay_queue = DelayQueue::new(); - /// let key = delay_queue.insert("foo", Duration::from_secs(5)); - /// - /// // Remove the entry - /// let item = delay_queue.remove(&key); - /// assert_eq!(*item.get_ref(), "foo"); - /// # } - /// ``` - #[track_caller] - pub fn remove(&mut self, key: &Key) -> Expired { - let prev_deadline = self.next_deadline(); - - self.remove_key(key); - let data = self.slab.remove(key); - - let next_deadline = self.next_deadline(); - if prev_deadline != next_deadline { - match (next_deadline, &mut self.delay) { - (None, _) => self.delay = None, - (Some(deadline), Some(delay)) => delay.as_mut().reset_at(deadline), - (Some(deadline), None) => self.delay = Some(Box::pin(sleep_until(deadline))), - } - } - - Expired { - key: Key::new(key.index), - data: data.inner, - deadline: self.start + Duration::from_millis(data.when), - } - } - - /// Sets the delay of the item associated with `key` to expire at `when`. - /// - /// This function is identical to `reset` but takes an `Instant` instead of - /// a `Duration`. - /// - /// The item remains in the queue but the delay is set to expire at `when`. - /// If `when` is in the past, then the item is immediately made available to - /// the caller. - /// - /// # Panics - /// - /// This function panics if `when` is too far in the future or if `key` is - /// not contained by the queue. - /// - /// # Examples - /// - /// Basic usage - /// - /// ```rust - /// use tokio::time::{Duration, Instant}; - /// use tokio_util::time::DelayQueue; - /// - /// # #[tokio::main] - /// # async fn main() { - /// let mut delay_queue = DelayQueue::new(); - /// let key = delay_queue.insert("foo", Duration::from_secs(5)); - /// - /// // "foo" is scheduled to be returned in 5 seconds - /// - /// delay_queue.reset_at(&key, Instant::now() + Duration::from_secs(10)); - /// - /// // "foo" is now scheduled to be returned in 10 seconds - /// # } - /// ``` - #[track_caller] - pub fn reset_at(&mut self, key: &Key, when: Instant) { - self.remove_key(key); - - // Normalize the deadline. Values cannot be set to expire in the past. - let when = self.normalize_deadline(when); - - self.slab[*key].when = when; - self.slab[*key].expired = false; - - self.insert_idx(when, *key); - - let next_deadline = self.next_deadline(); - if let (Some(ref mut delay), Some(deadline)) = (&mut self.delay, next_deadline) { - // This should awaken us if necessary (ie, if already expired) - delay.as_mut().reset_at(deadline); - } - } - - /// Shrink the capacity of the slab, which `DelayQueue` uses internally for storage allocation. - /// This function is not guaranteed to, and in most cases, won't decrease the capacity of the slab - /// to the number of elements still contained in it, because elements cannot be moved to a different - /// index. To decrease the capacity to the size of the slab use [`compact`]. - /// - /// This function can take O(n) time even when the capacity cannot be reduced or the allocation is - /// shrunk in place. Repeated calls run in O(1) though. - /// - /// [`compact`]: method@Self::compact - pub fn shrink_to_fit(&mut self) { - self.slab.shrink_to_fit(); - } - - /// Shrink the capacity of the slab, which `DelayQueue` uses internally for storage allocation, - /// to the number of elements that are contained in it. - /// - /// This methods runs in O(n). - /// - /// # Examples - /// - /// Basic usage - /// - /// ```rust - /// use tokio_util::time::DelayQueue; - /// use std::time::Duration; - /// - /// # #[tokio::main] - /// # async fn main() { - /// let mut delay_queue = DelayQueue::with_capacity(10); - /// - /// let key1 = delay_queue.insert(5, Duration::from_secs(5)); - /// let key2 = delay_queue.insert(10, Duration::from_secs(10)); - /// let key3 = delay_queue.insert(15, Duration::from_secs(15)); - /// - /// delay_queue.remove(&key2); - /// - /// delay_queue.compact(); - /// assert_eq!(delay_queue.capacity(), 2); - /// # } - /// ``` - pub fn compact(&mut self) { - self.slab.compact(); - } - - /// Returns the next time to poll as determined by the wheel - fn next_deadline(&mut self) -> Option { - self.wheel - .poll_at() - .map(|poll_at| self.start + Duration::from_millis(poll_at)) - } - - /// Sets the delay of the item associated with `key` to expire after - /// `timeout`. - /// - /// This function is identical to `reset_at` but takes a `Duration` instead - /// of an `Instant`. - /// - /// The item remains in the queue but the delay is set to expire after - /// `timeout`. If `timeout` is zero, then the item is immediately made - /// available to the caller. - /// - /// # Panics - /// - /// This function panics if `timeout` is greater than the maximum supported - /// duration or if `key` is not contained by the queue. - /// - /// # Examples - /// - /// Basic usage - /// - /// ```rust - /// use tokio_util::time::DelayQueue; - /// use std::time::Duration; - /// - /// # #[tokio::main] - /// # async fn main() { - /// let mut delay_queue = DelayQueue::new(); - /// let key = delay_queue.insert("foo", Duration::from_secs(5)); - /// - /// // "foo" is scheduled to be returned in 5 seconds - /// - /// delay_queue.reset(&key, Duration::from_secs(10)); - /// - /// // "foo"is now scheduled to be returned in 10 seconds - /// # } - /// ``` - #[track_caller] - pub fn reset(&mut self, key: &Key, timeout: Duration) { - self.reset_at(key, Instant::now() + timeout); - } - - /// Clears the queue, removing all items. - /// - /// After calling `clear`, [`poll_expired`] will return `Ok(Ready(None))`. - /// - /// Note that this method has no effect on the allocated capacity. - /// - /// [`poll_expired`]: method@Self::poll_expired - /// - /// # Examples - /// - /// ```rust - /// use tokio_util::time::DelayQueue; - /// use std::time::Duration; - /// - /// # #[tokio::main] - /// # async fn main() { - /// let mut delay_queue = DelayQueue::new(); - /// - /// delay_queue.insert("foo", Duration::from_secs(5)); - /// - /// assert!(!delay_queue.is_empty()); - /// - /// delay_queue.clear(); - /// - /// assert!(delay_queue.is_empty()); - /// # } - /// ``` - pub fn clear(&mut self) { - self.slab.clear(); - self.expired = Stack::default(); - self.wheel = Wheel::new(); - self.delay = None; - } - - /// Returns the number of elements the queue can hold without reallocating. - /// - /// # Examples - /// - /// ```rust - /// use tokio_util::time::DelayQueue; - /// - /// let delay_queue: DelayQueue = DelayQueue::with_capacity(10); - /// assert_eq!(delay_queue.capacity(), 10); - /// ``` - pub fn capacity(&self) -> usize { - self.slab.capacity() - } - - /// Returns the number of elements currently in the queue. - /// - /// # Examples - /// - /// ```rust - /// use tokio_util::time::DelayQueue; - /// use std::time::Duration; - /// - /// # #[tokio::main] - /// # async fn main() { - /// let mut delay_queue: DelayQueue = DelayQueue::with_capacity(10); - /// assert_eq!(delay_queue.len(), 0); - /// delay_queue.insert(3, Duration::from_secs(5)); - /// assert_eq!(delay_queue.len(), 1); - /// # } - /// ``` - pub fn len(&self) -> usize { - self.slab.len() - } - - /// Reserves capacity for at least `additional` more items to be queued - /// without allocating. - /// - /// `reserve` does nothing if the queue already has sufficient capacity for - /// `additional` more values. If more capacity is required, a new segment of - /// memory will be allocated and all existing values will be copied into it. - /// As such, if the queue is already very large, a call to `reserve` can end - /// up being expensive. - /// - /// The queue may reserve more than `additional` extra space in order to - /// avoid frequent reallocations. - /// - /// # Panics - /// - /// Panics if the new capacity exceeds the maximum number of entries the - /// queue can contain. - /// - /// # Examples - /// - /// ``` - /// use tokio_util::time::DelayQueue; - /// use std::time::Duration; - /// - /// # #[tokio::main] - /// # async fn main() { - /// let mut delay_queue = DelayQueue::new(); - /// - /// delay_queue.insert("hello", Duration::from_secs(10)); - /// delay_queue.reserve(10); - /// - /// assert!(delay_queue.capacity() >= 11); - /// # } - /// ``` - #[track_caller] - pub fn reserve(&mut self, additional: usize) { - assert!( - self.slab.capacity() + additional <= MAX_ENTRIES, - "max queue capacity exceeded" - ); - self.slab.reserve(additional); - } - - /// Returns `true` if there are no items in the queue. - /// - /// Note that this function returns `false` even if all items have not yet - /// expired and a call to `poll` will return `Poll::Pending`. - /// - /// # Examples - /// - /// ``` - /// use tokio_util::time::DelayQueue; - /// use std::time::Duration; - /// - /// # #[tokio::main] - /// # async fn main() { - /// let mut delay_queue = DelayQueue::new(); - /// assert!(delay_queue.is_empty()); - /// - /// delay_queue.insert("hello", Duration::from_secs(5)); - /// assert!(!delay_queue.is_empty()); - /// # } - /// ``` - pub fn is_empty(&self) -> bool { - self.slab.is_empty() - } - - /// Polls the queue, returning the index of the next slot in the slab that - /// should be returned. - /// - /// A slot should be returned when the associated deadline has been reached. - fn poll_idx(&mut self, cx: &mut task::Context<'_>) -> Poll> { - use self::wheel::Stack; - - let expired = self.expired.pop(&mut self.slab); - - if expired.is_some() { - return Poll::Ready(expired); - } - - loop { - if let Some(ref mut delay) = self.delay { - ready!(Pin::new(&mut *delay).poll(cx)).ok(); - - let now = crate::wasm_delay_queue::ms( - delay.deadline() - self.start, - crate::wasm_delay_queue::Round::Down, - ); - - self.wheel_now = now; - } - - // We poll the wheel to get the next value out before finding the next deadline. - let wheel_idx = self.wheel.poll(self.wheel_now, &mut self.slab); - - self.delay = self.next_deadline().map(|when| Box::pin(sleep_until(when))); - - if let Some(idx) = wheel_idx { - return Poll::Ready(Some(idx)); - } - - if self.delay.is_none() { - return Poll::Ready(None); - } - } - } - - fn normalize_deadline(&self, when: Instant) -> u64 { - let when = if when < self.start { - 0 - } else { - crate::wasm_delay_queue::ms(when - self.start, crate::wasm_delay_queue::Round::Up) - }; - - cmp::max(when, self.wheel.elapsed()) - } -} - -// We never put `T` in a `Pin`... -impl Unpin for DelayQueue {} - -impl Default for DelayQueue { - fn default() -> DelayQueue { - DelayQueue::new() - } -} - -impl futures_core::Stream for DelayQueue { - // DelayQueue seems much more specific, where a user may care that it - // has reached capacity, so return those errors instead of panicking. - type Item = Expired; - - fn poll_next(self: Pin<&mut Self>, cx: &mut task::Context<'_>) -> Poll> { - DelayQueue::poll_expired(self.get_mut(), cx) - } -} - -impl wheel::Stack for Stack { - type Owned = Key; - type Borrowed = Key; - type Store = SlabStorage; - - fn is_empty(&self) -> bool { - self.head.is_none() - } - - fn push(&mut self, item: Self::Owned, store: &mut Self::Store) { - // Ensure the entry is not already in a stack. - debug_assert!(store[item].next.is_none()); - debug_assert!(store[item].prev.is_none()); - - // Remove the old head entry - let old = self.head.take(); - - if let Some(idx) = old { - store[idx].prev = Some(item); - } - - store[item].next = old; - self.head = Some(item); - } - - fn pop(&mut self, store: &mut Self::Store) -> Option { - if let Some(key) = self.head { - self.head = store[key].next; - - if let Some(idx) = self.head { - store[idx].prev = None; - } - - store[key].next = None; - debug_assert!(store[key].prev.is_none()); - - Some(key) - } else { - None - } - } - - #[track_caller] - fn remove(&mut self, item: &Self::Borrowed, store: &mut Self::Store) { - let key = *item; - assert!(store.contains(item)); - - // Ensure that the entry is in fact contained by the stack - debug_assert!({ - // This walks the full linked list even if an entry is found. - let mut next = self.head; - let mut contains = false; - - while let Some(idx) = next { - let data = &store[idx]; - - if idx == *item { - debug_assert!(!contains); - contains = true; - } - - next = data.next; - } - - contains - }); - - if let Some(next) = store[key].next { - store[next].prev = store[key].prev; - } - - if let Some(prev) = store[key].prev { - store[prev].next = store[key].next; - } else { - self.head = store[key].next; - } - - store[key].next = None; - store[key].prev = None; - } - - fn when(item: &Self::Borrowed, store: &Self::Store) -> u64 { - store[*item].when - } -} - -impl Default for Stack { - fn default() -> Stack { - Stack { - head: None, - _p: PhantomData, - } - } -} - -impl Key { - pub(crate) fn new(index: usize) -> Key { - Key { index } - } -} - -impl KeyInternal { - pub(crate) fn new(index: usize) -> KeyInternal { - KeyInternal { index } - } -} - -impl From for KeyInternal { - fn from(item: Key) -> Self { - KeyInternal::new(item.index) - } -} - -impl From for Key { - fn from(item: KeyInternal) -> Self { - Key::new(item.index) - } -} - -impl Expired { - /// Returns a reference to the inner value. - pub fn get_ref(&self) -> &T { - &self.data - } - - /// Returns a mutable reference to the inner value. - pub fn get_mut(&mut self) -> &mut T { - &mut self.data - } - - /// Consumes `self` and returns the inner value. - pub fn into_inner(self) -> T { - self.data - } - - /// Returns the deadline that the expiration was set to. - pub fn deadline(&self) -> Instant { - self.deadline - } - - /// Returns the key that the expiration is indexed by. - pub fn key(&self) -> Key { - self.key - } -} diff --git a/common/nonexhaustive-delayqueue/src/wasm_delay_queue/mod.rs b/common/nonexhaustive-delayqueue/src/wasm_delay_queue/mod.rs deleted file mode 100644 index fa8b62f561..0000000000 --- a/common/nonexhaustive-delayqueue/src/wasm_delay_queue/mod.rs +++ /dev/null @@ -1,42 +0,0 @@ -use std::time::Duration; - -mod wheel; - -pub mod delay_queue; - -#[doc(inline)] -pub use delay_queue::DelayQueue; - -// ===== Internal utils ===== - -enum Round { - Up, - Down, -} - -/// Convert a `Duration` to milliseconds, rounding up and saturating at -/// `u64::MAX`. -/// -/// The saturating is fine because `u64::MAX` milliseconds are still many -/// million years. -#[inline] -fn ms(duration: Duration, round: Round) -> u64 { - const NANOS_PER_MILLI: u32 = 1_000_000; - const MILLIS_PER_SEC: u64 = 1_000; - - // Round up. - let millis = match round { - Round::Up => (duration.subsec_nanos() + NANOS_PER_MILLI - 1) / NANOS_PER_MILLI, - Round::Down => duration.subsec_millis(), - }; - - duration - .as_secs() - .saturating_mul(MILLIS_PER_SEC) - .saturating_add(u64::from(millis)) -} - -#[inline] -fn sleep_until(deadline: wasm_timer::Instant) -> wasm_timer::Delay { - wasm_timer::Delay::new_at(deadline) -} diff --git a/common/nonexhaustive-delayqueue/src/wasm_delay_queue/wheel/level.rs b/common/nonexhaustive-delayqueue/src/wasm_delay_queue/wheel/level.rs deleted file mode 100644 index f84d80e265..0000000000 --- a/common/nonexhaustive-delayqueue/src/wasm_delay_queue/wheel/level.rs +++ /dev/null @@ -1,253 +0,0 @@ -use crate::wasm_delay_queue::wheel::Stack; - -use std::fmt; - -/// Wheel for a single level in the timer. This wheel contains 64 slots. -pub(crate) struct Level { - level: usize, - - /// Bit field tracking which slots currently contain entries. - /// - /// Using a bit field to track slots that contain entries allows avoiding a - /// scan to find entries. This field is updated when entries are added or - /// removed from a slot. - /// - /// The least-significant bit represents slot zero. - occupied: u64, - - /// Slots - slot: [T; LEVEL_MULT], -} - -/// Indicates when a slot must be processed next. -#[derive(Debug)] -pub(crate) struct Expiration { - /// The level containing the slot. - pub(crate) level: usize, - - /// The slot index. - pub(crate) slot: usize, - - /// The instant at which the slot needs to be processed. - pub(crate) deadline: u64, -} - -/// Level multiplier. -/// -/// Being a power of 2 is very important. -const LEVEL_MULT: usize = 64; - -impl Level { - pub(crate) fn new(level: usize) -> Level { - // Rust's derived implementations for arrays require that the value - // contained by the array be `Copy`. So, here we have to manually - // initialize every single slot. - macro_rules! s { - () => { - T::default() - }; - } - - Level { - level, - occupied: 0, - slot: [ - // It does not look like the necessary traits are - // derived for [T; 64]. - s!(), - s!(), - s!(), - s!(), - s!(), - s!(), - s!(), - s!(), - s!(), - s!(), - s!(), - s!(), - s!(), - s!(), - s!(), - s!(), - s!(), - s!(), - s!(), - s!(), - s!(), - s!(), - s!(), - s!(), - s!(), - s!(), - s!(), - s!(), - s!(), - s!(), - s!(), - s!(), - s!(), - s!(), - s!(), - s!(), - s!(), - s!(), - s!(), - s!(), - s!(), - s!(), - s!(), - s!(), - s!(), - s!(), - s!(), - s!(), - s!(), - s!(), - s!(), - s!(), - s!(), - s!(), - s!(), - s!(), - s!(), - s!(), - s!(), - s!(), - s!(), - s!(), - s!(), - s!(), - ], - } - } - - /// Finds the slot that needs to be processed next and returns the slot and - /// `Instant` at which this slot must be processed. - pub(crate) fn next_expiration(&self, now: u64) -> Option { - // Use the `occupied` bit field to get the index of the next slot that - // needs to be processed. - let slot = match self.next_occupied_slot(now) { - Some(slot) => slot, - None => return None, - }; - - // From the slot index, calculate the `Instant` at which it needs to be - // processed. This value *must* be in the future with respect to `now`. - - let level_range = level_range(self.level); - let slot_range = slot_range(self.level); - - // TODO: This can probably be simplified w/ power of 2 math - let level_start = now - (now % level_range); - let deadline = level_start + slot as u64 * slot_range; - - debug_assert!( - deadline >= now, - "deadline={}; now={}; level={}; slot={}; occupied={:b}", - deadline, - now, - self.level, - slot, - self.occupied - ); - - Some(Expiration { - level: self.level, - slot, - deadline, - }) - } - - fn next_occupied_slot(&self, now: u64) -> Option { - if self.occupied == 0 { - return None; - } - - // Get the slot for now using Maths - let now_slot = (now / slot_range(self.level)) as usize; - let occupied = self.occupied.rotate_right(now_slot as u32); - let zeros = occupied.trailing_zeros() as usize; - let slot = (zeros + now_slot) % 64; - - Some(slot) - } - - pub(crate) fn add_entry(&mut self, when: u64, item: T::Owned, store: &mut T::Store) { - let slot = slot_for(when, self.level); - - self.slot[slot].push(item, store); - self.occupied |= occupied_bit(slot); - } - - pub(crate) fn remove_entry(&mut self, when: u64, item: &T::Borrowed, store: &mut T::Store) { - let slot = slot_for(when, self.level); - - self.slot[slot].remove(item, store); - - if self.slot[slot].is_empty() { - // The bit is currently set - debug_assert!(self.occupied & occupied_bit(slot) != 0); - - // Unset the bit - self.occupied ^= occupied_bit(slot); - } - } - - pub(crate) fn pop_entry_slot(&mut self, slot: usize, store: &mut T::Store) -> Option { - let ret = self.slot[slot].pop(store); - - if ret.is_some() && self.slot[slot].is_empty() { - // The bit is currently set - debug_assert!(self.occupied & occupied_bit(slot) != 0); - - self.occupied ^= occupied_bit(slot); - } - - ret - } -} - -impl fmt::Debug for Level { - fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { - fmt.debug_struct("Level") - .field("occupied", &self.occupied) - .finish() - } -} - -fn occupied_bit(slot: usize) -> u64 { - 1 << slot -} - -fn slot_range(level: usize) -> u64 { - LEVEL_MULT.pow(level as u32) as u64 -} - -fn level_range(level: usize) -> u64 { - LEVEL_MULT as u64 * slot_range(level) -} - -/// Convert a duration (milliseconds) and a level to a slot position -fn slot_for(duration: u64, level: usize) -> usize { - ((duration >> (level * 6)) % LEVEL_MULT as u64) as usize -} - -#[cfg(all(test, not(loom)))] -mod test { - use super::*; - - #[test] - fn test_slot_for() { - for pos in 0..64 { - assert_eq!(pos as usize, slot_for(pos, 0)); - } - - for level in 1..5 { - for pos in level..64 { - let a = pos * 64_usize.pow(level as u32); - assert_eq!(pos as usize, slot_for(a as u64, level)); - } - } - } -} diff --git a/common/nonexhaustive-delayqueue/src/wasm_delay_queue/wheel/mod.rs b/common/nonexhaustive-delayqueue/src/wasm_delay_queue/wheel/mod.rs deleted file mode 100644 index ffa05ab71b..0000000000 --- a/common/nonexhaustive-delayqueue/src/wasm_delay_queue/wheel/mod.rs +++ /dev/null @@ -1,315 +0,0 @@ -mod level; -pub(crate) use self::level::Expiration; -use self::level::Level; - -mod stack; -pub(crate) use self::stack::Stack; - -use std::borrow::Borrow; -use std::fmt::Debug; -use std::usize; - -/// Timing wheel implementation. -/// -/// This type provides the hashed timing wheel implementation that backs `Timer` -/// and `DelayQueue`. -/// -/// The structure is generic over `T: Stack`. This allows handling timeout data -/// being stored on the heap or in a slab. In order to support the latter case, -/// the slab must be passed into each function allowing the implementation to -/// lookup timer entries. -/// -/// See `Timer` documentation for some implementation notes. -#[derive(Debug)] -pub(crate) struct Wheel { - /// The number of milliseconds elapsed since the wheel started. - elapsed: u64, - - /// Timer wheel. - /// - /// Levels: - /// - /// * 1 ms slots / 64 ms range - /// * 64 ms slots / ~ 4 sec range - /// * ~ 4 sec slots / ~ 4 min range - /// * ~ 4 min slots / ~ 4 hr range - /// * ~ 4 hr slots / ~ 12 day range - /// * ~ 12 day slots / ~ 2 yr range - levels: Vec>, -} - -/// Number of levels. Each level has 64 slots. By using 6 levels with 64 slots -/// each, the timer is able to track time up to 2 years into the future with a -/// precision of 1 millisecond. -const NUM_LEVELS: usize = 6; - -/// The maximum duration of a delay -const MAX_DURATION: u64 = (1 << (6 * NUM_LEVELS)) - 1; - -#[derive(Debug)] -pub(crate) enum InsertError { - Elapsed, - Invalid, -} - -impl Wheel -where - T: Stack, -{ - /// Create a new timing wheel - pub(crate) fn new() -> Wheel { - let levels = (0..NUM_LEVELS).map(Level::new).collect(); - - Wheel { elapsed: 0, levels } - } - - /// Return the number of milliseconds that have elapsed since the timing - /// wheel's creation. - pub(crate) fn elapsed(&self) -> u64 { - self.elapsed - } - - /// Insert an entry into the timing wheel. - /// - /// # Arguments - /// - /// * `when`: is the instant at which the entry should be fired. It is - /// represented as the number of milliseconds since the creation - /// of the timing wheel. - /// - /// * `item`: The item to insert into the wheel. - /// - /// * `store`: The slab or `()` when using heap storage. - /// - /// # Return - /// - /// Returns `Ok` when the item is successfully inserted, `Err` otherwise. - /// - /// `Err(Elapsed)` indicates that `when` represents an instant that has - /// already passed. In this case, the caller should fire the timeout - /// immediately. - /// - /// `Err(Invalid)` indicates an invalid `when` argument as been supplied. - pub(crate) fn insert( - &mut self, - when: u64, - item: T::Owned, - store: &mut T::Store, - ) -> Result<(), (T::Owned, InsertError)> { - if when <= self.elapsed { - return Err((item, InsertError::Elapsed)); - } else if when - self.elapsed > MAX_DURATION { - return Err((item, InsertError::Invalid)); - } - - // Get the level at which the entry should be stored - let level = self.level_for(when); - - self.levels[level].add_entry(when, item, store); - - debug_assert!({ - self.levels[level] - .next_expiration(self.elapsed) - .map(|e| e.deadline >= self.elapsed) - .unwrap_or(true) - }); - - Ok(()) - } - - /// Remove `item` from the timing wheel. - #[track_caller] - pub(crate) fn remove(&mut self, item: &T::Borrowed, store: &mut T::Store) { - let when = T::when(item, store); - - assert!( - self.elapsed <= when, - "elapsed={}; when={}", - self.elapsed, - when - ); - - let level = self.level_for(when); - - self.levels[level].remove_entry(when, item, store); - } - - /// Instant at which to poll - pub(crate) fn poll_at(&self) -> Option { - self.next_expiration().map(|expiration| expiration.deadline) - } - - /// Advances the timer up to the instant represented by `now`. - pub(crate) fn poll(&mut self, now: u64, store: &mut T::Store) -> Option { - loop { - let expiration = self.next_expiration().and_then(|expiration| { - if expiration.deadline > now { - None - } else { - Some(expiration) - } - }); - - match expiration { - Some(ref expiration) => { - if let Some(item) = self.poll_expiration(expiration, store) { - return Some(item); - } - - self.set_elapsed(expiration.deadline); - } - None => { - // in this case the poll did not indicate an expiration - // _and_ we were not able to find a next expiration in - // the current list of timers. advance to the poll's - // current time and do nothing else. - self.set_elapsed(now); - return None; - } - } - } - } - - /// Returns the instant at which the next timeout expires. - fn next_expiration(&self) -> Option { - // Check all levels - for level in 0..NUM_LEVELS { - if let Some(expiration) = self.levels[level].next_expiration(self.elapsed) { - // There cannot be any expirations at a higher level that happen - // before this one. - debug_assert!(self.no_expirations_before(level + 1, expiration.deadline)); - - return Some(expiration); - } - } - - None - } - - /// Used for debug assertions - fn no_expirations_before(&self, start_level: usize, before: u64) -> bool { - let mut res = true; - - for l2 in start_level..NUM_LEVELS { - if let Some(e2) = self.levels[l2].next_expiration(self.elapsed) { - if e2.deadline < before { - res = false; - } - } - } - - res - } - - /// iteratively find entries that are between the wheel's current - /// time and the expiration time. for each in that population either - /// return it for notification (in the case of the last level) or tier - /// it down to the next level (in all other cases). - pub(crate) fn poll_expiration( - &mut self, - expiration: &Expiration, - store: &mut T::Store, - ) -> Option { - while let Some(item) = self.pop_entry(expiration, store) { - if expiration.level == 0 { - debug_assert_eq!(T::when(item.borrow(), store), expiration.deadline); - - return Some(item); - } else { - let when = T::when(item.borrow(), store); - - let next_level = expiration.level - 1; - - self.levels[next_level].add_entry(when, item, store); - } - } - - None - } - - fn set_elapsed(&mut self, when: u64) { - assert!( - self.elapsed <= when, - "elapsed={:?}; when={:?}", - self.elapsed, - when - ); - - if when > self.elapsed { - self.elapsed = when; - } - } - - fn pop_entry(&mut self, expiration: &Expiration, store: &mut T::Store) -> Option { - self.levels[expiration.level].pop_entry_slot(expiration.slot, store) - } - - fn level_for(&self, when: u64) -> usize { - level_for(self.elapsed, when) - } -} - -fn level_for(elapsed: u64, when: u64) -> usize { - const SLOT_MASK: u64 = (1 << 6) - 1; - - // Mask in the trailing bits ignored by the level calculation in order to cap - // the possible leading zeros - let masked = elapsed ^ when | SLOT_MASK; - - let leading_zeros = masked.leading_zeros() as usize; - let significant = 63 - leading_zeros; - significant / 6 -} - -#[cfg(all(test, not(loom)))] -mod test { - use super::*; - - #[test] - fn test_level_for() { - for pos in 0..64 { - assert_eq!( - 0, - level_for(0, pos), - "level_for({}) -- binary = {:b}", - pos, - pos - ); - } - - for level in 1..5 { - for pos in level..64 { - let a = pos * 64_usize.pow(level as u32); - assert_eq!( - level, - level_for(0, a as u64), - "level_for({}) -- binary = {:b}", - a, - a - ); - - if pos > level { - let a = a - 1; - assert_eq!( - level, - level_for(0, a as u64), - "level_for({}) -- binary = {:b}", - a, - a - ); - } - - if pos < 64 { - let a = a + 1; - assert_eq!( - level, - level_for(0, a as u64), - "level_for({}) -- binary = {:b}", - a, - a - ); - } - } - } - } -} diff --git a/common/nonexhaustive-delayqueue/src/wasm_delay_queue/wheel/stack.rs b/common/nonexhaustive-delayqueue/src/wasm_delay_queue/wheel/stack.rs deleted file mode 100644 index c87adcafda..0000000000 --- a/common/nonexhaustive-delayqueue/src/wasm_delay_queue/wheel/stack.rs +++ /dev/null @@ -1,28 +0,0 @@ -use std::borrow::Borrow; -use std::cmp::Eq; -use std::hash::Hash; - -/// Abstracts the stack operations needed to track timeouts. -pub(crate) trait Stack: Default { - /// Type of the item stored in the stack - type Owned: Borrow; - - /// Borrowed item - type Borrowed: Eq + Hash; - - /// Item storage, this allows a slab to be used instead of just the heap - type Store; - - /// Returns `true` if the stack is empty - fn is_empty(&self) -> bool; - - /// Push an item onto the stack - fn push(&mut self, item: Self::Owned, store: &mut Self::Store); - - /// Pop an item from the stack - fn pop(&mut self, store: &mut Self::Store) -> Option; - - fn remove(&mut self, item: &Self::Borrowed, store: &mut Self::Store); - - fn when(item: &Self::Borrowed, store: &Self::Store) -> u64; -} From 55bd7f8a61f5d3735958b00ec909fe28db6dde5c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bogdan-=C8=98tefan=20Neac=C5=9Fu?= Date: Thu, 3 Aug 2023 17:07:59 +0300 Subject: [PATCH 057/361] Fix doubling the data dir in nym-api (#3739) --- nym-api/src/support/config/mod.rs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/nym-api/src/support/config/mod.rs b/nym-api/src/support/config/mod.rs index 1e74708bda..51f2c6726c 100644 --- a/nym-api/src/support/config/mod.rs +++ b/nym-api/src/support/config/mod.rs @@ -106,15 +106,14 @@ impl NymConfigTemplate for Config { impl Config { pub fn new>(id: S) -> Self { - let base_data_dir = default_data_directory(id.as_ref()); Config { base: Base::new_default(id.as_ref()), - network_monitor: NetworkMonitor::new_default(&base_data_dir), - node_status_api: NodeStatusAPI::new_default(&base_data_dir), + network_monitor: NetworkMonitor::new_default(id.as_ref()), + node_status_api: NodeStatusAPI::new_default(id.as_ref()), topology_cacher: Default::default(), circulating_supply_cacher: Default::default(), rewarding: Default::default(), - coconut_signer: CoconutSigner::new_default(base_data_dir), + coconut_signer: CoconutSigner::new_default(id.as_ref()), } } From 9285aaf48764b424db1d13e4ed46810afb376fb2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C4=99drzej=20Stuczy=C5=84ski?= Date: Thu, 3 Aug 2023 15:09:07 +0100 Subject: [PATCH 058/361] Generate json schema for all used contracts (#3693) * removed sealed impl of serde for tx::Fee * further upgraded cosmwasm to 1.3.0 * wip * created schema for mixnet contract * updated return type of 'GetBondedMixnodeDetailsByIdentity' query * fixed imported version of serde_json_wasm * updated return type of 'GetFamilyByHead' query * updated return type of 'GetFamilyByLabel' query * updated return type of 'GetFamilyMembersByHead' and 'GetFamilyMembersByLabel' queries * fixed broken tests due to type changes * added support for GetFamilyMembersByLabel and GetFamilyMembersByHead queries in 'mixnet_query_client' * moved 'Account' and 'VestingContractError' to common crate * created schema for vesting contract * Added documentation for all query messages in the vesting contract * improved mixnet contract schema by adding documentation to all query types * feature-locking cw2 import * created schema for the name service contract * created schema for the service provider directory contract * created schema for the coconut bandwidth contract * created schema for the coconut dkg contract * created schema for the coconut cw4 group contract * created schema for the coconut cw3 multisig contract * fixed missing import and adjusted makefile * cargo fmt * clippy * adjusted contract CI to build with --lib flag * missing --lib flag in the makefile * updated lock files * makefile for generating the schemas * added github action to check for schema difference * adding missing step to checkout the repo --- .github/workflows/check-contract-schema.yml | 25 + .github/workflows/contracts.yml | 6 +- Cargo.lock | 140 +- Cargo.toml | 22 +- Makefile | 7 +- clients/webassembly/Cargo.lock | 121 +- .../client-libs/validator-client/Cargo.toml | 1 - .../validator-client/src/nyxd/fee/mod.rs | 57 +- .../src/nyxd/traits/mixnet_query_client.rs | 49 +- .../src/nyxd/traits/vesting_query_client.rs | 3 +- .../src/nyxd/traits/vesting_signing_client.rs | 6 +- .../mixnet/delegators/delegate_to_mixnode.rs | 1 + .../rewards/claim_delegator_reward.rs | 1 + .../rewards/vesting_claim_delegator_reward.rs | 1 + .../delegators/undelegate_from_mixnode.rs | 1 + .../delegators/vesting_delegate_to_mixnode.rs | 1 + .../vesting_undelegate_from_mixnode.rs | 1 + .../create_family_join_permit_sign_payload.rs | 1 + .../operators/mixnode/families/join_family.rs | 2 +- .../mixnode/families/leave_family.rs | 2 +- .../vesting/create_vesting_schedule.rs | 2 +- .../coconut-bandwidth-contract/Cargo.toml | 7 +- .../coconut-bandwidth-contract/src/deposit.rs | 5 +- .../coconut-bandwidth-contract/src/msg.rs | 30 +- .../src/spend_credential.rs | 16 +- .../coconut-dkg/Cargo.toml | 6 +- .../coconut-dkg/src/dealer.rs | 25 +- .../coconut-dkg/src/msg.rs | 43 +- .../coconut-dkg/src/types.rs | 20 +- .../coconut-dkg/src/verification_key.rs | 9 +- .../contracts-common/Cargo.toml | 7 +- .../contracts-common/src/dealings.rs | 2 - .../contracts-common/src/types.rs | 12 +- .../mixnet-contract/Cargo.toml | 11 +- .../mixnet-contract/src/delegation.rs | 34 +- .../mixnet-contract/src/error.rs | 3 + .../mixnet-contract/src/families.rs | 78 +- .../mixnet-contract/src/gateway.rs | 59 +- .../mixnet-contract/src/interval.rs | 129 +- .../mixnet-contract/src/lib.rs | 27 +- .../mixnet-contract/src/mixnode.rs | 125 +- .../mixnet-contract/src/msg.rs | 305 +- .../mixnet-contract/src/pending_events.rs | 182 +- .../mixnet-contract/src/reward_params.rs | 35 +- .../mixnet-contract/src/rewarding/mod.rs | 53 +- .../mixnet-contract/src/signing.rs | 0 .../mixnet-contract/src/types.rs | 62 +- .../multisig-contract/Cargo.toml | 2 +- .../name-service/Cargo.toml | 6 +- .../name-service/src/msg.rs | 72 +- .../name-service/src/response.rs | 21 +- .../name-service/src/types.rs | 17 +- .../service-provider-directory/Cargo.toml | 7 +- .../service-provider-directory/src/msg.rs | 61 +- .../src/response.rs | 24 +- .../src/signing_types.rs | 10 +- .../service-provider-directory/src/types.rs | 17 +- .../vesting-contract/Cargo.toml | 5 +- .../vesting-contract/src/account.rs | 124 + .../vesting-contract/src/error.rs | 11 +- .../vesting-contract/src/lib.rs | 140 +- .../vesting-contract/src/messages.rs | 199 +- .../vesting-contract/src/types.rs | 166 + common/types/Cargo.toml | 3 +- common/types/src/delegation.rs | 2 +- common/types/src/mixnode.rs | 8 +- common/types/src/pending_events.rs | 4 +- common/types/src/vesting.rs | 4 +- contracts/Cargo.lock | 134 +- contracts/Cargo.toml | 24 +- contracts/Makefile | 25 + contracts/coconut-bandwidth/.cargo/config | 4 + contracts/coconut-bandwidth/Cargo.toml | 9 +- contracts/coconut-bandwidth/Makefile | 2 + .../schema/nym-coconut-bandwidth.json | 382 + .../coconut-bandwidth/schema/raw/execute.json | 130 + .../schema/raw/instantiate.json | 22 + .../coconut-bandwidth/schema/raw/migrate.json | 6 + .../coconut-bandwidth/schema/raw/query.json | 56 + ...response_to_get_all_spent_credentials.json | 86 + .../raw/response_to_get_spent_credential.json | 74 + contracts/coconut-bandwidth/src/bin/schema.rs | 16 + contracts/coconut-bandwidth/src/state.rs | 3 +- contracts/coconut-dkg/.cargo/config | 5 + contracts/coconut-dkg/Cargo.toml | 11 +- contracts/coconut-dkg/Makefile | 2 + .../coconut-dkg/schema/nym-coconut-dkg.json | 1023 ++ contracts/coconut-dkg/schema/raw/execute.json | 150 + .../coconut-dkg/schema/raw/instantiate.json | 78 + contracts/coconut-dkg/schema/raw/migrate.json | 6 + contracts/coconut-dkg/schema/raw/query.json | 198 + .../raw/response_to_get_current_dealers.json | 66 + .../response_to_get_current_epoch_state.json | 201 + ...sponse_to_get_current_epoch_threshold.json | 7 + .../raw/response_to_get_dealer_details.json | 64 + .../schema/raw/response_to_get_dealing.json | 64 + .../raw/response_to_get_initial_dealers.json | 39 + .../raw/response_to_get_past_dealers.json | 66 + .../response_to_get_verification_keys.json | 76 + contracts/coconut-dkg/src/bin/schema.rs | 14 + contracts/coconut-dkg/src/state/mod.rs | 3 +- .../src/decrease_mixnode_pledge.rs | 3 +- contracts/mixnet/.cargo/config | 4 +- contracts/mixnet/Cargo.toml | 10 +- contracts/mixnet/Makefile | 3 + contracts/mixnet/examples/schema.rs | 19 - .../mixnet/schema/nym-mixnet-contract.json | 8507 +++++++++++++++++ .../{execute_msg.json => raw/execute.json} | 401 +- .../instantiate.json} | 4 +- .../{migrate_msg.json => raw/migrate.json} | 3 +- .../schema/{query_msg.json => raw/query.json} | 337 +- .../raw/response_to_get_all_delegations.json | 128 + .../response_to_get_all_families_paged.json | 62 + .../response_to_get_all_members_paged.json | 42 + ...et_bonded_mixnode_details_by_identity.json | 342 + ...response_to_get_c_w2_contract_version.json | 20 + .../raw/response_to_get_contract_version.json | 40 + ...ponse_to_get_current_interval_details.json | 97 + .../response_to_get_delegation_details.json | 117 + ...response_to_get_delegator_delegations.json | 128 + .../raw/response_to_get_epoch_status.json | 93 + ...imated_current_epoch_delegator_reward.json | 89 + ...timated_current_epoch_operator_reward.json | 89 + .../raw/response_to_get_family_by_head.json | 67 + .../raw/response_to_get_family_by_label.json | 63 + ...esponse_to_get_family_members_by_head.json | 34 + ...sponse_to_get_family_members_by_label.json | 24 + .../raw/response_to_get_gateway_bond.json | 154 + .../schema/raw/response_to_get_gateways.json | 160 + .../response_to_get_layer_distribution.json | 32 + .../raw/response_to_get_mix_node_bonds.json | 193 + .../response_to_get_mix_nodes_detailed.json | 350 + .../response_to_get_mixnode_delegations.json | 116 + .../raw/response_to_get_mixnode_details.json | 344 + ...onse_to_get_mixnode_rewarding_details.json | 155 + ...ponse_to_get_number_of_pending_events.json | 25 + .../raw/response_to_get_owned_gateway.json | 158 + .../raw/response_to_get_owned_mixnode.json | 346 + ...ponse_to_get_pending_delegator_reward.json | 74 + .../response_to_get_pending_epoch_event.json | 298 + .../response_to_get_pending_epoch_events.json | 331 + ...esponse_to_get_pending_interval_event.json | 291 + ...sponse_to_get_pending_interval_events.json | 324 + ..._get_pending_mix_node_operator_reward.json | 74 + ...sponse_to_get_pending_operator_reward.json | 74 + .../raw/response_to_get_rewarded_set.json | 61 + .../raw/response_to_get_rewarding_params.json | 129 + ...se_to_get_rewarding_validator_address.json | 5 + .../raw/response_to_get_signing_nonce.json | 7 + .../raw/response_to_get_stake_saturation.json | 46 + .../schema/raw/response_to_get_state.json | 115 + .../raw/response_to_get_state_params.json | 61 + ..._to_get_unbonded_mix_node_information.json | 76 + .../response_to_get_unbonded_mix_nodes.json | 94 + ...et_unbonded_mix_nodes_by_identity_key.json | 94 + ...se_to_get_unbonded_mix_nodes_by_owner.json | 94 + contracts/mixnet/src/bin/schema.rs | 14 + contracts/mixnet/src/contract.rs | 8 +- contracts/mixnet/src/families/queries.rs | 52 +- contracts/mixnet/src/families/storage.rs | 36 +- contracts/mixnet/src/families/transactions.rs | 36 +- contracts/mixnet/src/mixnodes/queries.rs | 24 +- contracts/mixnet/src/support/tests/mod.rs | 2 +- .../multisig/cw3-flex-multisig/.cargo/config | 6 +- .../multisig/cw3-flex-multisig/Cargo.toml | 7 +- contracts/multisig/cw3-flex-multisig/Makefile | 2 + .../schema/cw3-flex-multisig.json | 5201 ++++++++++ .../cw3-flex-multisig/schema/raw/execute.json | 976 ++ .../schema/raw/instantiate.json | 261 + .../cw3-flex-multisig/schema/raw/migrate.json | 18 + .../cw3-flex-multisig/schema/raw/query.json | 232 + .../schema/raw/response_to_config.json | 269 + .../raw/response_to_list_proposals.json | 1045 ++ .../schema/raw/response_to_list_voters.json | 37 + .../schema/raw/response_to_list_votes.json | 80 + .../schema/raw/response_to_proposal.json | 1030 ++ .../raw/response_to_reverse_proposals.json | 1045 ++ .../schema/raw/response_to_threshold.json | 103 + .../schema/raw/response_to_vote.json | 81 + .../schema/raw/response_to_voter.json | 16 + .../cw3-flex-multisig/src/bin/schema.rs | 14 + contracts/multisig/cw4-group/Cargo.toml | 5 + contracts/multisig/cw4-group/Makefile | 2 + .../multisig/cw4-group/schema/cw4-group.json | 395 + .../cw4-group/schema/raw/execute.json | 125 + .../cw4-group/schema/raw/instantiate.json | 45 + .../multisig/cw4-group/schema/raw/query.json | 114 + .../schema/raw/response_to_admin.json | 15 + .../schema/raw/response_to_hooks.json | 17 + .../schema/raw/response_to_list_members.json | 38 + .../schema/raw/response_to_member.json | 16 + .../schema/raw/response_to_total_weight.json | 16 + .../multisig/cw4-group/src/bin/schema.rs | 13 + contracts/name-service/.cargo/config | 4 + contracts/name-service/Cargo.toml | 8 + contracts/name-service/Makefile | 2 + .../name-service/schema/nym-name-service.json | 1242 +++ .../name-service/schema/raw/execute.json | 181 + .../name-service/schema/raw/instantiate.json | 35 + .../name-service/schema/raw/migrate.json | 6 + contracts/name-service/schema/raw/query.json | 210 + .../schema/raw/response_to_all.json | 158 + .../schema/raw/response_to_by_address.json | 143 + .../schema/raw/response_to_by_name.json | 128 + .../schema/raw/response_to_by_owner.json | 143 + .../schema/raw/response_to_config.json | 35 + ...response_to_get_c_w2_contract_version.json | 20 + .../raw/response_to_get_contract_version.json | 40 + .../schema/raw/response_to_name_id.json | 128 + .../schema/raw/response_to_signing_nonce.json | 7 + contracts/name-service/src/bin/schema.rs | 14 + contracts/name-service/src/state/names.rs | 2 + .../service-provider-directory/.cargo/config | 2 +- .../service-provider-directory/Cargo.toml | 8 + contracts/service-provider-directory/Makefile | 2 + .../nym-service-provider-directory.json | 1104 +++ .../schema/raw/execute.json | 181 + .../schema/raw/instantiate.json | 35 + .../schema/raw/migrate.json | 6 + .../schema/raw/query.json | 184 + .../schema/raw/response_to_all.json | 162 + .../schema/raw/response_to_by_announcer.json | 147 + .../raw/response_to_by_nym_address.json | 147 + .../schema/raw/response_to_config.json | 35 + ...response_to_get_c_w2_contract_version.json | 20 + .../raw/response_to_get_contract_version.json | 40 + .../schema/raw/response_to_service_id.json | 132 + .../schema/raw/response_to_signing_nonce.json | 7 + .../src/bin/schema.rs | 16 + .../src/state/services.rs | 2 + contracts/vesting/.cargo/config | 4 + contracts/vesting/Cargo.toml | 9 +- contracts/vesting/Makefile | 5 +- .../vesting/schema/nym-vesting-contract.json | 2841 ++++++ contracts/vesting/schema/raw/execute.json | 971 ++ contracts/vesting/schema/raw/instantiate.json | 18 + contracts/vesting/schema/raw/migrate.json | 6 + contracts/vesting/schema/raw/query.json | 719 ++ .../schema/raw/response_to_get_account.json | 179 + .../raw/response_to_get_accounts_paged.json | 61 + ...e_to_get_accounts_vesting_coins_paged.json | 89 + .../raw/response_to_get_all_delegations.json | 90 + ...response_to_get_c_w2_contract_version.json | 20 + .../raw/response_to_get_contract_version.json | 40 + ...esponse_to_get_current_vesting_period.json | 36 + .../raw/response_to_get_delegated_coins.json | 23 + .../raw/response_to_get_delegation.json | 47 + .../raw/response_to_get_delegation_times.json | 50 + .../schema/raw/response_to_get_end_time.json | 16 + .../schema/raw/response_to_get_gateway.json | 72 + ...get_historical_vesting_staking_reward.json | 23 + .../schema/raw/response_to_get_mixnode.json | 72 + .../raw/response_to_get_original_vesting.json | 55 + .../raw/response_to_get_pledged_coins.json | 23 + ...esponse_to_get_spendable_reward_coins.json | 23 + ...esponse_to_get_spendable_vested_coins.json | 23 + .../raw/response_to_get_staked_coins.json | 23 + .../raw/response_to_get_start_time.json | 16 + ...sponse_to_get_total_delegation_amount.json | 23 + .../raw/response_to_get_vested_coins.json | 23 + .../raw/response_to_get_vesting_coins.json | 23 + .../raw/response_to_get_withdrawn_coins.json | 23 + .../schema/raw/response_to_locked_coins.json | 23 + .../raw/response_to_spendable_coins.json | 23 + contracts/vesting/src/bin/schema.rs | 14 + contracts/vesting/src/contract.rs | 52 +- contracts/vesting/src/lib.rs | 1 - contracts/vesting/src/queries.rs | 61 +- contracts/vesting/src/storage.rs | 103 +- contracts/vesting/src/support/tests.rs | 12 +- .../vesting/src/traits/bonding_account.rs | 31 +- .../vesting/src/traits/delegating_account.rs | 12 +- contracts/vesting/src/traits/node_families.rs | 10 +- .../vesting/src/traits/vesting_account.rs | 31 +- contracts/vesting/src/transactions.rs | 118 +- .../src/vesting/account/delegating_account.rs | 17 +- .../account/gateway_bonding_account.rs | 15 +- .../account/mixnode_bonding_account.rs | 35 +- contracts/vesting/src/vesting/account/mod.rs | 392 +- .../src/vesting/account/node_families.rs | 11 +- .../src/vesting/account/vesting_account.rs | 32 +- contracts/vesting/src/vesting/mod.rs | 48 +- nym-connect/desktop/Cargo.lock | 133 +- nym-wallet/Cargo.lock | 132 +- nym-wallet/nym-wallet-types/Cargo.toml | 4 +- nym-wallet/src-tauri/Cargo.toml | 4 +- nym-wallet/src-tauri/src/utils.rs | 4 +- 287 files changed, 41959 insertions(+), 1828 deletions(-) create mode 100644 .github/workflows/check-contract-schema.yml delete mode 100644 common/cosmwasm-smart-contracts/mixnet-contract/src/signing.rs create mode 100644 common/cosmwasm-smart-contracts/vesting-contract/src/account.rs rename contracts/vesting/src/errors.rs => common/cosmwasm-smart-contracts/vesting-contract/src/error.rs (92%) create mode 100644 common/cosmwasm-smart-contracts/vesting-contract/src/types.rs create mode 100644 contracts/Makefile create mode 100644 contracts/coconut-bandwidth/.cargo/config create mode 100644 contracts/coconut-bandwidth/Makefile create mode 100644 contracts/coconut-bandwidth/schema/nym-coconut-bandwidth.json create mode 100644 contracts/coconut-bandwidth/schema/raw/execute.json create mode 100644 contracts/coconut-bandwidth/schema/raw/instantiate.json create mode 100644 contracts/coconut-bandwidth/schema/raw/migrate.json create mode 100644 contracts/coconut-bandwidth/schema/raw/query.json create mode 100644 contracts/coconut-bandwidth/schema/raw/response_to_get_all_spent_credentials.json create mode 100644 contracts/coconut-bandwidth/schema/raw/response_to_get_spent_credential.json create mode 100644 contracts/coconut-bandwidth/src/bin/schema.rs create mode 100644 contracts/coconut-dkg/.cargo/config create mode 100644 contracts/coconut-dkg/Makefile create mode 100644 contracts/coconut-dkg/schema/nym-coconut-dkg.json create mode 100644 contracts/coconut-dkg/schema/raw/execute.json create mode 100644 contracts/coconut-dkg/schema/raw/instantiate.json create mode 100644 contracts/coconut-dkg/schema/raw/migrate.json create mode 100644 contracts/coconut-dkg/schema/raw/query.json create mode 100644 contracts/coconut-dkg/schema/raw/response_to_get_current_dealers.json create mode 100644 contracts/coconut-dkg/schema/raw/response_to_get_current_epoch_state.json create mode 100644 contracts/coconut-dkg/schema/raw/response_to_get_current_epoch_threshold.json create mode 100644 contracts/coconut-dkg/schema/raw/response_to_get_dealer_details.json create mode 100644 contracts/coconut-dkg/schema/raw/response_to_get_dealing.json create mode 100644 contracts/coconut-dkg/schema/raw/response_to_get_initial_dealers.json create mode 100644 contracts/coconut-dkg/schema/raw/response_to_get_past_dealers.json create mode 100644 contracts/coconut-dkg/schema/raw/response_to_get_verification_keys.json create mode 100644 contracts/coconut-dkg/src/bin/schema.rs delete mode 100644 contracts/mixnet/examples/schema.rs create mode 100644 contracts/mixnet/schema/nym-mixnet-contract.json rename contracts/mixnet/schema/{execute_msg.json => raw/execute.json} (73%) rename contracts/mixnet/schema/{instantiate_msg.json => raw/instantiate.json} (97%) rename contracts/mixnet/schema/{migrate_msg.json => raw/migrate.json} (85%) rename contracts/mixnet/schema/{query_msg.json => raw/query.json} (56%) create mode 100644 contracts/mixnet/schema/raw/response_to_get_all_delegations.json create mode 100644 contracts/mixnet/schema/raw/response_to_get_all_families_paged.json create mode 100644 contracts/mixnet/schema/raw/response_to_get_all_members_paged.json create mode 100644 contracts/mixnet/schema/raw/response_to_get_bonded_mixnode_details_by_identity.json create mode 100644 contracts/mixnet/schema/raw/response_to_get_c_w2_contract_version.json create mode 100644 contracts/mixnet/schema/raw/response_to_get_contract_version.json create mode 100644 contracts/mixnet/schema/raw/response_to_get_current_interval_details.json create mode 100644 contracts/mixnet/schema/raw/response_to_get_delegation_details.json create mode 100644 contracts/mixnet/schema/raw/response_to_get_delegator_delegations.json create mode 100644 contracts/mixnet/schema/raw/response_to_get_epoch_status.json create mode 100644 contracts/mixnet/schema/raw/response_to_get_estimated_current_epoch_delegator_reward.json create mode 100644 contracts/mixnet/schema/raw/response_to_get_estimated_current_epoch_operator_reward.json create mode 100644 contracts/mixnet/schema/raw/response_to_get_family_by_head.json create mode 100644 contracts/mixnet/schema/raw/response_to_get_family_by_label.json create mode 100644 contracts/mixnet/schema/raw/response_to_get_family_members_by_head.json create mode 100644 contracts/mixnet/schema/raw/response_to_get_family_members_by_label.json create mode 100644 contracts/mixnet/schema/raw/response_to_get_gateway_bond.json create mode 100644 contracts/mixnet/schema/raw/response_to_get_gateways.json create mode 100644 contracts/mixnet/schema/raw/response_to_get_layer_distribution.json create mode 100644 contracts/mixnet/schema/raw/response_to_get_mix_node_bonds.json create mode 100644 contracts/mixnet/schema/raw/response_to_get_mix_nodes_detailed.json create mode 100644 contracts/mixnet/schema/raw/response_to_get_mixnode_delegations.json create mode 100644 contracts/mixnet/schema/raw/response_to_get_mixnode_details.json create mode 100644 contracts/mixnet/schema/raw/response_to_get_mixnode_rewarding_details.json create mode 100644 contracts/mixnet/schema/raw/response_to_get_number_of_pending_events.json create mode 100644 contracts/mixnet/schema/raw/response_to_get_owned_gateway.json create mode 100644 contracts/mixnet/schema/raw/response_to_get_owned_mixnode.json create mode 100644 contracts/mixnet/schema/raw/response_to_get_pending_delegator_reward.json create mode 100644 contracts/mixnet/schema/raw/response_to_get_pending_epoch_event.json create mode 100644 contracts/mixnet/schema/raw/response_to_get_pending_epoch_events.json create mode 100644 contracts/mixnet/schema/raw/response_to_get_pending_interval_event.json create mode 100644 contracts/mixnet/schema/raw/response_to_get_pending_interval_events.json create mode 100644 contracts/mixnet/schema/raw/response_to_get_pending_mix_node_operator_reward.json create mode 100644 contracts/mixnet/schema/raw/response_to_get_pending_operator_reward.json create mode 100644 contracts/mixnet/schema/raw/response_to_get_rewarded_set.json create mode 100644 contracts/mixnet/schema/raw/response_to_get_rewarding_params.json create mode 100644 contracts/mixnet/schema/raw/response_to_get_rewarding_validator_address.json create mode 100644 contracts/mixnet/schema/raw/response_to_get_signing_nonce.json create mode 100644 contracts/mixnet/schema/raw/response_to_get_stake_saturation.json create mode 100644 contracts/mixnet/schema/raw/response_to_get_state.json create mode 100644 contracts/mixnet/schema/raw/response_to_get_state_params.json create mode 100644 contracts/mixnet/schema/raw/response_to_get_unbonded_mix_node_information.json create mode 100644 contracts/mixnet/schema/raw/response_to_get_unbonded_mix_nodes.json create mode 100644 contracts/mixnet/schema/raw/response_to_get_unbonded_mix_nodes_by_identity_key.json create mode 100644 contracts/mixnet/schema/raw/response_to_get_unbonded_mix_nodes_by_owner.json create mode 100644 contracts/mixnet/src/bin/schema.rs create mode 100644 contracts/multisig/cw3-flex-multisig/Makefile create mode 100644 contracts/multisig/cw3-flex-multisig/schema/cw3-flex-multisig.json create mode 100644 contracts/multisig/cw3-flex-multisig/schema/raw/execute.json create mode 100644 contracts/multisig/cw3-flex-multisig/schema/raw/instantiate.json create mode 100644 contracts/multisig/cw3-flex-multisig/schema/raw/migrate.json create mode 100644 contracts/multisig/cw3-flex-multisig/schema/raw/query.json create mode 100644 contracts/multisig/cw3-flex-multisig/schema/raw/response_to_config.json create mode 100644 contracts/multisig/cw3-flex-multisig/schema/raw/response_to_list_proposals.json create mode 100644 contracts/multisig/cw3-flex-multisig/schema/raw/response_to_list_voters.json create mode 100644 contracts/multisig/cw3-flex-multisig/schema/raw/response_to_list_votes.json create mode 100644 contracts/multisig/cw3-flex-multisig/schema/raw/response_to_proposal.json create mode 100644 contracts/multisig/cw3-flex-multisig/schema/raw/response_to_reverse_proposals.json create mode 100644 contracts/multisig/cw3-flex-multisig/schema/raw/response_to_threshold.json create mode 100644 contracts/multisig/cw3-flex-multisig/schema/raw/response_to_vote.json create mode 100644 contracts/multisig/cw3-flex-multisig/schema/raw/response_to_voter.json create mode 100644 contracts/multisig/cw3-flex-multisig/src/bin/schema.rs create mode 100644 contracts/multisig/cw4-group/Makefile create mode 100644 contracts/multisig/cw4-group/schema/cw4-group.json create mode 100644 contracts/multisig/cw4-group/schema/raw/execute.json create mode 100644 contracts/multisig/cw4-group/schema/raw/instantiate.json create mode 100644 contracts/multisig/cw4-group/schema/raw/query.json create mode 100644 contracts/multisig/cw4-group/schema/raw/response_to_admin.json create mode 100644 contracts/multisig/cw4-group/schema/raw/response_to_hooks.json create mode 100644 contracts/multisig/cw4-group/schema/raw/response_to_list_members.json create mode 100644 contracts/multisig/cw4-group/schema/raw/response_to_member.json create mode 100644 contracts/multisig/cw4-group/schema/raw/response_to_total_weight.json create mode 100644 contracts/multisig/cw4-group/src/bin/schema.rs create mode 100644 contracts/name-service/.cargo/config create mode 100644 contracts/name-service/Makefile create mode 100644 contracts/name-service/schema/nym-name-service.json create mode 100644 contracts/name-service/schema/raw/execute.json create mode 100644 contracts/name-service/schema/raw/instantiate.json create mode 100644 contracts/name-service/schema/raw/migrate.json create mode 100644 contracts/name-service/schema/raw/query.json create mode 100644 contracts/name-service/schema/raw/response_to_all.json create mode 100644 contracts/name-service/schema/raw/response_to_by_address.json create mode 100644 contracts/name-service/schema/raw/response_to_by_name.json create mode 100644 contracts/name-service/schema/raw/response_to_by_owner.json create mode 100644 contracts/name-service/schema/raw/response_to_config.json create mode 100644 contracts/name-service/schema/raw/response_to_get_c_w2_contract_version.json create mode 100644 contracts/name-service/schema/raw/response_to_get_contract_version.json create mode 100644 contracts/name-service/schema/raw/response_to_name_id.json create mode 100644 contracts/name-service/schema/raw/response_to_signing_nonce.json create mode 100644 contracts/name-service/src/bin/schema.rs create mode 100644 contracts/service-provider-directory/Makefile create mode 100644 contracts/service-provider-directory/schema/nym-service-provider-directory.json create mode 100644 contracts/service-provider-directory/schema/raw/execute.json create mode 100644 contracts/service-provider-directory/schema/raw/instantiate.json create mode 100644 contracts/service-provider-directory/schema/raw/migrate.json create mode 100644 contracts/service-provider-directory/schema/raw/query.json create mode 100644 contracts/service-provider-directory/schema/raw/response_to_all.json create mode 100644 contracts/service-provider-directory/schema/raw/response_to_by_announcer.json create mode 100644 contracts/service-provider-directory/schema/raw/response_to_by_nym_address.json create mode 100644 contracts/service-provider-directory/schema/raw/response_to_config.json create mode 100644 contracts/service-provider-directory/schema/raw/response_to_get_c_w2_contract_version.json create mode 100644 contracts/service-provider-directory/schema/raw/response_to_get_contract_version.json create mode 100644 contracts/service-provider-directory/schema/raw/response_to_service_id.json create mode 100644 contracts/service-provider-directory/schema/raw/response_to_signing_nonce.json create mode 100644 contracts/service-provider-directory/src/bin/schema.rs create mode 100644 contracts/vesting/.cargo/config create mode 100644 contracts/vesting/schema/nym-vesting-contract.json create mode 100644 contracts/vesting/schema/raw/execute.json create mode 100644 contracts/vesting/schema/raw/instantiate.json create mode 100644 contracts/vesting/schema/raw/migrate.json create mode 100644 contracts/vesting/schema/raw/query.json create mode 100644 contracts/vesting/schema/raw/response_to_get_account.json create mode 100644 contracts/vesting/schema/raw/response_to_get_accounts_paged.json create mode 100644 contracts/vesting/schema/raw/response_to_get_accounts_vesting_coins_paged.json create mode 100644 contracts/vesting/schema/raw/response_to_get_all_delegations.json create mode 100644 contracts/vesting/schema/raw/response_to_get_c_w2_contract_version.json create mode 100644 contracts/vesting/schema/raw/response_to_get_contract_version.json create mode 100644 contracts/vesting/schema/raw/response_to_get_current_vesting_period.json create mode 100644 contracts/vesting/schema/raw/response_to_get_delegated_coins.json create mode 100644 contracts/vesting/schema/raw/response_to_get_delegation.json create mode 100644 contracts/vesting/schema/raw/response_to_get_delegation_times.json create mode 100644 contracts/vesting/schema/raw/response_to_get_end_time.json create mode 100644 contracts/vesting/schema/raw/response_to_get_gateway.json create mode 100644 contracts/vesting/schema/raw/response_to_get_historical_vesting_staking_reward.json create mode 100644 contracts/vesting/schema/raw/response_to_get_mixnode.json create mode 100644 contracts/vesting/schema/raw/response_to_get_original_vesting.json create mode 100644 contracts/vesting/schema/raw/response_to_get_pledged_coins.json create mode 100644 contracts/vesting/schema/raw/response_to_get_spendable_reward_coins.json create mode 100644 contracts/vesting/schema/raw/response_to_get_spendable_vested_coins.json create mode 100644 contracts/vesting/schema/raw/response_to_get_staked_coins.json create mode 100644 contracts/vesting/schema/raw/response_to_get_start_time.json create mode 100644 contracts/vesting/schema/raw/response_to_get_total_delegation_amount.json create mode 100644 contracts/vesting/schema/raw/response_to_get_vested_coins.json create mode 100644 contracts/vesting/schema/raw/response_to_get_vesting_coins.json create mode 100644 contracts/vesting/schema/raw/response_to_get_withdrawn_coins.json create mode 100644 contracts/vesting/schema/raw/response_to_locked_coins.json create mode 100644 contracts/vesting/schema/raw/response_to_spendable_coins.json create mode 100644 contracts/vesting/src/bin/schema.rs diff --git a/.github/workflows/check-contract-schema.yml b/.github/workflows/check-contract-schema.yml new file mode 100644 index 0000000000..700440bdd5 --- /dev/null +++ b/.github/workflows/check-contract-schema.yml @@ -0,0 +1,25 @@ +name: Check Contract Schema + +on: + push: + paths: + - 'contracts/**' + - 'common/**' + pull_request: + paths: + - 'contracts/**' + - 'common/**' + +jobs: + check-schema: + name: Generate and check schema + runs-on: ubuntu-latest + steps: + - name: Check out repository code + uses: actions/checkout@v2 + + - name: Generate the schema + run: make contract-schema + + - name: Check for diff + run: git diff --exit-code diff --git a/.github/workflows/contracts.yml b/.github/workflows/contracts.yml index 9de2cab348..64aeabb5d7 100644 --- a/.github/workflows/contracts.yml +++ b/.github/workflows/contracts.yml @@ -48,12 +48,12 @@ jobs: RUSTFLAGS: '-C link-arg=-s' with: command: build - args: --manifest-path contracts/Cargo.toml --workspace --target wasm32-unknown-unknown + args: --manifest-path contracts/Cargo.toml --workspace --lib --target wasm32-unknown-unknown - uses: actions-rs/cargo@v1 with: command: test - args: --manifest-path contracts/Cargo.toml + args: --lib --manifest-path contracts/Cargo.toml - uses: actions-rs/cargo@v1 with: @@ -64,4 +64,4 @@ jobs: if: ${{ matrix.rust != 'nightly' }} with: command: clippy - args: --manifest-path contracts/Cargo.toml --workspace --all-targets -- -D warnings + args: --lib --manifest-path contracts/Cargo.toml --workspace --all-targets -- -D warnings diff --git a/Cargo.lock b/Cargo.lock index a823625624..43b8e6eff2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -541,6 +541,12 @@ dependencies = [ "zeroize", ] +[[package]] +name = "bnum" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "845141a4fade3f790628b7daaaa298a25b204fb28907eb54febe5142db6ce653" + [[package]] name = "bs58" version = "0.4.0" @@ -969,9 +975,9 @@ dependencies = [ [[package]] name = "cosmwasm-crypto" -version = "1.2.5" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75836a10cb9654c54e77ee56da94d592923092a10b369cdb0dbd56acefc16340" +checksum = "0d076a08ec01ed23c4396aca98ec73a38fa1fee5f310465add52b4108181c7a8" dependencies = [ "digest 0.10.7", "ed25519-zebra", @@ -982,18 +988,18 @@ dependencies = [ [[package]] name = "cosmwasm-derive" -version = "1.2.5" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c9f7f0e51bfc7295f7b2664fe8513c966428642aa765dad8a74acdab5e0c773" +checksum = "dec361f3c09d7b41221948fc17be9b3c96cb58e55a02f82da36f888a651f2584" dependencies = [ "syn 1.0.109", ] [[package]] name = "cosmwasm-schema" -version = "1.2.5" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f00b363610218eea83f24bbab09e1a7c3920b79f068334fdfcc62f6129ef9fc" +checksum = "bb6b2fb76758ef59cddc77f2e2ae91c22f77da49037e9f182e9c2833f0e959b1" dependencies = [ "cosmwasm-schema-derive", "schemars", @@ -1004,9 +1010,9 @@ dependencies = [ [[package]] name = "cosmwasm-schema-derive" -version = "1.2.5" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae38f909b2822d32b275c9e2db9728497aa33ffe67dd463bc67c6a3b7092785c" +checksum = "2bfa39422f0d9f1c9a6fd3711573258495314dfa3aae738ea825ecd9964bc659" dependencies = [ "proc-macro2", "quote", @@ -1015,11 +1021,12 @@ dependencies = [ [[package]] name = "cosmwasm-std" -version = "1.2.5" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a49b85345e811c8e80ec55d0d091e4fcb4f00f97ab058f9be5f614c444a730cb" +checksum = "1f6dc2ee23313add5ecacc3ccac217b9967ad9d2d11bd56e5da6aa65a9da6138" dependencies = [ "base64 0.13.1", + "bnum", "cosmwasm-crypto", "cosmwasm-derive", "derivative", @@ -1027,10 +1034,9 @@ dependencies = [ "hex", "schemars", "serde", - "serde-json-wasm 0.5.1", + "serde-json-wasm", "sha2 0.10.6", "thiserror", - "uint", ] [[package]] @@ -1203,12 +1209,6 @@ dependencies = [ "winapi", ] -[[package]] -name = "crunchy" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" - [[package]] name = "crypto-bigint" version = "0.4.9" @@ -1370,9 +1370,9 @@ dependencies = [ [[package]] name = "cw-controllers" -version = "1.0.1" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91440ce8ec4f0642798bc8c8cb6b9b53c1926c6dadaf0eed267a5145cd529071" +checksum = "d5d8edce4b78785f36413f67387e4be7d0cb7d032b5d4164bcc024f9c3f3f2ea" dependencies = [ "cosmwasm-schema", "cosmwasm-std", @@ -1385,9 +1385,9 @@ dependencies = [ [[package]] name = "cw-storage-plus" -version = "1.0.1" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "053a5083c258acd68386734f428a5a171b29f7d733151ae83090c6fcc9417ffa" +checksum = "3f0e92a069d62067f3472c62e30adedb4cab1754725c0f2a682b3128d2bf3c79" dependencies = [ "cosmwasm-std", "schemars", @@ -1411,22 +1411,23 @@ dependencies = [ [[package]] name = "cw2" -version = "1.0.1" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fb70cee2cf0b4a8ff7253e6bc6647107905e8eb37208f87d54f67810faa62f8" +checksum = "29ac2dc7a55ad64173ca1e0a46697c31b7a5c51342f55a1e84a724da4eb99908" dependencies = [ "cosmwasm-schema", "cosmwasm-std", "cw-storage-plus", "schemars", "serde", + "thiserror", ] [[package]] name = "cw20" -version = "1.0.1" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91666da6c7b40c8dd5ff94df655a28114efc10c79b70b4d06f13c31e37d60609" +checksum = "011c45920f8200bd5d32d4fe52502506f64f2f75651ab408054d4cfc75ca3a9b" dependencies = [ "cosmwasm-schema", "cosmwasm-std", @@ -1437,9 +1438,9 @@ dependencies = [ [[package]] name = "cw3" -version = "1.0.1" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fe0b587008aa221cd2a2579a21990a28c4347dc53ad43167c68ad765f5b6efa" +checksum = "171af3d9127de6805a7dd819fb070c7d2f6c3ea85f4193f42cef259f0a7f33d5" dependencies = [ "cosmwasm-schema", "cosmwasm-std", @@ -1452,9 +1453,9 @@ dependencies = [ [[package]] name = "cw4" -version = "1.0.1" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c236e0bae02ce97e89235a681dd0e07d099524b369f1ef908d704db3e6b049b" +checksum = "a398696307efadaaa2d0850076f865fa706c959d493cb4203314f72be6b77a64" dependencies = [ "cosmwasm-schema", "cosmwasm-std", @@ -3846,22 +3847,21 @@ dependencies = [ name = "nym-coconut-bandwidth-contract-common" version = "0.1.0" dependencies = [ + "cosmwasm-schema", "cosmwasm-std", + "cw2", "nym-multisig-contract-common", - "schemars", - "serde", ] [[package]] name = "nym-coconut-dkg-common" version = "0.1.0" dependencies = [ + "cosmwasm-schema", "cosmwasm-std", "cw-utils", "nym-contracts-common", "nym-multisig-contract-common", - "schemars", - "serde", ] [[package]] @@ -3893,6 +3893,7 @@ name = "nym-contracts-common" version = "0.5.0" dependencies = [ "bs58 0.4.0", + "cosmwasm-schema", "cosmwasm-std", "schemars", "serde", @@ -4141,14 +4142,16 @@ name = "nym-mixnet-contract-common" version = "0.6.0" dependencies = [ "bs58 0.4.0", + "cosmwasm-schema", "cosmwasm-std", + "cw2", "humantime-serde", "log", "nym-contracts-common", "rand_chacha 0.3.1", "schemars", "serde", - "serde-json-wasm 0.4.1", + "serde-json-wasm", "serde_repr", "thiserror", "time 0.3.21", @@ -4249,11 +4252,12 @@ dependencies = [ name = "nym-name-service-common" version = "0.1.0" dependencies = [ + "cosmwasm-schema", "cosmwasm-std", "cw-controllers", "cw-utils", + "cw2", "nym-contracts-common", - "schemars", "serde", "thiserror", ] @@ -4449,12 +4453,12 @@ dependencies = [ name = "nym-service-provider-directory-common" version = "0.1.0" dependencies = [ + "cosmwasm-schema", "cosmwasm-std", "cw-controllers", "cw-utils", + "cw2", "nym-contracts-common", - "schemars", - "serde", "thiserror", ] @@ -4804,7 +4808,6 @@ dependencies = [ "nym-config", "nym-mixnet-contract-common", "nym-validator-client", - "nym-vesting-contract", "nym-vesting-contract-common", "reqwest", "schemars", @@ -4846,7 +4849,6 @@ dependencies = [ "nym-name-service-common", "nym-network-defaults", "nym-service-provider-directory-common", - "nym-vesting-contract", "nym-vesting-contract-common", "openssl", "prost", @@ -4862,33 +4864,17 @@ dependencies = [ "zeroize", ] -[[package]] -name = "nym-vesting-contract" -version = "1.4.0" -dependencies = [ - "cosmwasm-derive", - "cosmwasm-std", - "cw-storage-plus", - "cw2", - "nym-contracts-common", - "nym-mixnet-contract-common", - "nym-vesting-contract-common", - "schemars", - "semver 1.0.17", - "serde", - "thiserror", - "vergen", -] - [[package]] name = "nym-vesting-contract-common" version = "0.7.0" dependencies = [ + "cosmwasm-schema", "cosmwasm-std", + "cw2", "nym-contracts-common", "nym-mixnet-contract-common", - "schemars", "serde", + "thiserror", "ts-rs", ] @@ -4904,7 +4890,6 @@ dependencies = [ "nym-network-defaults", "nym-types", "nym-validator-client", - "nym-vesting-contract", "nym-vesting-contract-common", "serde", "serde_json", @@ -6431,18 +6416,9 @@ dependencies = [ [[package]] name = "serde-json-wasm" -version = "0.4.1" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "479b4dbc401ca13ee8ce902851b834893251404c4f3c65370a49e047a6be09a5" -dependencies = [ - "serde", -] - -[[package]] -name = "serde-json-wasm" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16a62a1fad1e1828b24acac8f2b468971dade7b8c3c2e672bcadefefb1f8c137" +checksum = "a15bee9b04dd165c3f4e142628982ddde884c2022a89e8ddf99c4829bf2c3a58" dependencies = [ "serde", ] @@ -6980,12 +6956,6 @@ dependencies = [ "loom", ] -[[package]] -name = "static_assertions" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" - [[package]] name = "stringprep" version = "0.1.2" @@ -7197,9 +7167,9 @@ dependencies = [ [[package]] name = "tendermint-rpc" -version = "0.32.0" +version = "0.32.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd2cc789170db5a35d4e0bb2490035c03ef96df08f119bee25fd8dab5a09aa25" +checksum = "d119d83a130537fc4a98c3c9eb6899ebe857fea4860400a61675bfb5f0b35129" dependencies = [ "async-trait", "bytes", @@ -7818,18 +7788,6 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9e79c4d996edb816c91e4308506774452e55e95c3c9de07b6729e17e15a5ef81" -[[package]] -name = "uint" -version = "0.9.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76f64bba2c53b04fcab63c01a7d7427eadc821e3bc48c34dc9ba29c501164b52" -dependencies = [ - "byteorder", - "crunchy", - "hex", - "static_assertions", -] - [[package]] name = "uncased" version = "0.9.9" diff --git a/Cargo.toml b/Cargo.toml index 22b4cb99ac..666d1f76fc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -117,20 +117,22 @@ anyhow = "1.0.71" async-trait = "0.1.64" bip39 = { version = "2.0.0", features = ["zeroize"] } cfg-if = "1.0.0" -cosmwasm-derive = "=1.2.5" -cosmwasm-schema = "=1.2.5" -cosmwasm-std = "=1.2.5" -cosmwasm-storage = "=1.2.5" +cosmwasm-derive = "=1.3.0" +cosmwasm-schema = "=1.3.0" +cosmwasm-std = "=1.3.0" +# use 0.5.0 as that's the version used by cosmwasm-std 1.3.0 +# (and ideally we don't want to pull the same dependency twice) +serde-json-wasm = "=0.5.0" +cosmwasm-storage = "=1.3.0" cosmrs = "=0.14.0" # same version as used by cosmrs tendermint-rpc = "=0.32" cw-utils = "=1.0.1" -cw-storage-plus = "=1.0.1" -cw2 = { version = "=1.0.1" } -cw3 = { version = "=1.0.1" } -cw3-fixed-multisig = { version = "=1.0.1" } -cw4 = { version = "=1.0.1" } -cw-controllers = { version = "=1.0.1" } +cw-storage-plus = "=1.1.0" +cw2 = { version = "=1.1.0" } +cw3 = { version = "=1.1.0" } +cw4 = { version = "=1.1.0" } +cw-controllers = { version = "=1.1.0" } dotenvy = "0.15.6" generic-array = "0.14.7" k256 = "0.13" diff --git a/Makefile b/Makefile index ad812f11b8..2576489be9 100644 --- a/Makefile +++ b/Makefile @@ -73,7 +73,7 @@ endef # Generate targets for the various cargo workspaces $(eval $(call add_cargo_workspace,main,.)) -$(eval $(call add_cargo_workspace,contracts,contracts,--target wasm32-unknown-unknown)) +$(eval $(call add_cargo_workspace,contracts,contracts,--lib --target wasm32-unknown-unknown)) $(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)) @@ -104,7 +104,7 @@ NAME_SERVICE_CONTRACT=$(CONTRACTS_OUT_DIR)/nym_name_service.wasm wasm: wasm-build wasm-opt wasm-build: - RUSTFLAGS='-C link-arg=-s' cargo build --manifest-path contracts/Cargo.toml --release --target wasm32-unknown-unknown + RUSTFLAGS='-C link-arg=-s' cargo build --lib --manifest-path contracts/Cargo.toml --release --target wasm32-unknown-unknown wasm-opt: wasm-opt --disable-sign-ext -Os $(VESTING_CONTRACT) -o $(VESTING_CONTRACT) @@ -112,6 +112,9 @@ wasm-opt: wasm-opt --disable-sign-ext -Os $(SERVICE_PROVIDER_DIRECTORY_CONTRACT) -o $(SERVICE_PROVIDER_DIRECTORY_CONTRACT) wasm-opt --disable-sign-ext -Os $(NAME_SERVICE_CONTRACT) -o $(NAME_SERVICE_CONTRACT) +contract-schema: + $(MAKE) -C contracts schema + # ----------------------------------------------------------------------------- # Misc # ----------------------------------------------------------------------------- diff --git a/clients/webassembly/Cargo.lock b/clients/webassembly/Cargo.lock index 330f31c4d5..793f321a97 100644 --- a/clients/webassembly/Cargo.lock +++ b/clients/webassembly/Cargo.lock @@ -378,6 +378,12 @@ dependencies = [ "zeroize", ] +[[package]] +name = "bnum" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "845141a4fade3f790628b7daaaa298a25b204fb28907eb54febe5142db6ce653" + [[package]] name = "bs58" version = "0.4.0" @@ -658,18 +664,18 @@ dependencies = [ [[package]] name = "cosmwasm-derive" -version = "1.2.5" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c9f7f0e51bfc7295f7b2664fe8513c966428642aa765dad8a74acdab5e0c773" +checksum = "dec361f3c09d7b41221948fc17be9b3c96cb58e55a02f82da36f888a651f2584" dependencies = [ "syn 1.0.109", ] [[package]] name = "cosmwasm-schema" -version = "1.2.5" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f00b363610218eea83f24bbab09e1a7c3920b79f068334fdfcc62f6129ef9fc" +checksum = "bb6b2fb76758ef59cddc77f2e2ae91c22f77da49037e9f182e9c2833f0e959b1" dependencies = [ "cosmwasm-schema-derive", "schemars", @@ -680,9 +686,9 @@ dependencies = [ [[package]] name = "cosmwasm-schema-derive" -version = "1.2.5" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae38f909b2822d32b275c9e2db9728497aa33ffe67dd463bc67c6a3b7092785c" +checksum = "2bfa39422f0d9f1c9a6fd3711573258495314dfa3aae738ea825ecd9964bc659" dependencies = [ "proc-macro2", "quote", @@ -691,11 +697,12 @@ dependencies = [ [[package]] name = "cosmwasm-std" -version = "1.2.5" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a49b85345e811c8e80ec55d0d091e4fcb4f00f97ab058f9be5f614c444a730cb" +checksum = "1f6dc2ee23313add5ecacc3ccac217b9967ad9d2d11bd56e5da6aa65a9da6138" dependencies = [ "base64 0.13.1", + "bnum", "cosmwasm-crypto", "cosmwasm-derive", "derivative", @@ -703,10 +710,9 @@ dependencies = [ "hex", "schemars", "serde", - "serde-json-wasm 0.5.1", + "serde-json-wasm", "sha2 0.10.6", "thiserror", - "uint", ] [[package]] @@ -810,12 +816,6 @@ dependencies = [ "cfg-if 1.0.0", ] -[[package]] -name = "crunchy" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" - [[package]] name = "crypto-bigint" version = "0.4.9" @@ -927,9 +927,9 @@ dependencies = [ [[package]] name = "cw-controllers" -version = "1.0.1" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91440ce8ec4f0642798bc8c8cb6b9b53c1926c6dadaf0eed267a5145cd529071" +checksum = "d5d8edce4b78785f36413f67387e4be7d0cb7d032b5d4164bcc024f9c3f3f2ea" dependencies = [ "cosmwasm-schema", "cosmwasm-std", @@ -942,9 +942,9 @@ dependencies = [ [[package]] name = "cw-storage-plus" -version = "1.0.1" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "053a5083c258acd68386734f428a5a171b29f7d733151ae83090c6fcc9417ffa" +checksum = "3f0e92a069d62067f3472c62e30adedb4cab1754725c0f2a682b3128d2bf3c79" dependencies = [ "cosmwasm-std", "schemars", @@ -968,15 +968,16 @@ dependencies = [ [[package]] name = "cw2" -version = "1.0.1" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fb70cee2cf0b4a8ff7253e6bc6647107905e8eb37208f87d54f67810faa62f8" +checksum = "29ac2dc7a55ad64173ca1e0a46697c31b7a5c51342f55a1e84a724da4eb99908" dependencies = [ "cosmwasm-schema", "cosmwasm-std", "cw-storage-plus", "schemars", "serde", + "thiserror", ] [[package]] @@ -994,9 +995,9 @@ dependencies = [ [[package]] name = "cw3" -version = "1.0.1" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fe0b587008aa221cd2a2579a21990a28c4347dc53ad43167c68ad765f5b6efa" +checksum = "171af3d9127de6805a7dd819fb070c7d2f6c3ea85f4193f42cef259f0a7f33d5" dependencies = [ "cosmwasm-schema", "cosmwasm-std", @@ -1009,9 +1010,9 @@ dependencies = [ [[package]] name = "cw4" -version = "1.0.1" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c236e0bae02ce97e89235a681dd0e07d099524b369f1ef908d704db3e6b049b" +checksum = "a398696307efadaaa2d0850076f865fa706c959d493cb4203314f72be6b77a64" dependencies = [ "cosmwasm-schema", "cosmwasm-std", @@ -2566,22 +2567,20 @@ dependencies = [ name = "nym-coconut-bandwidth-contract-common" version = "0.1.0" dependencies = [ + "cosmwasm-schema", "cosmwasm-std", "nym-multisig-contract-common", - "schemars", - "serde", ] [[package]] name = "nym-coconut-dkg-common" version = "0.1.0" dependencies = [ + "cosmwasm-schema", "cosmwasm-std", "cw-utils", "nym-contracts-common", "nym-multisig-contract-common", - "schemars", - "serde", ] [[package]] @@ -2613,6 +2612,7 @@ name = "nym-contracts-common" version = "0.5.0" dependencies = [ "bs58 0.4.0", + "cosmwasm-schema", "cosmwasm-std", "schemars", "serde", @@ -2757,13 +2757,14 @@ name = "nym-mixnet-contract-common" version = "0.6.0" dependencies = [ "bs58 0.4.0", + "cosmwasm-schema", "cosmwasm-std", "humantime-serde", "log", "nym-contracts-common", "schemars", "serde", - "serde-json-wasm 0.4.1", + "serde-json-wasm", "serde_repr", "thiserror", "time", @@ -2788,11 +2789,11 @@ dependencies = [ name = "nym-name-service-common" version = "0.1.0" dependencies = [ + "cosmwasm-schema", "cosmwasm-std", "cw-controllers", "cw-utils", "nym-contracts-common", - "schemars", "serde", "thiserror", ] @@ -2867,12 +2868,11 @@ dependencies = [ name = "nym-service-provider-directory-common" version = "0.1.0" dependencies = [ + "cosmwasm-schema", "cosmwasm-std", "cw-controllers", "cw-utils", "nym-contracts-common", - "schemars", - "serde", "thiserror", ] @@ -3096,7 +3096,6 @@ dependencies = [ "nym-name-service-common", "nym-network-defaults", "nym-service-provider-directory-common", - "nym-vesting-contract", "nym-vesting-contract-common", "openssl", "prost", @@ -3111,33 +3110,16 @@ dependencies = [ "zeroize", ] -[[package]] -name = "nym-vesting-contract" -version = "1.4.0" -dependencies = [ - "cosmwasm-derive", - "cosmwasm-std", - "cw-storage-plus", - "cw2", - "nym-contracts-common", - "nym-mixnet-contract-common", - "nym-vesting-contract-common", - "schemars", - "semver 1.0.17", - "serde", - "thiserror", - "vergen", -] - [[package]] name = "nym-vesting-contract-common" version = "0.7.0" dependencies = [ + "cosmwasm-schema", "cosmwasm-std", "nym-contracts-common", "nym-mixnet-contract-common", - "schemars", "serde", + "thiserror", ] [[package]] @@ -4061,18 +4043,9 @@ dependencies = [ [[package]] name = "serde-json-wasm" -version = "0.4.1" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "479b4dbc401ca13ee8ce902851b834893251404c4f3c65370a49e047a6be09a5" -dependencies = [ - "serde", -] - -[[package]] -name = "serde-json-wasm" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16a62a1fad1e1828b24acac8f2b468971dade7b8c3c2e672bcadefefb1f8c137" +checksum = "a15bee9b04dd165c3f4e142628982ddde884c2022a89e8ddf99c4829bf2c3a58" dependencies = [ "serde", ] @@ -4516,12 +4489,6 @@ dependencies = [ "tokio-rustls 0.23.4", ] -[[package]] -name = "static_assertions" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" - [[package]] name = "stringprep" version = "0.1.2" @@ -4984,18 +4951,6 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9e79c4d996edb816c91e4308506774452e55e95c3c9de07b6729e17e15a5ef81" -[[package]] -name = "uint" -version = "0.9.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76f64bba2c53b04fcab63c01a7d7427eadc821e3bc48c34dc9ba29c501164b52" -dependencies = [ - "byteorder", - "crunchy", - "hex", - "static_assertions", -] - [[package]] name = "unicode-bidi" version = "0.3.13" diff --git a/common/client-libs/validator-client/Cargo.toml b/common/client-libs/validator-client/Cargo.toml index 31475887bc..5ac785a9c7 100644 --- a/common/client-libs/validator-client/Cargo.toml +++ b/common/client-libs/validator-client/Cargo.toml @@ -20,7 +20,6 @@ nym-multisig-contract-common = { path = "../../cosmwasm-smart-contracts/multisig nym-name-service-common = { path = "../../cosmwasm-smart-contracts/name-service" } nym-group-contract-common = { path = "../../cosmwasm-smart-contracts/group-contract" } nym-service-provider-directory-common = { path = "../../cosmwasm-smart-contracts/service-provider-directory" } -nym-vesting-contract = { path = "../../../contracts/vesting" } serde = { workspace = true, features = ["derive"] } serde_json = { workspace = true } reqwest = { version = "0.11", features = ["json"] } diff --git a/common/client-libs/validator-client/src/nyxd/fee/mod.rs b/common/client-libs/validator-client/src/nyxd/fee/mod.rs index 0d67544203..ee30d1ffe5 100644 --- a/common/client-libs/validator-client/src/nyxd/fee/mod.rs +++ b/common/client-libs/validator-client/src/nyxd/fee/mod.rs @@ -32,7 +32,7 @@ impl Display for AutoFeeGrant { #[derive(Debug, Clone, Serialize, Deserialize)] pub enum Fee { - Manual(#[serde(with = "sealed::TxFee")] tx::Fee), + Manual(tx::Fee), Auto(Option), PayerGranterAuto(AutoFeeGrant), } @@ -121,58 +121,3 @@ impl GasAdjustable for Gas { } } } - -// a workaround to provide serde implementation for tx::Fee. We don't want to ever expose any of those -// types to the public and ideally they will get replaced by proper implementation inside comrs -mod sealed { - use cosmrs::tx::{self}; - use cosmrs::{AccountId, Denom as CosmosDenom}; - use cosmrs::{Coin as CosmosCoin, Gas}; - use serde::{Deserialize, Deserializer, Serialize, Serializer}; - - #[derive(Serialize, Deserialize, Clone)] - struct Coin { - denom: CosmosDenom, - amount: u128, - } - - impl From for CosmosCoin { - fn from(val: Coin) -> Self { - CosmosCoin { - denom: val.denom, - amount: val.amount, - } - } - } - - impl From for Coin { - fn from(val: CosmosCoin) -> Self { - Coin { - denom: val.denom, - amount: val.amount, - } - } - } - - fn coin_vec_ser(val: &[CosmosCoin], serializer: S) -> Result { - let vec: Vec = val.iter().cloned().map(Into::into).collect(); - vec.serialize(serializer) - } - fn coin_vec_deser<'de, D: Deserializer<'de>>( - deserializer: D, - ) -> Result, D::Error> { - let vec: Vec = Deserialize::deserialize(deserializer)?; - Ok(vec.iter().cloned().map(Into::into).collect()) - } - - #[derive(Serialize, Deserialize)] - #[serde(remote = "tx::Fee")] - pub(super) struct TxFee { - #[serde(serialize_with = "coin_vec_ser")] - #[serde(deserialize_with = "coin_vec_deser")] - pub amount: Vec, - pub gas_limit: Gas, - pub payer: Option, - pub granter: Option, - } -} diff --git a/common/client-libs/validator-client/src/nyxd/traits/mixnet_query_client.rs b/common/client-libs/validator-client/src/nyxd/traits/mixnet_query_client.rs index 84e4defaee..0297b90452 100644 --- a/common/client-libs/validator-client/src/nyxd/traits/mixnet_query_client.rs +++ b/common/client-libs/validator-client/src/nyxd/traits/mixnet_query_client.rs @@ -8,10 +8,9 @@ use async_trait::async_trait; use cosmrs::AccountId; use nym_contracts_common::signing::Nonce; use nym_mixnet_contract_common::delegation::{MixNodeDelegationResponse, OwnerProxySubKey}; -use nym_mixnet_contract_common::families::Family; use nym_mixnet_contract_common::mixnode::{ - MixNodeDetails, MixnodeRewardingDetailsResponse, PagedMixnodesDetailsResponse, - PagedUnbondedMixnodesResponse, StakeSaturationResponse, UnbondedMixnodeResponse, + MixnodeRewardingDetailsResponse, PagedMixnodesDetailsResponse, PagedUnbondedMixnodesResponse, + StakeSaturationResponse, UnbondedMixnodeResponse, }; use nym_mixnet_contract_common::reward_params::{Performance, RewardingParams}; use nym_mixnet_contract_common::rewarding::{ @@ -19,14 +18,15 @@ use nym_mixnet_contract_common::rewarding::{ }; use nym_mixnet_contract_common::{ delegation, ContractBuildInformation, ContractState, ContractStateParams, - CurrentIntervalResponse, EpochEventId, EpochStatus, GatewayBondResponse, - GatewayOwnershipResponse, IdentityKey, IntervalEventId, LayerDistribution, MixId, - MixOwnershipResponse, MixnodeDetailsResponse, NumberOfPendingEventsResponse, - PagedAllDelegationsResponse, PagedDelegatorDelegationsResponse, PagedFamiliesResponse, - PagedGatewayResponse, PagedMembersResponse, PagedMixNodeDelegationsResponse, - PagedMixnodeBondsResponse, PagedRewardedSetResponse, PendingEpochEventResponse, - PendingEpochEventsResponse, PendingIntervalEventResponse, PendingIntervalEventsResponse, - QueryMsg as MixnetQueryMsg, + CurrentIntervalResponse, EpochEventId, EpochStatus, FamilyByHeadResponse, + FamilyByLabelResponse, FamilyMembersByHeadResponse, FamilyMembersByLabelResponse, + GatewayBondResponse, GatewayOwnershipResponse, IdentityKey, IntervalEventId, LayerDistribution, + MixId, MixOwnershipResponse, MixnodeDetailsByIdentityResponse, MixnodeDetailsResponse, + NumberOfPendingEventsResponse, PagedAllDelegationsResponse, PagedDelegatorDelegationsResponse, + PagedFamiliesResponse, PagedGatewayResponse, PagedMembersResponse, + PagedMixNodeDelegationsResponse, PagedMixnodeBondsResponse, PagedRewardedSetResponse, + PendingEpochEventResponse, PendingEpochEventsResponse, PendingIntervalEventResponse, + PendingIntervalEventsResponse, QueryMsg as MixnetQueryMsg, }; use serde::Deserialize; @@ -100,6 +100,24 @@ pub trait MixnetQueryClient { .await } + async fn get_family_members_by_head + Send>( + &self, + head: S, + ) -> Result { + self.query_mixnet_contract(MixnetQueryMsg::GetFamilyMembersByHead { head: head.into() }) + .await + } + + async fn get_family_members_by_label + Send>( + &self, + label: S, + ) -> Result { + self.query_mixnet_contract(MixnetQueryMsg::GetFamilyMembersByLabel { + label: label.into(), + }) + .await + } + // mixnode-related: async fn get_mixnode_bonds_paged( @@ -178,7 +196,7 @@ pub trait MixnetQueryClient { async fn get_mixnode_details_by_identity( &self, mix_identity: IdentityKey, - ) -> Result, NyxdError> { + ) -> Result { self.query_mixnet_contract(MixnetQueryMsg::GetBondedMixnodeDetailsByIdentity { mix_identity, }) @@ -415,14 +433,17 @@ pub trait MixnetQueryClient { .await } - async fn get_node_family_by_label(&self, label: &str) -> Result, NyxdError> { + async fn get_node_family_by_label( + &self, + label: &str, + ) -> Result { self.query_mixnet_contract(MixnetQueryMsg::GetFamilyByLabel { label: label.to_string(), }) .await } - async fn get_node_family_by_head(&self, head: &str) -> Result, NyxdError> { + async fn get_node_family_by_head(&self, head: &str) -> Result { self.query_mixnet_contract(MixnetQueryMsg::GetFamilyByHead { head: head.to_string(), }) diff --git a/common/client-libs/validator-client/src/nyxd/traits/vesting_query_client.rs b/common/client-libs/validator-client/src/nyxd/traits/vesting_query_client.rs index 2f8931f98a..c70f0a78ca 100644 --- a/common/client-libs/validator-client/src/nyxd/traits/vesting_query_client.rs +++ b/common/client-libs/validator-client/src/nyxd/traits/vesting_query_client.rs @@ -10,12 +10,11 @@ use cosmwasm_std::{Coin as CosmWasmCoin, Timestamp}; use nym_contracts_common::ContractBuildInformation; use nym_mixnet_contract_common::MixId; use nym_vesting_contract_common::{ - messages::QueryMsg as VestingQueryMsg, AccountVestingCoins, AccountsResponse, + messages::QueryMsg as VestingQueryMsg, Account, AccountVestingCoins, AccountsResponse, AllDelegationsResponse, BaseVestingAccountInfo, DelegationTimesResponse, OriginalVestingResponse, Period, PledgeData, VestingCoinsResponse, VestingDelegation, }; use serde::Deserialize; -use vesting_contract::vesting::Account; #[async_trait] pub trait VestingQueryClient { diff --git a/common/client-libs/validator-client/src/nyxd/traits/vesting_signing_client.rs b/common/client-libs/validator-client/src/nyxd/traits/vesting_signing_client.rs index aba0d219cf..f1354566dd 100644 --- a/common/client-libs/validator-client/src/nyxd/traits/vesting_signing_client.rs +++ b/common/client-libs/validator-client/src/nyxd/traits/vesting_signing_client.rs @@ -12,10 +12,8 @@ use nym_mixnet_contract_common::families::FamilyHead; use nym_mixnet_contract_common::gateway::GatewayConfigUpdate; use nym_mixnet_contract_common::mixnode::{MixNodeConfigUpdate, MixNodeCostParams}; use nym_mixnet_contract_common::{Gateway, MixId, MixNode}; -use nym_vesting_contract_common::messages::{ - ExecuteMsg as VestingExecuteMsg, VestingSpecification, -}; -use nym_vesting_contract_common::PledgeCap; +use nym_vesting_contract_common::messages::ExecuteMsg as VestingExecuteMsg; +use nym_vesting_contract_common::{PledgeCap, VestingSpecification}; #[async_trait] pub trait VestingSigningClient { diff --git a/common/commands/src/validator/mixnet/delegators/delegate_to_mixnode.rs b/common/commands/src/validator/mixnet/delegators/delegate_to_mixnode.rs index 6efa068545..f07cd21d5f 100644 --- a/common/commands/src/validator/mixnet/delegators/delegate_to_mixnode.rs +++ b/common/commands/src/validator/mixnet/delegators/delegate_to_mixnode.rs @@ -34,6 +34,7 @@ pub async fn delegate_to_mixnode(args: Args, client: SigningClient) { .get_mixnode_details_by_identity(identity_key) .await .expect("contract query failed") + .mixnode_details .expect("mixnode with the specified identity doesnt exist"); node_details.mix_id() } diff --git a/common/commands/src/validator/mixnet/delegators/rewards/claim_delegator_reward.rs b/common/commands/src/validator/mixnet/delegators/rewards/claim_delegator_reward.rs index 3f2ce6b197..61265c9ac5 100644 --- a/common/commands/src/validator/mixnet/delegators/rewards/claim_delegator_reward.rs +++ b/common/commands/src/validator/mixnet/delegators/rewards/claim_delegator_reward.rs @@ -29,6 +29,7 @@ pub async fn claim_delegator_reward(args: Args, client: SigningClient) { .get_mixnode_details_by_identity(identity_key) .await .expect("contract query failed") + .mixnode_details .expect("mixnode with the specified identity doesnt exist"); node_details.mix_id() } diff --git a/common/commands/src/validator/mixnet/delegators/rewards/vesting_claim_delegator_reward.rs b/common/commands/src/validator/mixnet/delegators/rewards/vesting_claim_delegator_reward.rs index 96733206f9..6d9e0bcbf7 100644 --- a/common/commands/src/validator/mixnet/delegators/rewards/vesting_claim_delegator_reward.rs +++ b/common/commands/src/validator/mixnet/delegators/rewards/vesting_claim_delegator_reward.rs @@ -29,6 +29,7 @@ pub async fn vesting_claim_delegator_reward(args: Args, client: SigningClient) { .get_mixnode_details_by_identity(identity_key) .await .expect("contract query failed") + .mixnode_details .expect("mixnode with the specified identity doesnt exist"); node_details.mix_id() } diff --git a/common/commands/src/validator/mixnet/delegators/undelegate_from_mixnode.rs b/common/commands/src/validator/mixnet/delegators/undelegate_from_mixnode.rs index 2e4ce7b8cd..790f518267 100644 --- a/common/commands/src/validator/mixnet/delegators/undelegate_from_mixnode.rs +++ b/common/commands/src/validator/mixnet/delegators/undelegate_from_mixnode.rs @@ -29,6 +29,7 @@ pub async fn undelegate_from_mixnode(args: Args, client: SigningClient) { .get_mixnode_details_by_identity(identity_key) .await .expect("contract query failed") + .mixnode_details .expect("mixnode with the specified identity doesnt exist"); node_details.mix_id() } diff --git a/common/commands/src/validator/mixnet/delegators/vesting_delegate_to_mixnode.rs b/common/commands/src/validator/mixnet/delegators/vesting_delegate_to_mixnode.rs index ec10a60193..41a992f0ed 100644 --- a/common/commands/src/validator/mixnet/delegators/vesting_delegate_to_mixnode.rs +++ b/common/commands/src/validator/mixnet/delegators/vesting_delegate_to_mixnode.rs @@ -40,6 +40,7 @@ pub async fn vesting_delegate_to_mixnode(args: Args, client: SigningClient) { .get_mixnode_details_by_identity(identity_key) .await .expect("contract query failed") + .mixnode_details .expect("mixnode with the specified identity doesnt exist"); node_details.mix_id() } diff --git a/common/commands/src/validator/mixnet/delegators/vesting_undelegate_from_mixnode.rs b/common/commands/src/validator/mixnet/delegators/vesting_undelegate_from_mixnode.rs index 4f8dac045e..42493f493d 100644 --- a/common/commands/src/validator/mixnet/delegators/vesting_undelegate_from_mixnode.rs +++ b/common/commands/src/validator/mixnet/delegators/vesting_undelegate_from_mixnode.rs @@ -34,6 +34,7 @@ pub async fn vesting_undelegate_from_mixnode(args: Args, client: SigningClient) .get_mixnode_details_by_identity(identity_key) .await .expect("contract query failed") + .mixnode_details .expect("mixnode with the specified identity doesnt exist"); node_details.mix_id() } diff --git a/common/commands/src/validator/mixnet/operators/mixnode/families/create_family_join_permit_sign_payload.rs b/common/commands/src/validator/mixnet/operators/mixnode/families/create_family_join_permit_sign_payload.rs index a305df4839..7b02731b30 100644 --- a/common/commands/src/validator/mixnet/operators/mixnode/families/create_family_join_permit_sign_payload.rs +++ b/common/commands/src/validator/mixnet/operators/mixnode/families/create_family_join_permit_sign_payload.rs @@ -50,6 +50,7 @@ pub async fn create_family_join_permit_sign_payload(args: Args, client: QueryCli .get_node_family_by_head(mixnode.bond_information.identity()) .await .unwrap() + .family .is_none() { eprintln!("{} does not even seem to own a family!", args.address); diff --git a/common/commands/src/validator/mixnet/operators/mixnode/families/join_family.rs b/common/commands/src/validator/mixnet/operators/mixnode/families/join_family.rs index 8b888e8bd6..b69dd7a4b2 100644 --- a/common/commands/src/validator/mixnet/operators/mixnode/families/join_family.rs +++ b/common/commands/src/validator/mixnet/operators/mixnode/families/join_family.rs @@ -28,7 +28,7 @@ pub struct Args { pub async fn join_family(args: Args, client: SigningClient) { info!("Join family"); - let family_head = FamilyHead::new(&args.family_head.to_base58_string()); + let family_head = FamilyHead::new(args.family_head.to_base58_string()); let res = if args.with_vesting_account { client diff --git a/common/commands/src/validator/mixnet/operators/mixnode/families/leave_family.rs b/common/commands/src/validator/mixnet/operators/mixnode/families/leave_family.rs index 5cb06e7762..193c003ab4 100644 --- a/common/commands/src/validator/mixnet/operators/mixnode/families/leave_family.rs +++ b/common/commands/src/validator/mixnet/operators/mixnode/families/leave_family.rs @@ -23,7 +23,7 @@ pub struct Args { pub async fn leave_family(args: Args, client: SigningClient) { info!("Leave family"); - let family_head = FamilyHead::new(&args.family_head.to_base58_string()); + let family_head = FamilyHead::new(args.family_head.to_base58_string()); let res = if args.with_vesting_account { client diff --git a/common/commands/src/validator/vesting/create_vesting_schedule.rs b/common/commands/src/validator/vesting/create_vesting_schedule.rs index 524cdf67e1..f7397bf14c 100644 --- a/common/commands/src/validator/vesting/create_vesting_schedule.rs +++ b/common/commands/src/validator/vesting/create_vesting_schedule.rs @@ -11,7 +11,7 @@ use nym_network_defaults::NymNetworkDetails; use nym_validator_client::nyxd::traits::VestingSigningClient; use nym_validator_client::nyxd::AccountId; use nym_validator_client::nyxd::{CosmosCoin, Denom}; -use nym_vesting_contract_common::messages::VestingSpecification; +use nym_vesting_contract_common::types::VestingSpecification; use nym_vesting_contract_common::PledgeCap; use crate::context::SigningClient; diff --git a/common/cosmwasm-smart-contracts/coconut-bandwidth-contract/Cargo.toml b/common/cosmwasm-smart-contracts/coconut-bandwidth-contract/Cargo.toml index fc5e1b43d7..90cf90ba17 100644 --- a/common/cosmwasm-smart-contracts/coconut-bandwidth-contract/Cargo.toml +++ b/common/cosmwasm-smart-contracts/coconut-bandwidth-contract/Cargo.toml @@ -7,6 +7,9 @@ edition = "2021" [dependencies] cosmwasm-std = { workspace = true } -schemars = "0.8" -serde = { version = "1.0.103", default-features = false, features = ["derive"] } +cosmwasm-schema = { workspace = true } +cw2 = { workspace = true, optional = true } nym-multisig-contract-common = { path = "../multisig-contract" } + +[features] +schema = ["cw2"] \ No newline at end of file diff --git a/common/cosmwasm-smart-contracts/coconut-bandwidth-contract/src/deposit.rs b/common/cosmwasm-smart-contracts/coconut-bandwidth-contract/src/deposit.rs index d0572c3c91..c0e3a4acd3 100644 --- a/common/cosmwasm-smart-contracts/coconut-bandwidth-contract/src/deposit.rs +++ b/common/cosmwasm-smart-contracts/coconut-bandwidth-contract/src/deposit.rs @@ -1,10 +1,9 @@ // Copyright 2021 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use schemars::JsonSchema; -use serde::{Deserialize, Serialize}; +use cosmwasm_schema::cw_serde; -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] +#[cw_serde] pub struct DepositData { deposit_info: String, identity_key: String, diff --git a/common/cosmwasm-smart-contracts/coconut-bandwidth-contract/src/msg.rs b/common/cosmwasm-smart-contracts/coconut-bandwidth-contract/src/msg.rs index 8fffcc81eb..8387ac4ec5 100644 --- a/common/cosmwasm-smart-contracts/coconut-bandwidth-contract/src/msg.rs +++ b/common/cosmwasm-smart-contracts/coconut-bandwidth-contract/src/msg.rs @@ -1,39 +1,41 @@ // Copyright 2021 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use cosmwasm_std::Coin; -use schemars::JsonSchema; -use serde::{Deserialize, Serialize}; - use crate::{deposit::DepositData, spend_credential::SpendCredentialData}; +use cosmwasm_schema::cw_serde; +use cosmwasm_std::Coin; -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] +#[cfg(feature = "schema")] +use crate::spend_credential::{PagedSpendCredentialResponse, SpendCredentialResponse}; +#[cfg(feature = "schema")] +use cosmwasm_schema::QueryResponses; + +#[cw_serde] pub struct InstantiateMsg { pub multisig_addr: String, pub pool_addr: String, pub mix_denom: String, } -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] -#[serde(rename_all = "snake_case")] +#[cw_serde] pub enum ExecuteMsg { DepositFunds { data: DepositData }, SpendCredential { data: SpendCredentialData }, ReleaseFunds { funds: Coin }, } -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] -#[serde(rename_all = "snake_case")] +#[cw_serde] +#[cfg_attr(feature = "schema", derive(QueryResponses))] pub enum QueryMsg { - GetSpentCredential { - blinded_serial_number: String, - }, + #[cfg_attr(feature = "schema", returns(SpendCredentialResponse))] + GetSpentCredential { blinded_serial_number: String }, + + #[cfg_attr(feature = "schema", returns(PagedSpendCredentialResponse))] GetAllSpentCredentials { limit: Option, start_after: Option, }, } -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] -#[serde(rename_all = "snake_case")] +#[cw_serde] pub struct MigrateMsg {} diff --git a/common/cosmwasm-smart-contracts/coconut-bandwidth-contract/src/spend_credential.rs b/common/cosmwasm-smart-contracts/coconut-bandwidth-contract/src/spend_credential.rs index 7cb47dc73b..d8fc108d14 100644 --- a/common/cosmwasm-smart-contracts/coconut-bandwidth-contract/src/spend_credential.rs +++ b/common/cosmwasm-smart-contracts/coconut-bandwidth-contract/src/spend_credential.rs @@ -1,14 +1,13 @@ // Copyright 2022 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 +use cosmwasm_schema::cw_serde; use cosmwasm_std::{from_binary, to_binary, Addr, Coin, CosmosMsg, StdResult, WasmMsg}; use nym_multisig_contract_common::msg::ExecuteMsg as MultisigExecuteMsg; -use schemars::JsonSchema; -use serde::{Deserialize, Serialize}; use crate::msg::ExecuteMsg; -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] +#[cw_serde] pub struct SpendCredentialData { funds: Coin, blinded_serial_number: String, @@ -37,13 +36,14 @@ impl SpendCredentialData { } } -#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Eq, Serialize, JsonSchema)] +#[cw_serde] +#[derive(Copy)] pub enum SpendCredentialStatus { InProgress, Spent, } -#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize, JsonSchema)] +#[cw_serde] pub struct SpendCredential { funds: Coin, blinded_serial_number: String, @@ -74,10 +74,12 @@ impl SpendCredential { } } -#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize, JsonSchema)] +#[cw_serde] pub struct PagedSpendCredentialResponse { pub spend_credentials: Vec, pub per_page: usize, + + /// Field indicating paging information for the following queries if the caller wishes to get further entries. pub start_next_after: Option, } @@ -95,7 +97,7 @@ impl PagedSpendCredentialResponse { } } -#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize, JsonSchema)] +#[cw_serde] pub struct SpendCredentialResponse { pub spend_credential: Option, } diff --git a/common/cosmwasm-smart-contracts/coconut-dkg/Cargo.toml b/common/cosmwasm-smart-contracts/coconut-dkg/Cargo.toml index 1f6d6c3f3f..271d5b5bb9 100644 --- a/common/cosmwasm-smart-contracts/coconut-dkg/Cargo.toml +++ b/common/cosmwasm-smart-contracts/coconut-dkg/Cargo.toml @@ -6,10 +6,12 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +cosmwasm-schema = { workspace = true } cosmwasm-std = { workspace = true } cw-utils = { workspace = true } -schemars = "0.8" -serde = { version = "1.0.103", default-features = false, features = ["derive"] } contracts-common = { path = "../contracts-common", package = "nym-contracts-common" } nym-multisig-contract-common = { path = "../multisig-contract" } + +[features] +schema = [] \ No newline at end of file diff --git a/common/cosmwasm-smart-contracts/coconut-dkg/src/dealer.rs b/common/cosmwasm-smart-contracts/coconut-dkg/src/dealer.rs index 7b24be4869..3547345fa1 100644 --- a/common/cosmwasm-smart-contracts/coconut-dkg/src/dealer.rs +++ b/common/cosmwasm-smart-contracts/coconut-dkg/src/dealer.rs @@ -2,11 +2,10 @@ // SPDX-License-Identifier: Apache-2.0 use crate::types::{ContractSafeBytes, EncodedBTEPublicKeyWithProof, NodeIndex}; +use cosmwasm_schema::cw_serde; use cosmwasm_std::Addr; -use serde::{Deserialize, Serialize}; -#[derive(Serialize, Deserialize, Clone, Debug, Eq, PartialEq)] -#[serde(rename_all = "snake_case")] +#[cw_serde] pub struct DealerDetails { pub address: Addr, pub bte_public_key_with_proof: EncodedBTEPublicKeyWithProof, @@ -14,8 +13,8 @@ pub struct DealerDetails { pub assigned_index: NodeIndex, } -#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)] -#[serde(rename_all = "snake_case")] +#[cw_serde] +#[derive(Copy)] pub enum DealerType { Current, Past, @@ -28,8 +27,7 @@ impl DealerType { } } -#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] -#[serde(rename_all = "snake_case")] +#[cw_serde] pub struct DealerDetailsResponse { pub details: Option, pub dealer_type: DealerType, @@ -44,11 +42,12 @@ impl DealerDetailsResponse { } } -#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] -#[serde(rename_all = "snake_case")] +#[cw_serde] pub struct PagedDealerResponse { pub dealers: Vec, pub per_page: usize, + + /// Field indicating paging information for the following queries if the caller wishes to get further entries. pub start_next_after: Option, } @@ -66,8 +65,7 @@ impl PagedDealerResponse { } } -#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] -#[serde(rename_all = "snake_case")] +#[cw_serde] pub struct ContractDealing { pub dealing: ContractSafeBytes, pub dealer: Addr, @@ -79,11 +77,12 @@ impl ContractDealing { } } -#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] -#[serde(rename_all = "snake_case")] +#[cw_serde] pub struct PagedDealingsResponse { pub dealings: Vec, pub per_page: usize, + + /// Field indicating paging information for the following queries if the caller wishes to get further entries. pub start_next_after: Option, } diff --git a/common/cosmwasm-smart-contracts/coconut-dkg/src/msg.rs b/common/cosmwasm-smart-contracts/coconut-dkg/src/msg.rs index 62891e8fe7..076f84291c 100644 --- a/common/cosmwasm-smart-contracts/coconut-dkg/src/msg.rs +++ b/common/cosmwasm-smart-contracts/coconut-dkg/src/msg.rs @@ -3,11 +3,19 @@ use crate::types::{ContractSafeBytes, EncodedBTEPublicKeyWithProof, EpochId, TimeConfiguration}; use crate::verification_key::VerificationKeyShare; +use cosmwasm_schema::cw_serde; use cosmwasm_std::Addr; -use schemars::JsonSchema; -use serde::{Deserialize, Serialize}; -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] +#[cfg(feature = "schema")] +use crate::{ + dealer::{DealerDetailsResponse, PagedDealerResponse, PagedDealingsResponse}, + types::{Epoch, InitialReplacementData}, + verification_key::PagedVKSharesResponse, +}; +#[cfg(feature = "schema")] +use cosmwasm_schema::QueryResponses; + +#[cw_serde] pub struct InstantiateMsg { pub group_addr: String, pub multisig_addr: String, @@ -15,8 +23,7 @@ pub struct InstantiateMsg { pub mix_denom: String, } -#[derive(Serialize, Deserialize, Clone, Debug, Eq, PartialEq, JsonSchema)] -#[serde(rename_all = "snake_case")] +#[cw_serde] pub enum ExecuteMsg { RegisterDealer { bte_key_with_proof: EncodedBTEPublicKeyWithProof, @@ -44,28 +51,41 @@ pub enum ExecuteMsg { AdvanceEpochState {}, } -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] -#[serde(rename_all = "snake_case")] +#[cw_serde] +#[cfg_attr(feature = "schema", derive(QueryResponses))] pub enum QueryMsg { + #[cfg_attr(feature = "schema", returns(Epoch))] GetCurrentEpochState {}, + + #[cfg_attr(feature = "schema", returns(u64))] GetCurrentEpochThreshold {}, + + #[cfg_attr(feature = "schema", returns(Option))] GetInitialDealers {}, - GetDealerDetails { - dealer_address: String, - }, + + #[cfg_attr(feature = "schema", returns(DealerDetailsResponse))] + GetDealerDetails { dealer_address: String }, + + #[cfg_attr(feature = "schema", returns(PagedDealerResponse))] GetCurrentDealers { limit: Option, start_after: Option, }, + + #[cfg_attr(feature = "schema", returns(PagedDealerResponse))] GetPastDealers { limit: Option, start_after: Option, }, + + #[cfg_attr(feature = "schema", returns(PagedDealingsResponse))] GetDealing { idx: u64, limit: Option, start_after: Option, }, + + #[cfg_attr(feature = "schema", returns(PagedVKSharesResponse))] GetVerificationKeys { epoch_id: EpochId, limit: Option, @@ -73,6 +93,5 @@ pub enum QueryMsg { }, } -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] -#[serde(rename_all = "snake_case")] +#[cw_serde] pub struct MigrateMsg {} diff --git a/common/cosmwasm-smart-contracts/coconut-dkg/src/types.rs b/common/cosmwasm-smart-contracts/coconut-dkg/src/types.rs index ee0b854eb9..b01f1101ab 100644 --- a/common/cosmwasm-smart-contracts/coconut-dkg/src/types.rs +++ b/common/cosmwasm-smart-contracts/coconut-dkg/src/types.rs @@ -1,8 +1,7 @@ -// Copyright 2022 - Nym Technologies SA +// Copyright 2022-2023 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use schemars::JsonSchema; -use serde::{Deserialize, Serialize}; +use cosmwasm_schema::cw_serde; use std::fmt::{Display, Formatter}; use std::str::FromStr; @@ -18,15 +17,14 @@ pub type EpochId = u64; // 2 public attributes, 2 private attributes, 1 fixed for coconut credential pub const TOTAL_DEALINGS: usize = 2 + 2 + 1; -#[derive(Serialize, Deserialize, Default, Clone, Debug, PartialEq, Eq, Ord, PartialOrd)] +#[cw_serde] pub struct InitialReplacementData { pub initial_dealers: Vec, pub initial_height: u64, } -#[derive( - Serialize, Deserialize, Clone, Copy, Debug, PartialEq, Eq, Ord, PartialOrd, JsonSchema, -)] +#[cw_serde] +#[derive(Copy)] pub struct TimeConfiguration { // The time sign-up is open for dealers to join pub public_key_submission_time_secs: u64, @@ -75,8 +73,8 @@ impl Default for TimeConfiguration { } } -#[derive(Serialize, Deserialize, Default, Clone, Copy, Debug, PartialEq, Eq, Ord, PartialOrd)] -#[serde(rename_all = "snake_case")] +#[cw_serde] +#[derive(Copy, Default)] pub struct Epoch { pub state: EpochState, pub epoch_id: EpochId, @@ -155,8 +153,8 @@ impl Epoch { // 8. InProgress -> all receivers have all their secrets derived and all is good // // Note: It's important that the variant ordering is not changed otherwise it would mess up the derived `PartialOrd` -#[derive(Serialize, Deserialize, Clone, Copy, Debug, PartialEq, Eq, Ord, PartialOrd)] -#[serde(rename_all = "snake_case")] +#[cw_serde] +#[derive(Copy)] pub enum EpochState { PublicKeySubmission { resharing: bool }, DealingExchange { resharing: bool }, diff --git a/common/cosmwasm-smart-contracts/coconut-dkg/src/verification_key.rs b/common/cosmwasm-smart-contracts/coconut-dkg/src/verification_key.rs index 9b8338b6f1..7a3d02f2e8 100644 --- a/common/cosmwasm-smart-contracts/coconut-dkg/src/verification_key.rs +++ b/common/cosmwasm-smart-contracts/coconut-dkg/src/verification_key.rs @@ -3,14 +3,14 @@ use crate::msg::ExecuteMsg; use crate::types::{EpochId, NodeIndex}; +use cosmwasm_schema::cw_serde; use cosmwasm_std::{from_binary, to_binary, Addr, CosmosMsg, StdResult, Timestamp, WasmMsg}; use cw_utils::Expiration; use nym_multisig_contract_common::msg::ExecuteMsg as MultisigExecuteMsg; -use serde::{Deserialize, Serialize}; pub type VerificationKeyShare = String; -#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] +#[cw_serde] pub struct ContractVKShare { pub share: VerificationKeyShare, pub announce_address: String, @@ -20,11 +20,12 @@ pub struct ContractVKShare { pub verified: bool, } -#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] -#[serde(rename_all = "snake_case")] +#[cw_serde] pub struct PagedVKSharesResponse { pub shares: Vec, pub per_page: usize, + + /// Field indicating paging information for the following queries if the caller wishes to get further entries. pub start_next_after: Option, } diff --git a/common/cosmwasm-smart-contracts/contracts-common/Cargo.toml b/common/cosmwasm-smart-contracts/contracts-common/Cargo.toml index 512c3edc6b..fc55402be0 100644 --- a/common/cosmwasm-smart-contracts/contracts-common/Cargo.toml +++ b/common/cosmwasm-smart-contracts/contracts-common/Cargo.toml @@ -10,9 +10,10 @@ repository = { workspace = true } [dependencies] bs58 = "0.4.0" cosmwasm-std = { workspace = true } +cosmwasm-schema = { workspace = true } schemars = "0.8" -serde = { version = "1.0", features = ["derive"] } -thiserror = "1" +serde = { workspace = true, features = ["derive"] } +thiserror = { workspace = true } [dev-dependencies] -serde_json = "1.0.0" +serde_json = { workspace = true } diff --git a/common/cosmwasm-smart-contracts/contracts-common/src/dealings.rs b/common/cosmwasm-smart-contracts/contracts-common/src/dealings.rs index 87251d38e7..8c79ccbcfc 100644 --- a/common/cosmwasm-smart-contracts/contracts-common/src/dealings.rs +++ b/common/cosmwasm-smart-contracts/contracts-common/src/dealings.rs @@ -1,8 +1,6 @@ // Copyright 2022 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -#[cfg(feature = "dkg")] -use nym_dkg::{error::DkgError, Dealing}; use schemars::JsonSchema; use serde::{Deserialize, Deserializer, Serialize, Serializer}; use std::fmt::{Display, Formatter}; diff --git a/common/cosmwasm-smart-contracts/contracts-common/src/types.rs b/common/cosmwasm-smart-contracts/contracts-common/src/types.rs index 858907bafa..4dca6b173b 100644 --- a/common/cosmwasm-smart-contracts/contracts-common/src/types.rs +++ b/common/cosmwasm-smart-contracts/contracts-common/src/types.rs @@ -1,16 +1,17 @@ // Copyright 2022 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 +use cosmwasm_schema::cw_serde; use cosmwasm_std::Decimal; use cosmwasm_std::Uint128; -use schemars::JsonSchema; use serde::de::Error; -use serde::{Deserialize, Deserializer, Serialize}; +use serde::{Deserialize, Deserializer}; use std::fmt::{self, Display, Formatter}; use std::ops::Mul; use std::str::FromStr; use thiserror::Error; +/// Ed25519 public key strinfified into base58. pub type IdentityKey = String; pub type IdentityKeyRef<'a> = &'a str; @@ -32,9 +33,8 @@ pub enum ContractsCommonError { /// Percent represents a value between 0 and 100% /// (i.e. between 0.0 and 1.0) -#[derive( - Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Serialize, Deserialize, JsonSchema, -)] +#[cw_serde] +#[derive(Copy, Default, PartialOrd)] pub struct Percent(#[serde(deserialize_with = "de_decimal_percent")] Decimal); impl Percent { @@ -129,7 +129,7 @@ where // TODO: there's no reason this couldn't be used for proper binaries, but in that case // perhaps the struct should get renamed and moved to a "more" common crate -#[derive(Debug, Serialize, Deserialize)] +#[cw_serde] pub struct ContractBuildInformation { // VERGEN_BUILD_TIMESTAMP /// Provides the build timestamp, for example `2021-02-23T20:14:46.558472672+00:00`. diff --git a/common/cosmwasm-smart-contracts/mixnet-contract/Cargo.toml b/common/cosmwasm-smart-contracts/mixnet-contract/Cargo.toml index 736cedf911..47397d7b88 100644 --- a/common/cosmwasm-smart-contracts/mixnet-contract/Cargo.toml +++ b/common/cosmwasm-smart-contracts/mixnet-contract/Cargo.toml @@ -11,14 +11,16 @@ repository = { workspace = true } [dependencies] bs58 = "0.4.0" cosmwasm-std = { workspace = true } -serde = { version = "1.0", features = ["derive"] } +cosmwasm-schema = { workspace = true } +cw2 = { workspace = true, optional = true } +serde = { workspace = true, features = ["derive"] } serde_repr = "0.1" + +# we still have to preserve that import for `JsonSchema` for `Layer` type (since we can't use cw_serde macro due to custom serde impl) schemars = "0.8" thiserror = "1.0" contracts-common = { path = "../contracts-common", package = "nym-contracts-common", version = "0.5.0" } -# use 0.4.1 as that's the version used by cosmwasm-std 1.0.0 -# (and ideally we don't want to pull the same dependency twice) -serde-json-wasm = "=0.4.1" +serde-json-wasm = { workspace = true } humantime-serde = "1.1.1" # TO CHECK WHETHER STILL NEEDED: @@ -33,4 +35,5 @@ time = { version = "0.3.5", features = ["serde", "macros"] } [features] default = [] contract-testing = [] +schema = ["cw2"] generate-ts = ['ts-rs'] diff --git a/common/cosmwasm-smart-contracts/mixnet-contract/src/delegation.rs b/common/cosmwasm-smart-contracts/mixnet-contract/src/delegation.rs index 55294498b8..3a7cdd7702 100644 --- a/common/cosmwasm-smart-contracts/mixnet-contract/src/delegation.rs +++ b/common/cosmwasm-smart-contracts/mixnet-contract/src/delegation.rs @@ -1,15 +1,11 @@ // Copyright 2021-2023 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -// due to code generated by JsonSchema -#![allow(clippy::field_reassign_with_default)] - use crate::constants::TOKEN_SUPPLY; use crate::helpers::IntoBaseDecimal; use crate::{Addr, MixId}; +use cosmwasm_schema::cw_serde; use cosmwasm_std::{Coin, Decimal, StdResult}; -use schemars::JsonSchema; -use serde::{Deserialize, Serialize}; // just use a string representation of those so that we wouldn't need to bother with decoding bytes // and trying to figure out whether they're valid, etc @@ -37,7 +33,9 @@ pub fn generate_owner_storage_subkey( } } -#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq, JsonSchema)] +/// Information about tokens being delegated towards given mixnode in order to accrue rewards +/// with their work. +#[cw_serde] pub struct Delegation { /// Address of the owner of this delegation. pub owner: Addr, @@ -114,9 +112,13 @@ impl Delegation { } } -#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize, JsonSchema)] +/// Response containing paged list of all delegations made towards particular mixnode. +#[cw_serde] pub struct PagedMixNodeDelegationsResponse { + /// Each individual delegation made. pub delegations: Vec, + + /// Field indicating paging information for the following queries if the caller wishes to get further entries. pub start_next_after: Option, } @@ -129,9 +131,13 @@ impl PagedMixNodeDelegationsResponse { } } -#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize, JsonSchema)] +/// Response containing paged list of all delegations made by the particular address. +#[cw_serde] pub struct PagedDelegatorDelegationsResponse { + /// Each individual delegation made. pub delegations: Vec, + + /// Field indicating paging information for the following queries if the caller wishes to get further entries. pub start_next_after: Option<(MixId, OwnerProxySubKey)>, } @@ -147,9 +153,13 @@ impl PagedDelegatorDelegationsResponse { } } -#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize, JsonSchema)] +/// Response containing delegation details. +#[cw_serde] pub struct MixNodeDelegationResponse { + /// If the delegation exists, this field contains its detailed information. pub delegation: Option, + + /// Flag indicating whether the node towards which the delegation was made is still bonded in the network. pub mixnode_still_bonded: bool, } @@ -162,9 +172,13 @@ impl MixNodeDelegationResponse { } } -#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize, JsonSchema)] +/// Response containing paged list of all delegations currently active. +#[cw_serde] pub struct PagedAllDelegationsResponse { + /// Each individual delegation made. pub delegations: Vec, + + /// Field indicating paging information for the following queries if the caller wishes to get further entries. pub start_next_after: Option, } diff --git a/common/cosmwasm-smart-contracts/mixnet-contract/src/error.rs b/common/cosmwasm-smart-contracts/mixnet-contract/src/error.rs index 0b572183ac..6007884cc8 100644 --- a/common/cosmwasm-smart-contracts/mixnet-contract/src/error.rs +++ b/common/cosmwasm-smart-contracts/mixnet-contract/src/error.rs @@ -165,6 +165,9 @@ pub enum MixnetContractError { #[error("Family with head {head} does not exist!")] FamilyDoesNotExist { head: String }, + #[error("Family with label {label} does not exist!")] + FamilyLabelDoesNotExist { label: String }, + #[error("Family with label '{0}' already exists")] FamilyWithLabelExists(String), diff --git a/common/cosmwasm-smart-contracts/mixnet-contract/src/families.rs b/common/cosmwasm-smart-contracts/mixnet-contract/src/families.rs index 122e1690f6..5b44483c83 100644 --- a/common/cosmwasm-smart-contracts/mixnet-contract/src/families.rs +++ b/common/cosmwasm-smart-contracts/mixnet-contract/src/families.rs @@ -1,22 +1,34 @@ +// Copyright 2022-2023 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + use crate::{IdentityKey, IdentityKeyRef}; +use cosmwasm_schema::cw_serde; use cosmwasm_std::Addr; use schemars::JsonSchema; use serde::{Deserialize, Deserializer, Serialize, Serializer}; use std::fmt::{Display, Formatter}; use std::str::FromStr; +/// A group of mixnodes associated with particular staking entity. +/// When defined all nodes belonging to the same family will be prioritised to be put onto the same layer. #[cfg_attr(feature = "generate-ts", derive(ts_rs::TS))] #[cfg_attr( feature = "generate-ts", ts(export_to = "ts-packages/types/src/types/rust/NodeFamily.ts") )] -#[derive(Serialize, Deserialize, Debug, Clone, Eq, PartialEq, JsonSchema)] +#[cw_serde] pub struct Family { + /// Owner of this family. head: FamilyHead, + + /// Optional proxy (i.e. vesting contract address) used when creating the family. proxy: Option, + + /// Human readable label for this family. label: String, } +/// Head of particular family as identified by its identity key (i.e. public component of its ed25519 keypair stringified into base58). #[cfg_attr(feature = "generate-ts", derive(ts_rs::TS))] #[cfg_attr( feature = "generate-ts", @@ -62,8 +74,8 @@ impl Display for FamilyHead { } impl FamilyHead { - pub fn new(identity: IdentityKeyRef<'_>) -> Self { - FamilyHead(identity.to_string()) + pub fn new>(identity: S) -> Self { + FamilyHead(identity.into()) } pub fn identity(&self) -> IdentityKeyRef<'_> { @@ -99,6 +111,66 @@ impl Family { } } +/// Response containing paged list of all families registered in the contract. +#[cw_serde] +pub struct PagedFamiliesResponse { + /// The families registered in the contract. + pub families: Vec, + + /// Field indicating paging information for the following queries if the caller wishes to get further entries. + pub start_next_after: Option, +} + +/// Response containing paged list of all family members (of ALL families) registered in the contract. +#[cw_serde] +pub struct PagedMembersResponse { + /// The members alongside their family heads. + pub members: Vec<(IdentityKey, FamilyHead)>, + + /// Field indicating paging information for the following queries if the caller wishes to get further entries. + pub start_next_after: Option, +} + +/// Response containing family information. +#[cw_serde] +pub struct FamilyByHeadResponse { + /// The family head used for the query. + pub head: FamilyHead, + + /// If applicable, the family associated with the provided head. + pub family: Option, +} + +/// Response containing family information. +#[cw_serde] +pub struct FamilyByLabelResponse { + /// The family label used for the query. + pub label: String, + + /// If applicable, the family associated with the provided label. + pub family: Option, +} + +/// Response containing family members information. +#[cw_serde] +pub struct FamilyMembersByHeadResponse { + /// The family head used for the query. + pub head: FamilyHead, + + /// All members belonging to the specified family. + pub members: Vec, +} + +/// Response containing family members information. +#[cw_serde] +pub struct FamilyMembersByLabelResponse { + /// The family label used for the query. + pub label: String, + + /// All members belonging to the specified family. + pub members: Vec, +} + #[cfg(test)] mod tests { use super::*; diff --git a/common/cosmwasm-smart-contracts/mixnet-contract/src/gateway.rs b/common/cosmwasm-smart-contracts/mixnet-contract/src/gateway.rs index c312d42bf7..fd29090374 100644 --- a/common/cosmwasm-smart-contracts/mixnet-contract/src/gateway.rs +++ b/common/cosmwasm-smart-contracts/mixnet-contract/src/gateway.rs @@ -1,34 +1,56 @@ // Copyright 2021-2023 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -// due to code generated by JsonSchema -#![allow(clippy::field_reassign_with_default)] - use crate::{IdentityKey, SphinxKey}; +use cosmwasm_schema::cw_serde; use cosmwasm_std::{Addr, Coin}; -use schemars::JsonSchema; -use serde::{Deserialize, Serialize}; use std::cmp::Ordering; use std::fmt::Display; -#[derive(Clone, Debug, Deserialize, PartialEq, Eq, PartialOrd, Serialize, JsonSchema)] +/// Information provided by the node operator during bonding that are used to allow other entities to use the services of this node. +#[cw_serde] +#[derive(PartialOrd)] pub struct Gateway { + /// Network address of this gateway, for example 1.1.1.1 or foo.gateway.com pub host: String, + + /// Port used by this gateway for listening for mix packets. pub mix_port: u16, + + /// Port used by this gateway for listening for client requests. pub clients_port: u16, + + /// The physical, self-reported, location of this gateway. + // this field should be deprecated in favour of externally hosted information, like the mixnodes'. pub location: String, + + /// Base58-encoded x25519 public key used for sphinx key derivation. pub sphinx_key: SphinxKey, + /// Base58 encoded ed25519 EdDSA public key of the gateway used to derive shared keys with clients pub identity_key: IdentityKey, + + /// The self-reported semver version of this gateway. pub version: String, } -#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize, JsonSchema)] +/// Basic gateway information provided by the node operator. +#[cw_serde] pub struct GatewayBond { + /// Original amount pledged by the operator of this node. pub pledge_amount: Coin, + + /// Address of the owner of this gateway. pub owner: Addr, + + /// Block height at which this gateway has been bonded. pub block_height: u64, + + /// Information provided by the operator for the purposes of bonding. pub gateway: Gateway, + + /// Entity who bonded this gateway on behalf of the owner. + /// If exists, it's most likely the address of the vesting contract. pub proxy: Option, } @@ -117,7 +139,7 @@ impl Display for GatewayBond { feature = "generate-ts", ts(export_to = "ts-packages/types/src/types/rust/GatewayConfigUpdate.ts") )] -#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize, JsonSchema)] +#[cw_serde] pub struct GatewayConfigUpdate { pub host: String, pub mix_port: u16, @@ -132,10 +154,17 @@ impl GatewayConfigUpdate { } } -#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize, JsonSchema)] +/// Response containing paged list of all gateway bonds in the contract. +#[cw_serde] pub struct PagedGatewayResponse { + /// The gateway bond information present in the contract. pub nodes: Vec, + + /// Maximum number of entries that could be included in a response. `per_page <= nodes.len()` + // this field is rather redundant and should be deprecated. pub per_page: usize, + + /// Field indicating paging information for the following queries if the caller wishes to get further entries. pub start_next_after: Option, } @@ -153,15 +182,23 @@ impl PagedGatewayResponse { } } -#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize, JsonSchema)] +/// Response containing details of a gateway belonging to the particular owner. +#[cw_serde] pub struct GatewayOwnershipResponse { + /// Validated address of the gateway owner. pub address: Addr, + + /// If the provided address owns a gateway, this field contains its details. pub gateway: Option, } -#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize, JsonSchema)] +/// Response containing details of a gateway with the provided identity key. +#[cw_serde] pub struct GatewayBondResponse { + /// The identity key (base58-encoded ed25519 public key) of the gateway. pub identity: IdentityKey, + + /// If there exists a gateway with the provided identity key, this field contains its details. pub gateway: Option, } diff --git a/common/cosmwasm-smart-contracts/mixnet-contract/src/interval.rs b/common/cosmwasm-smart-contracts/mixnet-contract/src/interval.rs index 5afdbd827e..cee4cf02be 100644 --- a/common/cosmwasm-smart-contracts/mixnet-contract/src/interval.rs +++ b/common/cosmwasm-smart-contracts/mixnet-contract/src/interval.rs @@ -2,20 +2,20 @@ // SPDX-License-Identifier: Apache-2.0 use crate::error::MixnetContractError; -use crate::pending_events::{PendingEpochEvent, PendingIntervalEvent}; -use crate::{ - EpochEventId, EpochId, IntervalEventId, IntervalId, MixId, PendingEpochEventData, - PendingIntervalEventData, -}; +use crate::MixId; +use cosmwasm_schema::cw_serde; +use cosmwasm_schema::schemars::gen::SchemaGenerator; +use cosmwasm_schema::schemars::schema::{InstanceType, Schema, SchemaObject}; +use cosmwasm_schema::schemars::JsonSchema; use cosmwasm_std::{Addr, Env}; -use schemars::gen::SchemaGenerator; -use schemars::schema::{InstanceType, Schema, SchemaObject}; -use schemars::JsonSchema; use serde::{Deserialize, Serialize}; use std::fmt::{Display, Formatter}; use std::time::Duration; use time::OffsetDateTime; +pub type EpochId = u32; +pub type IntervalId = u32; + // internally, since version 0.3.6, time uses deserialize_any for deserialization, which can't be handled // by serde wasm. We could just downgrade to 0.3.5 and call it a day, but then it would break // when we decided to upgrade it at some point in the future. And then it would have been more problematic @@ -64,12 +64,15 @@ pub(crate) mod string_rfc3339_offset_date_time { } } -#[derive(Debug, Serialize, Deserialize, Eq, PartialEq)] +/// The status of the current rewarding epoch. +#[cw_serde] pub struct EpochStatus { // TODO: introduce mechanism to allow another validator to take over if no progress has been made in X blocks / Y seconds /// Specifies either, which validator is currently performing progression into the following epoch (if the epoch is currently being progressed), /// or which validator was responsible for progressing into the current epoch (if the epoch is currently in progress) pub being_advanced_by: Addr, + + /// The concrete state of the epoch. pub state: EpochState, } @@ -150,7 +153,9 @@ impl EpochStatus { } } -#[derive(Debug, Clone, Copy, Serialize, Deserialize, Eq, PartialEq)] +/// The state of the current rewarding epoch. +#[cw_serde] +#[derive(Copy)] pub enum EpochState { /// Represents the state of an epoch that's in progress (well, duh.) /// All actions are allowed to be issued. @@ -159,8 +164,10 @@ pub enum EpochState { /// Represents the state of an epoch when the rewarding entity has been decided on, /// and the mixnodes are in the process of being rewarded for their work in this epoch. Rewarding { + /// The id of the last node that has already received its rewards. last_rewarded: MixId, + /// The id of the last node that's going to be rewarded before progressing into the next state. final_node_id: MixId, // total_rewarded: u32, }, @@ -191,6 +198,7 @@ impl Display for EpochState { } } +/// Specification of a rewarding interval. #[cfg_attr(feature = "generate-ts", derive(ts_rs::TS))] #[cfg_attr( feature = "generate-ts", @@ -198,7 +206,10 @@ impl Display for EpochState { )] #[derive(Clone, Copy, Debug, Deserialize, PartialEq, Eq, Serialize)] pub struct Interval { + /// Monotonously increasing id of this interval. id: IntervalId, + + /// Number of epochs in this interval. epochs_in_interval: u32, // TODO add a better TS type generation @@ -207,10 +218,17 @@ pub struct Interval { // note: the `ts-rs failed to parse this attribute. It will be ignored.` warning emitted during // compilation is fine (I guess). `ts-rs` can't handle `with` serde attribute, but that's okay // since we explicitly specified this field should correspond to typescript's string + /// The timestamp indicating the start of the current rewarding epoch. current_epoch_start: OffsetDateTime, + + /// Monotonously increasing id of the current epoch in this interval. current_epoch_id: EpochId, + + /// The duration of all epochs in this interval. #[cfg_attr(feature = "generate-ts", ts(type = "{ secs: number; nanos: number; }"))] epoch_length: Duration, + + /// The total amount of elapsed epochs since the first epoch of the first interval. total_elapsed_epochs: EpochId, } @@ -455,11 +473,19 @@ impl Display for Interval { } } -#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Eq, Serialize)] +/// Information about the current rewarding interval. +#[cw_serde] pub struct CurrentIntervalResponse { + /// Detailed information about the underlying interval. pub interval: Interval, + + /// The current blocktime pub current_blocktime: u64, + + /// Flag indicating whether the current interval is over and it should be advanced. pub is_current_interval_over: bool, + + /// Flag indicating whether the current epoch is over and it should be advanced. pub is_current_epoch_over: bool, } @@ -489,87 +515,6 @@ impl CurrentIntervalResponse { } } -#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)] -pub struct PendingEpochEventsResponse { - pub seconds_until_executable: i64, - pub events: Vec, - pub start_next_after: Option, -} - -impl PendingEpochEventsResponse { - pub fn new( - seconds_until_executable: i64, - events: Vec, - start_next_after: Option, - ) -> Self { - PendingEpochEventsResponse { - seconds_until_executable, - events, - start_next_after, - } - } -} - -#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)] -pub struct PendingIntervalEventsResponse { - pub seconds_until_executable: i64, - pub events: Vec, - pub start_next_after: Option, -} - -impl PendingIntervalEventsResponse { - pub fn new( - seconds_until_executable: i64, - events: Vec, - start_next_after: Option, - ) -> Self { - PendingIntervalEventsResponse { - seconds_until_executable, - events, - start_next_after, - } - } -} - -#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] -pub struct PendingEpochEventResponse { - pub event_id: EpochEventId, - pub event: Option, -} - -impl PendingEpochEventResponse { - pub fn new(event_id: EpochEventId, event: Option) -> Self { - PendingEpochEventResponse { event_id, event } - } -} - -#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] -pub struct PendingIntervalEventResponse { - pub event_id: IntervalEventId, - pub event: Option, -} - -impl PendingIntervalEventResponse { - pub fn new(event_id: IntervalEventId, event: Option) -> Self { - PendingIntervalEventResponse { event_id, event } - } -} - -#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] -pub struct NumberOfPendingEventsResponse { - pub epoch_events: u32, - pub interval_events: u32, -} - -impl NumberOfPendingEventsResponse { - pub fn new(epoch_events: u32, interval_events: u32) -> Self { - Self { - epoch_events, - interval_events, - } - } -} - #[cfg(test)] mod tests { use super::*; diff --git a/common/cosmwasm-smart-contracts/mixnet-contract/src/lib.rs b/common/cosmwasm-smart-contracts/mixnet-contract/src/lib.rs index e2a4a67a56..50a5e90aec 100644 --- a/common/cosmwasm-smart-contracts/mixnet-contract/src/lib.rs +++ b/common/cosmwasm-smart-contracts/mixnet-contract/src/lib.rs @@ -11,14 +11,14 @@ pub mod events; pub mod families; pub mod gateway; pub mod helpers; -mod interval; +pub mod interval; pub mod mixnode; -mod msg; +pub mod msg; pub mod pending_events; pub mod reward_params; pub mod rewarding; pub mod signing_types; -mod types; +pub mod types; pub use contracts_common::types::*; pub use cosmwasm_std::{Addr, Coin, Decimal, Fraction}; @@ -26,25 +26,32 @@ pub use delegation::{ Delegation, PagedAllDelegationsResponse, PagedDelegatorDelegationsResponse, PagedMixNodeDelegationsResponse, }; +pub use families::{ + Family, FamilyByHeadResponse, FamilyByLabelResponse, FamilyHead, FamilyMembersByHeadResponse, + FamilyMembersByLabelResponse, PagedFamiliesResponse, PagedMembersResponse, +}; pub use gateway::{ Gateway, GatewayBond, GatewayBondResponse, GatewayConfigUpdate, GatewayOwnershipResponse, PagedGatewayResponse, }; pub use interval::{ - CurrentIntervalResponse, EpochState, EpochStatus, Interval, NumberOfPendingEventsResponse, - PendingEpochEventResponse, PendingEpochEventsResponse, PendingIntervalEventResponse, - PendingIntervalEventsResponse, + CurrentIntervalResponse, EpochId, EpochState, EpochStatus, Interval, IntervalId, }; pub use mixnode::{ Layer, MixNode, MixNodeBond, MixNodeConfigUpdate, MixNodeCostParams, MixNodeDetails, - MixNodeRewarding, MixOwnershipResponse, MixnodeDetailsResponse, PagedMixnodeBondsResponse, - RewardedSetNodeStatus, UnbondedMixnode, + MixNodeRewarding, MixOwnershipResponse, MixnodeDetailsByIdentityResponse, + MixnodeDetailsResponse, PagedMixnodeBondsResponse, RewardedSetNodeStatus, UnbondedMixnode, }; pub use msg::*; pub use pending_events::{ - PendingEpochEvent, PendingEpochEventData, PendingEpochEventKind, PendingIntervalEvent, - PendingIntervalEventData, PendingIntervalEventKind, + EpochEventId, IntervalEventId, NumberOfPendingEventsResponse, PendingEpochEvent, + PendingEpochEventData, PendingEpochEventKind, PendingEpochEventResponse, + PendingEpochEventsResponse, PendingIntervalEvent, PendingIntervalEventData, + PendingIntervalEventKind, PendingIntervalEventResponse, PendingIntervalEventsResponse, }; pub use reward_params::{IntervalRewardParams, IntervalRewardingParamsUpdate, RewardingParams}; +pub use rewarding::{ + EstimatedCurrentEpochRewardResponse, PagedRewardedSetResponse, PendingRewardResponse, +}; pub use signing_types::*; pub use types::*; diff --git a/common/cosmwasm-smart-contracts/mixnet-contract/src/mixnode.rs b/common/cosmwasm-smart-contracts/mixnet-contract/src/mixnode.rs index 69f1a89209..248bc7e143 100644 --- a/common/cosmwasm-smart-contracts/mixnet-contract/src/mixnode.rs +++ b/common/cosmwasm-smart-contracts/mixnet-contract/src/mixnode.rs @@ -11,19 +11,24 @@ use crate::reward_params::{NodeRewardParams, RewardingParams}; use crate::rewarding::helpers::truncate_reward; use crate::rewarding::RewardDistribution; use crate::{Delegation, EpochEventId, EpochId, IdentityKey, MixId, Percent, SphinxKey}; +use cosmwasm_schema::cw_serde; use cosmwasm_std::{Addr, Coin, Decimal, StdResult, Uint128}; use schemars::JsonSchema; -use serde::{Deserialize, Serialize}; use serde_repr::{Deserialize_repr, Serialize_repr}; +/// Current state of given node in the rewarded set. #[cfg_attr(feature = "generate-ts", derive(ts_rs::TS))] #[cfg_attr( feature = "generate-ts", ts(export_to = "ts-packages/types/src/types/rust/RewardedSetNodeStatus.ts") )] -#[derive(Clone, Copy, Debug, Deserialize, Serialize, JsonSchema, PartialEq, Eq)] +#[cw_serde] +#[derive(Copy)] pub enum RewardedSetNodeStatus { + /// Node that is currently active, i.e. is expected to be used by clients for mixing packets. Active, + + /// Node that is currently in standby, i.e. it's present in the rewarded set but is not active. Standby, } @@ -33,10 +38,16 @@ impl RewardedSetNodeStatus { } } -#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize, JsonSchema)] +/// Full details associated with given mixnode. +#[cw_serde] pub struct MixNodeDetails { + /// Basic bond information of this mixnode, such as owner address, original pledge, etc. pub bond_information: MixNodeBond, + + /// Details used for computation of rewarding related data. pub rewarding_details: MixNodeRewarding, + + /// Adjustments to the mixnode that are ought to happen during future epoch transitions. #[serde(default)] pub pending_changes: PendingMixNodeChanges, } @@ -86,7 +97,7 @@ impl MixNodeDetails { } } -#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize, JsonSchema)] +#[cw_serde] pub struct MixNodeRewarding { /// Information provided by the operator that influence the cost function. pub cost_params: MixNodeCostParams, @@ -464,8 +475,8 @@ impl MixNodeRewarding { } } -// operator information + data assigned by the contract(s) -#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize, JsonSchema)] +/// Basic mixnode information provided by the node operator. +#[cw_serde] pub struct MixNodeBond { /// Unique id assigned to the bonded mixnode. pub mix_id: MixId, @@ -533,21 +544,24 @@ impl MixNodeBond { } } -// information provided by the operator -#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize, JsonSchema)] +/// Information provided by the node operator during bonding that are used to allow other entities to use the services of this node. +#[cw_serde] #[cfg_attr(feature = "generate-ts", derive(ts_rs::TS))] #[cfg_attr( feature = "generate-ts", ts(export_to = "ts-packages/types/src/types/rust/Mixnode.ts") )] pub struct MixNode { - /// Network address of this mixnode, for example 1.1.1.1:1234 or foo.mixnode.com + /// Network address of this mixnode, for example 1.1.1.1 or foo.mixnode.com pub host: String, + /// Port used by this mixnode for listening for mix packets. pub mix_port: u16, + /// Port used by this mixnode for listening for verloc requests. pub verloc_port: u16, + /// Port used by this mixnode for its http(s) API pub http_api_port: u16, /// Base58-encoded x25519 public key used for sphinx key derivation. @@ -556,11 +570,15 @@ pub struct MixNode { /// Base58-encoded ed25519 EdDSA public key. pub identity_key: IdentityKey, + /// The self-reported semver version of this mixnode. pub version: String, } -#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize, JsonSchema)] +/// The cost parameters, or the cost function, defined for the particular mixnode that influences +/// how the rewards should be split between the node operator and its delegators. +#[cw_serde] pub struct MixNodeCostParams { + /// The profit margin of the associated mixnode, i.e. the desired percent of the reward to be distributed to the operator. pub profit_margin_percent: Percent, /// Operating cost of the associated mixnode per the entire interval. @@ -633,7 +651,8 @@ impl From for u8 { feature = "generate-ts", ts(export_to = "ts-packages/types/src/types/rust/PendingMixnodeChanges.ts") )] -#[derive(Clone, Copy, Debug, Default, Deserialize, PartialEq, Eq, Serialize, JsonSchema)] +#[cw_serde] +#[derive(Default, Copy)] pub struct PendingMixNodeChanges { pub pledge_change: Option, // pub cost_params_change: Option, @@ -647,21 +666,27 @@ impl PendingMixNodeChanges { } } +/// Basic information of a node that used to be part of the mix network but has already unbonded. #[cfg_attr(feature = "generate-ts", derive(ts_rs::TS))] #[cfg_attr( feature = "generate-ts", ts(export_to = "ts-packages/types/src/types/rust/UnbondedMixnode.ts") )] -#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize, JsonSchema)] +#[cw_serde] pub struct UnbondedMixnode { + /// Base58-encoded ed25519 EdDSA public key. pub identity_key: IdentityKey, + /// Address of the owner of this mixnode. #[cfg_attr(feature = "generate-ts", ts(type = "string"))] pub owner: Addr, + /// Entity who bonded this mixnode on behalf of the owner. + /// If exists, it's most likely the address of the vesting contract. #[cfg_attr(feature = "generate-ts", ts(type = "string | null"))] pub proxy: Option, + /// Block height at which this mixnode has unbonded. #[cfg_attr(feature = "generate-ts", ts(type = "number"))] pub unbonding_height: u64, } @@ -671,7 +696,7 @@ pub struct UnbondedMixnode { feature = "generate-ts", ts(export_to = "ts-packages/types/src/types/rust/MixNodeConfigUpdate.ts") )] -#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize, JsonSchema)] +#[cw_serde] pub struct MixNodeConfigUpdate { pub host: String, pub mix_port: u16, @@ -686,10 +711,17 @@ impl MixNodeConfigUpdate { } } -#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize, JsonSchema)] +/// Response containing paged list of all mixnode bonds in the contract. +#[cw_serde] pub struct PagedMixnodeBondsResponse { + /// The mixnode bond information present in the contract. pub nodes: Vec, + + /// Maximum number of entries that could be included in a response. `per_page <= nodes.len()` + // this field is rather redundant and should be deprecated. pub per_page: usize, + + /// Field indicating paging information for the following queries if the caller wishes to get further entries. pub start_next_after: Option, } @@ -703,10 +735,19 @@ impl PagedMixnodeBondsResponse { } } -#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize, JsonSchema)] +/// Response containing paged list of all mixnode details in the contract. +#[cw_serde] pub struct PagedMixnodesDetailsResponse { + /// All mixnode details stored in the contract. + /// Apart from the basic bond information it also contains details required for all future reward calculation + /// as well as any pending changes requested by the operator. pub nodes: Vec, + + /// Maximum number of entries that could be included in a response. `per_page <= nodes.len()` + // this field is rather redundant and should be deprecated. pub per_page: usize, + + /// Field indicating paging information for the following queries if the caller wishes to get further entries. pub start_next_after: Option, } @@ -724,10 +765,17 @@ impl PagedMixnodesDetailsResponse { } } -#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize, JsonSchema)] +/// Response containing paged list of all mixnodes that have ever unbonded. +#[cw_serde] pub struct PagedUnbondedMixnodesResponse { + /// The past ids of unbonded mixnodes alongside their basic information such as the owner or the identity key. pub nodes: Vec<(MixId, UnbondedMixnode)>, + + /// Maximum number of entries that could be included in a response. `per_page <= nodes.len()` + // this field is rather redundant and should be deprecated. pub per_page: usize, + + /// Field indicating paging information for the following queries if the caller wishes to get further entries. pub start_next_after: Option, } @@ -745,33 +793,68 @@ impl PagedUnbondedMixnodesResponse { } } -#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize, JsonSchema)] +/// Response containing details of a mixnode belonging to the particular owner. +#[cw_serde] pub struct MixOwnershipResponse { + /// Validated address of the mixnode owner. pub address: Addr, + + /// If the provided address owns a mixnode, this field contains its detailed information. pub mixnode_details: Option, } -#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize, JsonSchema)] +/// Response containing details of a mixnode with the provided id. +#[cw_serde] pub struct MixnodeDetailsResponse { + /// Id of the requested mixnode. pub mix_id: MixId, + + /// If there exists a mixnode with the provided id, this field contains its detailed information. pub mixnode_details: Option, } -#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize, JsonSchema)] +/// Response containing details of a bonded mixnode with the provided identity key. +#[cw_serde] +pub struct MixnodeDetailsByIdentityResponse { + /// The identity key (base58-encoded ed25519 public key) of the mixnode. + pub identity_key: IdentityKey, + + /// If there exists a bonded mixnode with the provided identity key, this field contains its detailed information. + pub mixnode_details: Option, +} + +/// Response containing rewarding information of a mixnode with the provided id. +#[cw_serde] pub struct MixnodeRewardingDetailsResponse { + /// Id of the requested mixnode. pub mix_id: MixId, + + /// If there exists a mixnode with the provided id, this field contains its rewarding information. pub rewarding_details: Option, } -#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize, JsonSchema)] +/// Response containing basic information of an unbonded mixnode with the provided id. +#[cw_serde] pub struct UnbondedMixnodeResponse { + /// Id of the requested mixnode. pub mix_id: MixId, + + /// If there existed a mixnode with the provided id, this field contains its basic information. pub unbonded_info: Option, } -#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Eq, Serialize, JsonSchema)] +/// Response containing the current state of the stake saturation of a mixnode with the provided id. +#[cw_serde] pub struct StakeSaturationResponse { + /// Id of the requested mixnode. pub mix_id: MixId, + + /// The current stake saturation of this node that is indirectly used in reward calculation formulas. + /// Note that it can't be larger than 1. pub current_saturation: Option, + + /// The current, absolute, stake saturation of this node. + /// Note that as the name suggests it can be larger than 1. + /// However, anything beyond that value has no effect on the total node reward. pub uncapped_saturation: Option, } diff --git a/common/cosmwasm-smart-contracts/mixnet-contract/src/msg.rs b/common/cosmwasm-smart-contracts/mixnet-contract/src/msg.rs index 24603147a0..ca154e231b 100644 --- a/common/cosmwasm-smart-contracts/mixnet-contract/src/msg.rs +++ b/common/cosmwasm-smart-contracts/mixnet-contract/src/msg.rs @@ -1,28 +1,54 @@ // Copyright 2021-2023 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use crate::delegation::OwnerProxySubKey; +use crate::delegation::{self, OwnerProxySubKey}; use crate::error::MixnetContractError; use crate::families::FamilyHead; -use crate::gateway::GatewayConfigUpdate; +use crate::gateway::{Gateway, GatewayConfigUpdate}; use crate::helpers::IntoBaseDecimal; -use crate::mixnode::{MixNodeConfigUpdate, MixNodeCostParams}; +use crate::mixnode::{Layer, MixNode, MixNodeConfigUpdate, MixNodeCostParams}; +use crate::pending_events::{EpochEventId, IntervalEventId}; use crate::reward_params::{ IntervalRewardParams, IntervalRewardingParamsUpdate, Performance, RewardingParams, }; -use crate::{ - delegation, ContractStateParams, EpochEventId, IntervalEventId, Layer, LayerAssignment, MixId, - Percent, -}; -use crate::{Gateway, IdentityKey, MixNode}; -use contracts_common::signing::MessageSignature; +use crate::types::{ContractStateParams, LayerAssignment, MixId}; +use contracts_common::{signing::MessageSignature, IdentityKey, Percent}; +use cosmwasm_schema::cw_serde; use cosmwasm_std::{Coin, Decimal}; -use schemars::JsonSchema; -use serde::{Deserialize, Serialize}; use std::time::Duration; -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] -#[serde(rename_all = "snake_case")] +#[cfg(feature = "schema")] +use crate::{ + delegation::{ + MixNodeDelegationResponse, PagedAllDelegationsResponse, PagedDelegatorDelegationsResponse, + PagedMixNodeDelegationsResponse, + }, + families::{ + FamilyByHeadResponse, FamilyByLabelResponse, FamilyMembersByHeadResponse, + FamilyMembersByLabelResponse, PagedFamiliesResponse, PagedMembersResponse, + }, + gateway::{GatewayBondResponse, GatewayOwnershipResponse, PagedGatewayResponse}, + interval::{CurrentIntervalResponse, EpochStatus}, + mixnode::{ + MixOwnershipResponse, MixnodeDetailsByIdentityResponse, MixnodeDetailsResponse, + MixnodeRewardingDetailsResponse, PagedMixnodeBondsResponse, PagedMixnodesDetailsResponse, + PagedUnbondedMixnodesResponse, StakeSaturationResponse, UnbondedMixnodeResponse, + }, + pending_events::{ + NumberOfPendingEventsResponse, PendingEpochEventResponse, PendingEpochEventsResponse, + PendingIntervalEventResponse, PendingIntervalEventsResponse, + }, + rewarding::{ + EstimatedCurrentEpochRewardResponse, PagedRewardedSetResponse, PendingRewardResponse, + }, + types::{ContractState, LayerDistribution}, +}; +#[cfg(feature = "schema")] +use contracts_common::{signing::Nonce, ContractBuildInformation}; +#[cfg(feature = "schema")] +use cosmwasm_schema::QueryResponses; + +#[cw_serde] pub struct InstantiateMsg { pub rewarding_validator_address: String, pub vesting_contract_address: String, @@ -33,8 +59,7 @@ pub struct InstantiateMsg { pub initial_rewarding_params: InitialRewardingParams, } -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] -#[serde(rename_all = "snake_case")] +#[cw_serde] pub struct InitialRewardingParams { pub initial_reward_pool: Decimal, pub initial_staking_supply: Decimal, @@ -76,8 +101,7 @@ impl InitialRewardingParams { } } -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] -#[serde(rename_all = "snake_case")] +#[cw_serde] pub enum ExecuteMsg { AssignNodeLayer { mix_id: MixId, @@ -365,177 +389,376 @@ impl ExecuteMsg { } } -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] -#[serde(rename_all = "snake_case")] +#[cw_serde] +#[cfg_attr(feature = "schema", derive(QueryResponses))] pub enum QueryMsg { // families + /// Gets the list of families registered in this contract. + #[cfg_attr(feature = "schema", returns(PagedFamiliesResponse))] GetAllFamiliesPaged { + /// Controls the maximum number of entries returned by the query. Note that too large values will be overwritten by a saner default. limit: Option, + + /// Pagination control for the values returned by the query. Note that the provided value itself will **not** be used for the response. start_after: Option, }, + + /// Gets the list of all family members registered in this contract. + #[cfg_attr(feature = "schema", returns(PagedMembersResponse))] GetAllMembersPaged { + /// Controls the maximum number of entries returned by the query. Note that too large values will be overwritten by a saner default. limit: Option, + + /// Pagination control for the values returned by the query. Note that the provided value itself will **not** be used for the response. start_after: Option, }, - GetFamilyByHead { - head: String, - }, - GetFamilyByLabel { - label: String, - }, - GetFamilyMembersByHead { - head: String, - }, - GetFamilyMembersByLabel { - label: String, - }, + + /// Attempts to lookup family information given the family head. + #[cfg_attr(feature = "schema", returns(FamilyByHeadResponse))] + GetFamilyByHead { head: String }, + + /// Attempts to lookup family information given the family label. + #[cfg_attr(feature = "schema", returns(FamilyByLabelResponse))] + GetFamilyByLabel { label: String }, + + /// Attempts to retrieve family members given the family head. + #[cfg_attr(feature = "schema", returns(FamilyMembersByHeadResponse))] + GetFamilyMembersByHead { head: String }, + + /// Attempts to retrieve family members given the family label. + #[cfg_attr(feature = "schema", returns(FamilyMembersByLabelResponse))] + GetFamilyMembersByLabel { label: String }, + // state/sys-params-related + /// Gets build information of this contract, such as the commit hash used for the build or rustc version. + #[cfg_attr(feature = "schema", returns(ContractBuildInformation))] GetContractVersion {}, + + /// Gets the stored contract version information that's required by the CW2 spec interface for migrations. #[serde(rename = "get_cw2_contract_version")] + #[cfg_attr(feature = "schema", returns(cw2::ContractVersion))] GetCW2ContractVersion {}, + + /// Gets the address of the validator that's allowed to send rewarding transactions and transition the epoch. + #[cfg_attr(feature = "schema", returns(String))] GetRewardingValidatorAddress {}, + + /// Gets the contract parameters that could be adjusted in a transaction by the contract admin. + #[cfg_attr(feature = "schema", returns(ContractStateParams))] GetStateParams {}, + + /// Gets the current state of the contract. + #[cfg_attr(feature = "schema", returns(ContractState))] GetState {}, + + /// Gets the current parameters used for reward calculation. + #[cfg_attr(feature = "schema", returns(RewardingParams))] GetRewardingParams {}, + + /// Gets the status of the current rewarding epoch. + #[cfg_attr(feature = "schema", returns(EpochStatus))] GetEpochStatus {}, + + /// Get the details of the current rewarding interval. + #[cfg_attr(feature = "schema", returns(CurrentIntervalResponse))] GetCurrentIntervalDetails {}, + + /// Gets the current list of mixnodes in the rewarded set. + #[cfg_attr(feature = "schema", returns(PagedRewardedSetResponse))] GetRewardedSet { + /// Controls the maximum number of entries returned by the query. Note that too large values will be overwritten by a saner default. limit: Option, + + /// Pagination control for the values returned by the query. Note that the provided value itself will **not** be used for the response. start_after: Option, }, // mixnode-related: + /// Gets the basic list of all currently bonded mixnodes. + #[cfg_attr(feature = "schema", returns(PagedMixnodeBondsResponse))] GetMixNodeBonds { + /// Controls the maximum number of entries returned by the query. Note that too large values will be overwritten by a saner default. limit: Option, + + /// Pagination control for the values returned by the query. Note that the provided value itself will **not** be used for the response. start_after: Option, }, + + /// Gets the detailed list of all currently bonded mixnodes. + #[cfg_attr(feature = "schema", returns(PagedMixnodesDetailsResponse))] GetMixNodesDetailed { + /// Controls the maximum number of entries returned by the query. Note that too large values will be overwritten by a saner default. limit: Option, + + /// Pagination control for the values returned by the query. Note that the provided value itself will **not** be used for the response. start_after: Option, }, + + /// Gets the basic list of all unbonded mixnodes. + #[cfg_attr(feature = "schema", returns(PagedUnbondedMixnodesResponse))] GetUnbondedMixNodes { + /// Controls the maximum number of entries returned by the query. Note that too large values will be overwritten by a saner default. limit: Option, + + /// Pagination control for the values returned by the query. Note that the provided value itself will **not** be used for the response. start_after: Option, }, + + /// Gets the basic list of all unbonded mixnodes that belonged to a particular owner. + #[cfg_attr(feature = "schema", returns(PagedUnbondedMixnodesResponse))] GetUnbondedMixNodesByOwner { + /// The address of the owner of the the mixnodes used for the query. owner: String, + + /// Controls the maximum number of entries returned by the query. Note that too large values will be overwritten by a saner default. limit: Option, + + /// Pagination control for the values returned by the query. Note that the provided value itself will **not** be used for the response. start_after: Option, }, + + /// Gets the basic list of all unbonded mixnodes that used the particular identity key. + #[cfg_attr(feature = "schema", returns(PagedUnbondedMixnodesResponse))] GetUnbondedMixNodesByIdentityKey { + /// The identity key (base58-encoded ed25519 public key) of the mixnode used for the query. identity_key: String, + + /// Controls the maximum number of entries returned by the query. Note that too large values will be overwritten by a saner default. limit: Option, + + /// Pagination control for the values returned by the query. Note that the provided value itself will **not** be used for the response. start_after: Option, }, + + /// Gets the detailed mixnode information belonging to the particular owner. + #[cfg_attr(feature = "schema", returns(MixOwnershipResponse))] GetOwnedMixnode { + /// Address of the mixnode owner to use for the query. address: String, }, + + /// Gets the detailed mixnode information of a node with the provided id. + #[cfg_attr(feature = "schema", returns(MixnodeDetailsResponse))] GetMixnodeDetails { + /// Id of the node to query. mix_id: MixId, }, + + /// Gets the rewarding information of a mixnode with the provided id. + #[cfg_attr(feature = "schema", returns(MixnodeRewardingDetailsResponse))] GetMixnodeRewardingDetails { + /// Id of the node to query. mix_id: MixId, }, + + /// Gets the stake saturation of a mixnode with the provided id. + #[cfg_attr(feature = "schema", returns(StakeSaturationResponse))] GetStakeSaturation { + /// Id of the node to query. mix_id: MixId, }, + + /// Gets the basic information of an unbonded mixnode with the provided id. + #[cfg_attr(feature = "schema", returns(UnbondedMixnodeResponse))] GetUnbondedMixNodeInformation { + /// Id of the node to query. mix_id: MixId, }, + + /// Gets the detailed mixnode information of a node given its current identity key. + #[cfg_attr(feature = "schema", returns(MixnodeDetailsByIdentityResponse))] GetBondedMixnodeDetailsByIdentity { + /// The identity key (base58-encoded ed25519 public key) of the mixnode used for the query. mix_identity: IdentityKey, }, + + /// Gets the current layer configuration of the mix network. + #[cfg_attr(feature = "schema", returns(LayerDistribution))] GetLayerDistribution {}, + // gateway-related: + /// Gets the basic list of all currently bonded gateways. + #[cfg_attr(feature = "schema", returns(PagedGatewayResponse))] GetGateways { + /// Pagination control for the values returned by the query. Note that the provided value itself will **not** be used for the response. start_after: Option, + + /// Controls the maximum number of entries returned by the query. Note that too large values will be overwritten by a saner default. limit: Option, }, + + /// Gets the gateway details of a node given its identity key. + #[cfg_attr(feature = "schema", returns(GatewayBondResponse))] GetGatewayBond { + /// The identity key (base58-encoded ed25519 public key) of the gateway used for the query. identity: IdentityKey, }, + + /// Gets the detailed gateway information belonging to the particular owner. + #[cfg_attr(feature = "schema", returns(GatewayOwnershipResponse))] GetOwnedGateway { + /// Address of the gateway owner to use for the query. address: String, }, // delegation-related: - // gets all [paged] delegations associated with particular mixnode + /// Gets all delegations associated with particular mixnode + #[cfg_attr(feature = "schema", returns(PagedMixNodeDelegationsResponse))] GetMixnodeDelegations { + /// Id of the node to query. mix_id: MixId, - // since `start_after` is user-provided input, we can't use `Addr` as we - // can't guarantee it's validated. - start_after: Option, + + /// Pagination control for the values returned by the query. Note that the provided value itself will **not** be used for the response. + start_after: Option, + + /// Controls the maximum number of entries returned by the query. Note that too large values will be overwritten by a saner default. limit: Option, }, - // gets all [paged] delegations associated with particular delegator + + /// Gets all delegations associated with particular delegator + #[cfg_attr(feature = "schema", returns(PagedDelegatorDelegationsResponse))] GetDelegatorDelegations { // since `delegator` is user-provided input, we can't use `Addr` as we // can't guarantee it's validated. + /// The address of the owner of the delegations. delegator: String, + + /// Pagination control for the values returned by the query. Note that the provided value itself will **not** be used for the response. start_after: Option<(MixId, OwnerProxySubKey)>, + + /// Controls the maximum number of entries returned by the query. Note that too large values will be overwritten by a saner default. limit: Option, }, - // gets delegation associated with particular mixnode, delegator pair + + /// Gets delegation information associated with particular mixnode - delegator pair + #[cfg_attr(feature = "schema", returns(MixNodeDelegationResponse))] GetDelegationDetails { + /// Id of the node to query. mix_id: MixId, + + /// The address of the owner of the delegation. delegator: String, + + /// Entity who made the delegation on behalf of the owner. + /// If present, it's most likely the address of the vesting contract. proxy: Option, }, - // gets all delegations in the system + + /// Gets all delegations in the system + #[cfg_attr(feature = "schema", returns(PagedAllDelegationsResponse))] GetAllDelegations { + /// Pagination control for the values returned by the query. Note that the provided value itself will **not** be used for the response. start_after: Option, + + /// Controls the maximum number of entries returned by the query. Note that too large values will be overwritten by a saner default. limit: Option, }, // rewards related + /// Gets the reward amount accrued by the node operator that has not yet been claimed. + #[cfg_attr(feature = "schema", returns(PendingRewardResponse))] GetPendingOperatorReward { + /// Address of the operator to use for the query. address: String, }, + + /// Gets the reward amount accrued by the particular mixnode that has not yet been claimed. + #[cfg_attr(feature = "schema", returns(PendingRewardResponse))] GetPendingMixNodeOperatorReward { + /// Id of the node to query. mix_id: MixId, }, + + /// Gets the reward amount accrued by the particular delegator that has not yet been claimed. + #[cfg_attr(feature = "schema", returns(PendingRewardResponse))] GetPendingDelegatorReward { + /// Address of the delegator to use for the query. address: String, + + /// Id of the node to query. mix_id: MixId, + + /// Entity who made the delegation on behalf of the owner. + /// If present, it's most likely the address of the vesting contract. proxy: Option, }, - // given the provided performance, estimate the reward at the end of the current epoch + + /// Given the provided node performance, attempt to estimate the operator reward for the current epoch. + #[cfg_attr(feature = "schema", returns(EstimatedCurrentEpochRewardResponse))] GetEstimatedCurrentEpochOperatorReward { + /// Id of the node to query. mix_id: MixId, + + /// The estimated performance for the current epoch of the given node. estimated_performance: Performance, }, + + /// Given the provided node performance, attempt to estimate the delegator reward for the current epoch. + #[cfg_attr(feature = "schema", returns(EstimatedCurrentEpochRewardResponse))] GetEstimatedCurrentEpochDelegatorReward { + /// Address of the delegator to use for the query. address: String, + + /// Id of the node to query. mix_id: MixId, + + /// Entity who made the delegation on behalf of the owner. + /// If present, it's most likely the address of the vesting contract. proxy: Option, + + /// The estimated performance for the current epoch of the given node. estimated_performance: Performance, }, // interval-related + /// Gets the list of all currently pending epoch events that will be resolved once the current epoch finishes. + #[cfg_attr(feature = "schema", returns(PendingEpochEventsResponse))] GetPendingEpochEvents { + /// Controls the maximum number of entries returned by the query. Note that too large values will be overwritten by a saner default. limit: Option, + + /// Pagination control for the values returned by the query. Note that the provided value itself will **not** be used for the response. start_after: Option, }, + + /// Gets the list of all currently pending interval events that will be resolved once the current interval finishes. + #[cfg_attr(feature = "schema", returns(PendingIntervalEventsResponse))] GetPendingIntervalEvents { + /// Controls the maximum number of entries returned by the query. Note that too large values will be overwritten by a saner default. limit: Option, + + /// Pagination control for the values returned by the query. Note that the provided value itself will **not** be used for the response. start_after: Option, }, + + /// Gets detailed information about a pending epoch event given its id. + #[cfg_attr(feature = "schema", returns(PendingEpochEventResponse))] GetPendingEpochEvent { + /// The unique id associated with the event. event_id: EpochEventId, }, + + /// Gets detailed information about a pending interval event given its id. + #[cfg_attr(feature = "schema", returns(PendingIntervalEventResponse))] GetPendingIntervalEvent { + /// The unique id associated with the event. event_id: IntervalEventId, }, + + /// Gets the information about the number of currently pending epoch and interval events. + #[cfg_attr(feature = "schema", returns(NumberOfPendingEventsResponse))] GetNumberOfPendingEvents {}, // signing-related + /// Gets the signing nonce associated with the particular cosmos address. + #[cfg_attr(feature = "schema", returns(Nonce))] GetSigningNonce { + /// Cosmos address used for the query of the signing nonce. address: String, }, } -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] -#[serde(rename_all = "snake_case")] +#[cw_serde] pub struct MigrateMsg { pub vesting_contract_address: Option, } diff --git a/common/cosmwasm-smart-contracts/mixnet-contract/src/pending_events.rs b/common/cosmwasm-smart-contracts/mixnet-contract/src/pending_events.rs index 5f4db1ab90..595839c99f 100644 --- a/common/cosmwasm-smart-contracts/mixnet-contract/src/pending_events.rs +++ b/common/cosmwasm-smart-contracts/mixnet-contract/src/pending_events.rs @@ -3,49 +3,95 @@ use crate::mixnode::MixNodeCostParams; use crate::reward_params::IntervalRewardingParamsUpdate; -use crate::{BlockHeight, EpochEventId, IntervalEventId, MixId}; +use crate::{BlockHeight, MixId}; +use cosmwasm_schema::cw_serde; use cosmwasm_std::{Addr, Coin}; -use serde::{Deserialize, Serialize}; -#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] +pub type EpochEventId = u32; +pub type IntervalEventId = u32; + +/// A request made at some point in the current epoch that's going to get resolved once the epoch rolls over. +#[cw_serde] pub struct PendingEpochEvent { + /// The unique id associated with the event. pub id: EpochEventId, + + /// The underlying event details, containing its type and information on how it should get resolved. pub event: PendingEpochEventData, } -#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] +/// Details of a particular pending epoch event. +#[cw_serde] pub struct PendingEpochEventData { + /// The block height at which the request has been made. pub created_at: BlockHeight, + + /// The underlying event data, containing its concrete type and information on how it should get resolved. pub kind: PendingEpochEventKind, } -#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] +/// Enum encompassing all possible epoch events. +#[cw_serde] pub enum PendingEpochEventKind { // can't just pass the `Delegation` struct here as it's impossible to determine // `cumulative_reward_ratio` ahead of time + /// Request to create a delegation towards particular mixnode. + /// Note that if such delegation already exists, it will get updated with the provided token amount. Delegate { + /// The address of the owner of the delegation. owner: Addr, + + /// The id of the mixnode used for the delegation. mix_id: MixId, + + /// The amount of tokens to use for the delegation. amount: Coin, + + /// Entity who made the delegation on behalf of the owner. + /// If present, it's most likely the address of the vesting contract. proxy: Option, }, + + /// Request to remove delegation from particular mixnode. Undelegate { + /// The address of the owner of the delegation. owner: Addr, + + /// The id of the mixnode used for the delegation. mix_id: MixId, + + /// Entity who made the delegation on behalf of the owner. + /// If present, it's most likely the address of the vesting contract. proxy: Option, }, + + /// Request to pledge more tokens (by the node operator) towards its node. PledgeMore { + /// The id of the mixnode that will have its pledge updated. mix_id: MixId, + + /// The amount of additional tokens to use by the pledge. amount: Coin, }, + + /// Request to decrease amount of pledged tokens (by the node operator) from its node. DecreasePledge { + /// The id of the mixnode that will have its pledge updated. mix_id: MixId, + + /// The amount of tokens that should be removed from the pledge. decrease_by: Coin, }, + + /// Request to unbond a mixnode and completely remove it from the network. UnbondMixnode { + /// The id of the mixnode that will get unbonded. mix_id: MixId, }, + + /// Request to update the current size of the active set. UpdateActiveSetSize { + /// The new desired size of the active set. new_size: u32, }, } @@ -68,29 +114,50 @@ impl From<(EpochEventId, PendingEpochEventData)> for PendingEpochEvent { } } -#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] +/// A request made at some point in the current interval that's going to get resolved once the interval rolls over. +#[cw_serde] pub struct PendingIntervalEvent { + /// The unique id associated with the event. pub id: IntervalEventId, + + /// The underlying event details, containing its type and information on how it should get resolved. pub event: PendingIntervalEventData, } -#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] +/// Details of a particular pending interval event. +#[cw_serde] pub struct PendingIntervalEventData { + /// The block height at which the request has been made. pub created_at: BlockHeight, + + /// The underlying event data, containing its concrete type and information on how it should get resolved. pub kind: PendingIntervalEventKind, } -#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] +/// Enum encompassing all possible interval events. +#[cw_serde] pub enum PendingIntervalEventKind { + /// Request to update cost parameters of given mixnode. ChangeMixCostParams { + /// The id of the mixnode that will have its cost parameters updated. mix_id: MixId, + + /// The new updated cost function of this mixnode. new_costs: MixNodeCostParams, }, + + /// Request to update the underlying rewarding parameters used by the system UpdateRewardingParams { + /// The detailed specification of the update. update: IntervalRewardingParamsUpdate, }, + + /// Request to change the next interval configuration. UpdateIntervalConfig { + /// The new number of epochs in intervals. epochs_in_interval: u32, + + /// The new epoch duration. epoch_duration_secs: u64, }, } @@ -112,3 +179,102 @@ impl From<(IntervalEventId, PendingIntervalEventData)> for PendingIntervalEvent } } } + +/// Response containing all currently pending epoch events that will be resolved once the current epoch finishes. +#[cw_serde] +pub struct PendingEpochEventsResponse { + /// Amount of seconds until the events would be eligible to be resolved. + /// It's equivalent to the time until the current epoch finishes. + pub seconds_until_executable: i64, + + /// The currently pending events. + pub events: Vec, + + /// Field indicating paging information for the following queries if the caller wishes to get further entries. + pub start_next_after: Option, +} + +impl PendingEpochEventsResponse { + pub fn new( + seconds_until_executable: i64, + events: Vec, + start_next_after: Option, + ) -> Self { + PendingEpochEventsResponse { + seconds_until_executable, + events, + start_next_after, + } + } +} + +/// Response containing all currently pending interval events that will be resolved once the current interval finishes. +#[cw_serde] +pub struct PendingIntervalEventsResponse { + /// Amount of seconds until the events would be eligible to be resolved. + /// It's equivalent to the time until the current interval finishes. + pub seconds_until_executable: i64, + + /// The currently pending events. + pub events: Vec, + + /// Field indicating paging information for the following queries if the caller wishes to get further entries. + pub start_next_after: Option, +} + +impl PendingIntervalEventsResponse { + pub fn new( + seconds_until_executable: i64, + events: Vec, + start_next_after: Option, + ) -> Self { + PendingIntervalEventsResponse { + seconds_until_executable, + events, + start_next_after, + } + } +} + +#[cw_serde] +pub struct PendingEpochEventResponse { + pub event_id: EpochEventId, + pub event: Option, +} + +impl PendingEpochEventResponse { + pub fn new(event_id: EpochEventId, event: Option) -> Self { + PendingEpochEventResponse { event_id, event } + } +} + +#[cw_serde] +pub struct PendingIntervalEventResponse { + pub event_id: IntervalEventId, + pub event: Option, +} + +impl PendingIntervalEventResponse { + pub fn new(event_id: IntervalEventId, event: Option) -> Self { + PendingIntervalEventResponse { event_id, event } + } +} + +/// Response containing number of currently pending epoch and interval events. +#[cw_serde] +pub struct NumberOfPendingEventsResponse { + /// The number of the currently pending epoch events. + pub epoch_events: u32, + + /// The number of the currently pending epoch events. + pub interval_events: u32, +} + +impl NumberOfPendingEventsResponse { + pub fn new(epoch_events: u32, interval_events: u32) -> Self { + Self { + epoch_events, + interval_events, + } + } +} diff --git a/common/cosmwasm-smart-contracts/mixnet-contract/src/reward_params.rs b/common/cosmwasm-smart-contracts/mixnet-contract/src/reward_params.rs index 74777eb99f..4928bd6a1e 100644 --- a/common/cosmwasm-smart-contracts/mixnet-contract/src/reward_params.rs +++ b/common/cosmwasm-smart-contracts/mixnet-contract/src/reward_params.rs @@ -3,9 +3,8 @@ use crate::helpers::IntoBaseDecimal; use crate::{error::MixnetContractError, Percent}; +use cosmwasm_schema::cw_serde; use cosmwasm_std::Decimal; -use schemars::JsonSchema; -use serde::{Deserialize, Serialize}; pub type Performance = Percent; @@ -15,7 +14,8 @@ pub type Performance = Percent; feature = "generate-ts", ts(export_to = "ts-packages/types/src/types/rust/IntervalRewardParams.ts") )] -#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Eq, PartialOrd, Serialize, JsonSchema)] +#[cw_serde] +#[derive(Copy)] pub struct IntervalRewardParams { /// Current value of the rewarding pool. /// It is expected to be constant throughout the interval. @@ -74,21 +74,26 @@ impl IntervalRewardParams { } } +/// Parameters used for reward calculation. #[cfg_attr(feature = "generate-ts", derive(ts_rs::TS))] #[cfg_attr( feature = "generate-ts", ts(export_to = "ts-packages/types/src/types/rust/RewardingParams.ts") )] -#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Eq, PartialOrd, Serialize, JsonSchema)] +#[cw_serde] +#[derive(Copy)] pub struct RewardingParams { /// Parameters that should remain unchanged throughout an interval. pub interval: IntervalRewardParams, - // while the active set size can change between epochs to accommodate for bandwidth demands, + // while the rewarded set size can change between epochs to accommodate for bandwidth demands, // the active set size should be unchanged between epochs and should only be adjusted between // intervals. However, it makes more sense to keep both of those values together as they're // very strongly related to each other. + /// The expected number of mixnodes in the rewarded set (i.e. active + standby). pub rewarded_set_size: u32, + + /// The expected number of mixnodes in the active set. pub active_set_size: u32, } @@ -224,9 +229,14 @@ impl RewardingParams { } // TODO: possibly refactor this -#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Eq, PartialOrd, Serialize, JsonSchema)] +/// Parameters used for rewarding particular mixnode. +#[cw_serde] +#[derive(Copy)] pub struct NodeRewardParams { + /// Performance of the particular node in the current epoch. pub performance: Percent, + + /// Flag indicating whether the node has been in the active set during the epoch. pub in_active_set: bool, } @@ -239,33 +249,40 @@ impl NodeRewardParams { } } +/// Specification on how the rewarding params should be updated. #[cfg_attr(feature = "generate-ts", derive(ts_rs::TS))] #[cfg_attr( feature = "generate-ts", ts(export_to = "ts-packages/types/src/types/rust/IntervalRewardingParamsUpdate.ts") )] -#[derive( - Clone, Copy, Debug, Default, Deserialize, PartialEq, Eq, PartialOrd, Serialize, JsonSchema, -)] +#[cw_serde] +#[derive(Copy, Default)] pub struct IntervalRewardingParamsUpdate { #[cfg_attr(feature = "generate-ts", ts(type = "string | null"))] + /// Defines the new value of the reward pool. pub reward_pool: Option, #[cfg_attr(feature = "generate-ts", ts(type = "string | null"))] + /// Defines the new value of the staking supply. pub staking_supply: Option, #[cfg_attr(feature = "generate-ts", ts(type = "string | null"))] + /// Defines the new value of the staking supply scale factor. pub staking_supply_scale_factor: Option, #[cfg_attr(feature = "generate-ts", ts(type = "string | null"))] + /// Defines the new value of the sybil resistance percent. pub sybil_resistance_percent: Option, #[cfg_attr(feature = "generate-ts", ts(type = "string | null"))] + /// Defines the new value of the active set work factor. pub active_set_work_factor: Option, #[cfg_attr(feature = "generate-ts", ts(type = "string | null"))] + /// Defines the new value of the interval pool emission rate. pub interval_pool_emission: Option, + /// Defines the new size of the rewarded set. pub rewarded_set_size: Option, } diff --git a/common/cosmwasm-smart-contracts/mixnet-contract/src/rewarding/mod.rs b/common/cosmwasm-smart-contracts/mixnet-contract/src/rewarding/mod.rs index 650e39a2ff..4ef9cd433a 100644 --- a/common/cosmwasm-smart-contracts/mixnet-contract/src/rewarding/mod.rs +++ b/common/cosmwasm-smart-contracts/mixnet-contract/src/rewarding/mod.rs @@ -1,9 +1,9 @@ // Copyright 2022 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 +use crate::{MixId, RewardedSetNodeStatus}; +use cosmwasm_schema::cw_serde; use cosmwasm_std::{Coin, Decimal}; -use schemars::JsonSchema; -use serde::{Deserialize, Serialize}; pub mod helpers; pub mod simulator; @@ -13,32 +13,52 @@ pub mod simulator; feature = "generate-ts", ts(export_to = "ts-packages/types/src/types/rust/RewardEstimate.ts") )] -#[derive(Clone, Copy, Debug, Default, Deserialize, Serialize, JsonSchema, PartialEq, Eq)] +#[cw_serde] +#[derive(Copy, Default)] pub struct RewardEstimate { + /// The amount of **decimal** coins that are going to get distributed to the node, + /// i.e. the operator and all its delegators. #[cfg_attr(feature = "generate-ts", ts(type = "string"))] pub total_node_reward: Decimal, // note that operator reward includes the operating_cost, // i.e. say total_node_reward was `1nym` and operating_cost was `2nym` // in that case the operator reward would still be `1nym` as opposed to 0 + /// The share of the reward that is going to get distributed to the node operator. #[cfg_attr(feature = "generate-ts", ts(type = "string"))] pub operator: Decimal, + + /// The share of the reward that is going to get distributed among the node delegators. #[cfg_attr(feature = "generate-ts", ts(type = "string"))] pub delegates: Decimal, + + /// The operating cost of this node. Note: it's already included in the operator reward. #[cfg_attr(feature = "generate-ts", ts(type = "string"))] pub operating_cost: Decimal, } -#[derive(Clone, Copy, Debug, Default, Deserialize, Serialize, JsonSchema, PartialEq, Eq)] +#[cw_serde] +#[derive(Copy, Default)] pub struct RewardDistribution { + /// The share of the reward that is going to get distributed to the node operator. pub operator: Decimal, + + /// The share of the reward that is going to get distributed among the node delegators. pub delegates: Decimal, } -#[derive(Clone, Debug, Default, Deserialize, Serialize, JsonSchema, PartialEq, Eq)] +/// Response containing information about accrued rewards. +#[cw_serde] +#[derive(Default)] pub struct PendingRewardResponse { + /// The amount of tokens initially staked. pub amount_staked: Option, + + /// The amount of tokens that could be claimed. pub amount_earned: Option, + + /// The full pending rewards. Note that it's nearly identical to `amount_earned`, + /// however, it contains few additional decimal points for more accurate reward calculation. pub amount_earned_detailed: Option, /// The associated mixnode is still fully bonded, meaning it is neither unbonded @@ -46,14 +66,25 @@ pub struct PendingRewardResponse { pub mixnode_still_fully_bonded: bool, } -#[derive(Clone, Debug, Default, Deserialize, Serialize, JsonSchema, PartialEq, Eq)] +/// Response containing estimation of node rewards for the current epoch. +#[cw_serde] pub struct EstimatedCurrentEpochRewardResponse { + /// The amount of tokens initially staked. pub original_stake: Option, + /// The current stake value given all past rewarding and compounding since the original staking was performed. pub current_stake_value: Option, + + /// The current stake value. Note that it's nearly identical to `current_stake_value`, + /// however, it contains few additional decimal points for more accurate reward calculation. pub current_stake_value_detailed_amount: Option, + /// The reward estimation for the current epoch, i.e. the amount of tokens that could be claimable + /// after the epoch finishes and the state of the network does not change. pub estimation: Option, + + /// The full reward estimation. Note that it's nearly identical to `estimation`, + /// however, it contains few additional decimal points for more accurate reward calculation. pub detailed_estimation_amount: Option, } @@ -68,3 +99,13 @@ impl EstimatedCurrentEpochRewardResponse { } } } + +/// Response containing paged list of all mixnodes in the rewarded set. +#[cw_serde] +pub struct PagedRewardedSetResponse { + /// Nodes in the current rewarded set. + pub nodes: Vec<(MixId, RewardedSetNodeStatus)>, + + /// Field indicating paging information for the following queries if the caller wishes to get further entries. + pub start_next_after: Option, +} diff --git a/common/cosmwasm-smart-contracts/mixnet-contract/src/signing.rs b/common/cosmwasm-smart-contracts/mixnet-contract/src/signing.rs deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/common/cosmwasm-smart-contracts/mixnet-contract/src/types.rs b/common/cosmwasm-smart-contracts/mixnet-contract/src/types.rs index e0a8109f98..e1ff854bb9 100644 --- a/common/cosmwasm-smart-contracts/mixnet-contract/src/types.rs +++ b/common/cosmwasm-smart-contracts/mixnet-contract/src/types.rs @@ -2,29 +2,26 @@ // SPDX-License-Identifier: Apache-2.0 use crate::error::MixnetContractError; -use crate::families::{Family, FamilyHead}; -use crate::{Layer, RewardedSetNodeStatus}; -use contracts_common::IdentityKey; +use crate::Layer; +use cosmwasm_schema::cw_serde; use cosmwasm_std::Addr; use cosmwasm_std::Coin; -use schemars::JsonSchema; -use serde::{Deserialize, Serialize}; use std::ops::Index; // type aliases for better reasoning about available data pub type SphinxKey = String; pub type SphinxKeyRef<'a> = &'a str; -pub type EpochId = u32; -pub type IntervalId = u32; + pub type MixId = u32; pub type BlockHeight = u64; -pub type EpochEventId = u32; -pub type IntervalEventId = u32; -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Serialize, Deserialize, Debug, Clone, JsonSchema, PartialEq, Eq)] +/// Specifies layer assignment for the given mixnode. +#[cw_serde] pub struct LayerAssignment { + /// The id of the mixnode. mix_id: MixId, + + /// The layer to which it's going to be assigned layer: Layer, } @@ -42,10 +39,17 @@ impl LayerAssignment { } } -#[derive(Debug, Default, Serialize, Deserialize, Copy, Clone, Eq, PartialEq)] +/// The current layer distribution of the mix network. +#[cw_serde] +#[derive(Copy, Default)] pub struct LayerDistribution { + /// Number of nodes on the first layer. pub layer1: u64, + + /// Number of nodes on the second layer. pub layer2: u64, + + /// Number of nodes on the third layer. pub layer3: u64, } @@ -118,19 +122,29 @@ impl Index for LayerDistribution { } } -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] +/// The current state of the mixnet contract. +#[cw_serde] pub struct ContractState { - pub owner: Addr, // only the owner account can update state + /// Address of the contract owner. + pub owner: Addr, + + /// Address of "rewarding validator" (nym-api) that's allowed to send any rewarding-related transactions. pub rewarding_validator_address: Addr, /// Address of the vesting contract to which the mixnet contract would be sending all /// track-related messages. pub vesting_contract_address: Addr, + + /// The expected denom used for rewarding (and realistically any other operation). + /// Default: `unym` pub rewarding_denom: String, + + /// Contract parameters that could be adjusted in a transaction the contract admin. pub params: ContractStateParams, } -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] +/// Contract parameters that could be adjusted in a transaction by the contract admin. +#[cw_serde] pub struct ContractStateParams { /// Minimum amount a delegator must stake in orders for his delegation to get accepted. pub minimum_mixnode_delegation: Option, @@ -141,21 +155,3 @@ pub struct ContractStateParams { /// Minimum amount a gateway must pledge to get into the system. pub minimum_gateway_pledge: Coin, } - -#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq, JsonSchema)] -pub struct PagedRewardedSetResponse { - pub nodes: Vec<(MixId, RewardedSetNodeStatus)>, - pub start_next_after: Option, -} - -#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq, JsonSchema)] -pub struct PagedFamiliesResponse { - pub families: Vec, - pub start_next_after: Option, -} - -#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq, JsonSchema)] -pub struct PagedMembersResponse { - pub members: Vec<(IdentityKey, FamilyHead)>, - pub start_next_after: Option, -} diff --git a/common/cosmwasm-smart-contracts/multisig-contract/Cargo.toml b/common/cosmwasm-smart-contracts/multisig-contract/Cargo.toml index 2a6753267e..d419e25086 100644 --- a/common/cosmwasm-smart-contracts/multisig-contract/Cargo.toml +++ b/common/cosmwasm-smart-contracts/multisig-contract/Cargo.toml @@ -8,7 +8,7 @@ edition = "2021" [dependencies] cw-utils = { workspace = true } cw3 = { workspace = true } -cw4 = { workspace= true } +cw4 = { workspace = true } cw-storage-plus = { workspace = true } cosmwasm-schema = { workspace = true } cosmwasm-std = { workspace = true } diff --git a/common/cosmwasm-smart-contracts/name-service/Cargo.toml b/common/cosmwasm-smart-contracts/name-service/Cargo.toml index 23fb221e23..4af7d851f1 100644 --- a/common/cosmwasm-smart-contracts/name-service/Cargo.toml +++ b/common/cosmwasm-smart-contracts/name-service/Cargo.toml @@ -6,10 +6,14 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +cosmwasm-schema = { workspace = true } cosmwasm-std = { workspace = true } +cw2 = { workspace = true, optional = true } cw-controllers = { workspace = true } cw-utils = { workspace = true } nym-contracts-common = { path = "../contracts-common", version = "0.5.0" } -schemars = "0.8" serde = { workspace = true, features = ["derive"] } thiserror = { workspace = true } + +[features] +schema = ["cw2"] \ No newline at end of file diff --git a/common/cosmwasm-smart-contracts/name-service/src/msg.rs b/common/cosmwasm-smart-contracts/name-service/src/msg.rs index 9a8e2b8ecf..cdac95c215 100644 --- a/common/cosmwasm-smart-contracts/name-service/src/msg.rs +++ b/common/cosmwasm-smart-contracts/name-service/src/msg.rs @@ -1,10 +1,22 @@ +// Copyright 2023 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + use crate::{Address, NameDetails, NameId, NymName}; +use cosmwasm_schema::cw_serde; use cosmwasm_std::Coin; use nym_contracts_common::signing::MessageSignature; -use serde::{Deserialize, Serialize}; -#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)] -#[serde(rename_all = "snake_case")] +#[cfg(feature = "schema")] +use crate::{ + response::{ConfigResponse, NamesListResponse, PagedNamesListResponse}, + types::RegisteredName, +}; +#[cfg(feature = "schema")] +use cosmwasm_schema::QueryResponses; +#[cfg(feature = "schema")] +use nym_contracts_common::{signing::Nonce, ContractBuildInformation}; + +#[cw_serde] pub struct InstantiateMsg { pub deposit_required: Coin, } @@ -15,22 +27,23 @@ impl InstantiateMsg { } } -#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)] -#[serde(rename_all = "snake_case")] +#[cw_serde] pub struct MigrateMsg {} -#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)] -#[serde(rename_all = "snake_case")] +#[cw_serde] pub enum ExecuteMsg { /// Announcing a name pointing to a nym-address Register { name: NameDetails, owner_signature: MessageSignature, }, + /// Delete a name entry by id DeleteId { name_id: NameId }, + /// Delete a name entry by name DeleteName { name: NymName }, + /// Change the deposit required for announcing a name UpdateDepositRequired { deposit_required: Coin }, } @@ -61,33 +74,42 @@ impl ExecuteMsg { } } -#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)] -#[serde(rename_all = "snake_case")] +#[cw_serde] +#[cfg_attr(feature = "schema", derive(QueryResponses))] pub enum QueryMsg { /// Query the name by it's assigned id - NameId { - name_id: NameId, - }, - // Query the names by the registrator - ByOwner { - owner: String, - }, - ByName { - name: NymName, - }, - ByAddress { - address: Address, - }, + #[cfg_attr(feature = "schema", returns(RegisteredName))] + NameId { name_id: NameId }, + + /// Query the names by the registrator + #[cfg_attr(feature = "schema", returns(NamesListResponse))] + ByOwner { owner: String }, + + #[cfg_attr(feature = "schema", returns(RegisteredName))] + ByName { name: NymName }, + + #[cfg_attr(feature = "schema", returns(NamesListResponse))] + ByAddress { address: Address }, + + #[cfg_attr(feature = "schema", returns(PagedNamesListResponse))] All { limit: Option, start_after: Option, }, - SigningNonce { - address: String, - }, + + #[cfg_attr(feature = "schema", returns(Nonce))] + SigningNonce { address: String }, + + #[cfg_attr(feature = "schema", returns(ConfigResponse))] Config {}, + + /// Gets build information of this contract, such as the commit hash used for the build or rustc version. + #[cfg_attr(feature = "schema", returns(ContractBuildInformation))] GetContractVersion {}, + + /// Gets the stored contract version information that's required by the CW2 spec interface for migrations. #[serde(rename = "get_cw2_contract_version")] + #[cfg_attr(feature = "schema", returns(cw2::ContractVersion))] GetCW2ContractVersion {}, } diff --git a/common/cosmwasm-smart-contracts/name-service/src/response.rs b/common/cosmwasm-smart-contracts/name-service/src/response.rs index 8087195eee..162021d7c7 100644 --- a/common/cosmwasm-smart-contracts/name-service/src/response.rs +++ b/common/cosmwasm-smart-contracts/name-service/src/response.rs @@ -1,10 +1,11 @@ -use crate::{NameId, RegisteredName}; -use cosmwasm_std::Coin; -use schemars::JsonSchema; -use serde::{Deserialize, Serialize}; +// Copyright 2023 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 -#[derive(Serialize, Deserialize, PartialEq, Debug, Clone, JsonSchema)] -#[serde(rename_all = "snake_case")] +use crate::{NameId, RegisteredName}; +use cosmwasm_schema::cw_serde; +use cosmwasm_std::Coin; + +#[cw_serde] pub struct NamesListResponse { pub names: Vec, } @@ -23,11 +24,12 @@ impl From<&[RegisteredName]> for NamesListResponse { } } -#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)] -#[serde(rename_all = "snake_case")] +#[cw_serde] pub struct PagedNamesListResponse { pub names: Vec, pub per_page: usize, + + /// Field indicating paging information for the following queries if the caller wishes to get further entries. pub start_next_after: Option, } @@ -45,8 +47,7 @@ impl PagedNamesListResponse { } } -#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)] -#[serde(rename_all = "snake_case")] +#[cw_serde] pub struct ConfigResponse { pub deposit_required: Coin, } diff --git a/common/cosmwasm-smart-contracts/name-service/src/types.rs b/common/cosmwasm-smart-contracts/name-service/src/types.rs index dd44340815..c595e82452 100644 --- a/common/cosmwasm-smart-contracts/name-service/src/types.rs +++ b/common/cosmwasm-smart-contracts/name-service/src/types.rs @@ -1,14 +1,15 @@ -use std::fmt::{Display, Formatter}; +// Copyright 2023 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 +use cosmwasm_schema::cw_serde; use cosmwasm_std::{Addr, Coin}; use nym_contracts_common::IdentityKey; -use schemars::JsonSchema; -use serde::{Deserialize, Serialize}; +use std::fmt::{Display, Formatter}; /// The directory of names are indexed by [`NameId`]. pub type NameId = u32; -#[derive(Serialize, Deserialize, Clone, PartialEq, Debug, JsonSchema)] +#[cw_serde] pub struct RegisteredName { /// Unique id assigned to the registerd name. pub id: NameId, @@ -33,7 +34,7 @@ impl RegisteredName { } } -#[derive(Serialize, Deserialize, Clone, PartialEq, Debug, JsonSchema)] +#[cw_serde] pub struct NameDetails { /// The name pointing to the nym address pub name: NymName, @@ -48,8 +49,7 @@ pub struct NameDetails { /// String representation of a nym address, which is of the form /// client_id.client_enc@gateway_id. /// NOTE: entirely unvalidated. -#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug, JsonSchema)] -#[serde(rename_all = "snake_case")] +#[cw_serde] pub enum Address { NymAddress(String), // Possible extension: @@ -83,8 +83,7 @@ impl Display for Address { } /// Name stored and pointing a to a nym-address -#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug, JsonSchema)] -#[serde(rename_all = "snake_case")] +#[cw_serde] pub struct NymName(String); #[derive(Debug)] diff --git a/common/cosmwasm-smart-contracts/service-provider-directory/Cargo.toml b/common/cosmwasm-smart-contracts/service-provider-directory/Cargo.toml index 3965734b2d..aed3912abd 100644 --- a/common/cosmwasm-smart-contracts/service-provider-directory/Cargo.toml +++ b/common/cosmwasm-smart-contracts/service-provider-directory/Cargo.toml @@ -6,10 +6,13 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +cosmwasm-schema = { workspace = true } cosmwasm-std = { workspace = true } +cw2 = { workspace = true, optional = true } cw-controllers = { workspace = true } cw-utils = { workspace = true } nym-contracts-common = { path = "../contracts-common", version = "0.5.0" } -schemars = "0.8" -serde = { workspace = true, features = ["derive"] } thiserror = { workspace = true } + +[features] +schema = ["cw2"] \ No newline at end of file diff --git a/common/cosmwasm-smart-contracts/service-provider-directory/src/msg.rs b/common/cosmwasm-smart-contracts/service-provider-directory/src/msg.rs index f66ad787a5..0c7928e079 100644 --- a/common/cosmwasm-smart-contracts/service-provider-directory/src/msg.rs +++ b/common/cosmwasm-smart-contracts/service-provider-directory/src/msg.rs @@ -1,10 +1,22 @@ +// Copyright 2023 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + use crate::{NymAddress, ServiceDetails, ServiceId}; +use cosmwasm_schema::cw_serde; use cosmwasm_std::Coin; use nym_contracts_common::signing::MessageSignature; -use serde::{Deserialize, Serialize}; -#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)] -#[serde(rename_all = "snake_case")] +#[cfg(feature = "schema")] +use crate::{ + response::{ConfigResponse, PagedServicesListResponse, ServicesListResponse}, + types::Service, +}; +#[cfg(feature = "schema")] +use cosmwasm_schema::QueryResponses; +#[cfg(feature = "schema")] +use nym_contracts_common::{signing::Nonce, ContractBuildInformation}; + +#[cw_serde] pub struct InstantiateMsg { pub deposit_required: Coin, } @@ -15,12 +27,10 @@ impl InstantiateMsg { } } -#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)] -#[serde(rename_all = "snake_case")] +#[cw_serde] pub struct MigrateMsg {} -#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)] -#[serde(rename_all = "snake_case")] +#[cw_serde] pub enum ExecuteMsg { Announce { service: ServiceDetails, @@ -64,28 +74,37 @@ impl ExecuteMsg { } } -#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)] -#[serde(rename_all = "snake_case")] +#[cw_serde] +#[cfg_attr(feature = "schema", derive(QueryResponses))] pub enum QueryMsg { - ServiceId { - service_id: ServiceId, - }, - ByAnnouncer { - announcer: String, - }, - ByNymAddress { - nym_address: NymAddress, - }, + #[cfg_attr(feature = "schema", returns(Service))] + ServiceId { service_id: ServiceId }, + + #[cfg_attr(feature = "schema", returns(ServicesListResponse))] + ByAnnouncer { announcer: String }, + + #[cfg_attr(feature = "schema", returns(ServicesListResponse))] + ByNymAddress { nym_address: NymAddress }, + + #[cfg_attr(feature = "schema", returns(PagedServicesListResponse))] All { limit: Option, start_after: Option, }, - SigningNonce { - address: String, - }, + + #[cfg_attr(feature = "schema", returns(Nonce))] + SigningNonce { address: String }, + + #[cfg_attr(feature = "schema", returns(ConfigResponse))] Config {}, + + /// Gets build information of this contract, such as the commit hash used for the build or rustc version. + #[cfg_attr(feature = "schema", returns(ContractBuildInformation))] GetContractVersion {}, + + /// Gets the stored contract version information that's required by the CW2 spec interface for migrations. #[serde(rename = "get_cw2_contract_version")] + #[cfg_attr(feature = "schema", returns(cw2::ContractVersion))] GetCW2ContractVersion {}, } diff --git a/common/cosmwasm-smart-contracts/service-provider-directory/src/response.rs b/common/cosmwasm-smart-contracts/service-provider-directory/src/response.rs index 8b6bf16fbf..dd9392c2d9 100644 --- a/common/cosmwasm-smart-contracts/service-provider-directory/src/response.rs +++ b/common/cosmwasm-smart-contracts/service-provider-directory/src/response.rs @@ -1,17 +1,17 @@ -use crate::{Service, ServiceId}; -use cosmwasm_std::Coin; -use schemars::JsonSchema; -use serde::{Deserialize, Serialize}; +// Copyright 2023 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 -#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)] -#[serde(rename_all = "snake_case")] +use crate::{Service, ServiceId}; +use cosmwasm_schema::cw_serde; +use cosmwasm_std::Coin; + +#[cw_serde] pub struct ServiceInfoResponse { pub service_id: ServiceId, pub service: Option, } -#[derive(Serialize, Deserialize, PartialEq, Debug, Clone, JsonSchema)] -#[serde(rename_all = "snake_case")] +#[cw_serde] pub struct ServicesListResponse { pub services: Vec, } @@ -30,11 +30,12 @@ impl From<&[Service]> for ServicesListResponse { } } -#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)] -#[serde(rename_all = "snake_case")] +#[cw_serde] pub struct PagedServicesListResponse { pub services: Vec, pub per_page: usize, + + /// Field indicating paging information for the following queries if the caller wishes to get further entries. pub start_next_after: Option, } @@ -52,8 +53,7 @@ impl PagedServicesListResponse { } } -#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)] -#[serde(rename_all = "snake_case")] +#[cw_serde] pub struct ConfigResponse { pub deposit_required: Coin, } diff --git a/common/cosmwasm-smart-contracts/service-provider-directory/src/signing_types.rs b/common/cosmwasm-smart-contracts/service-provider-directory/src/signing_types.rs index 742470b2cd..fb9752d10a 100644 --- a/common/cosmwasm-smart-contracts/service-provider-directory/src/signing_types.rs +++ b/common/cosmwasm-smart-contracts/service-provider-directory/src/signing_types.rs @@ -1,15 +1,17 @@ +// Copyright 2023 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use crate::ServiceDetails; +use cosmwasm_schema::cw_serde; use cosmwasm_std::{Addr, Coin}; use nym_contracts_common::signing::{ ContractMessageContent, MessageType, Nonce, SignableMessage, SigningPurpose, }; -use serde::Serialize; - -use crate::ServiceDetails; pub type SignableServiceProviderAnnounceMsg = SignableMessage>; -#[derive(Serialize)] +#[cw_serde] pub struct ServiceProviderAnnounce { service: ServiceDetails, } diff --git a/common/cosmwasm-smart-contracts/service-provider-directory/src/types.rs b/common/cosmwasm-smart-contracts/service-provider-directory/src/types.rs index c3ad296fa2..845c05b768 100644 --- a/common/cosmwasm-smart-contracts/service-provider-directory/src/types.rs +++ b/common/cosmwasm-smart-contracts/service-provider-directory/src/types.rs @@ -1,14 +1,15 @@ -use std::fmt::{Display, Formatter}; +// Copyright 2023 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 +use cosmwasm_schema::cw_serde; use cosmwasm_std::{Addr, Coin}; use nym_contracts_common::IdentityKey; -use schemars::JsonSchema; -use serde::{Deserialize, Serialize}; +use std::fmt::{Display, Formatter}; /// The directory of services are indexed by [`ServiceId`]. pub type ServiceId = u32; -#[derive(Serialize, Deserialize, Clone, PartialEq, Debug, JsonSchema)] +#[cw_serde] pub struct Service { /// Unique id assigned to the anounced service. pub service_id: ServiceId, @@ -22,7 +23,7 @@ pub struct Service { pub deposit: Coin, } -#[derive(Serialize, Deserialize, Clone, PartialEq, Debug, JsonSchema)] +#[cw_serde] pub struct ServiceDetails { /// The address of the service. pub nym_address: NymAddress, @@ -33,8 +34,7 @@ pub struct ServiceDetails { } /// The types of addresses supported. -#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug, JsonSchema)] -#[serde(rename_all = "snake_case")] +#[cw_serde] pub enum NymAddress { /// String representation of a nym address, which is of the form /// client_id.client_enc@gateway_id. @@ -64,8 +64,7 @@ impl Display for NymAddress { } /// The type of services provider supported -#[derive(Serialize, Deserialize, Copy, Clone, PartialEq, Eq, Debug, JsonSchema)] -#[serde(rename_all = "snake_case")] +#[cw_serde] pub enum ServiceType { NetworkRequester, } diff --git a/common/cosmwasm-smart-contracts/vesting-contract/Cargo.toml b/common/cosmwasm-smart-contracts/vesting-contract/Cargo.toml index 516f8a8e35..f5fa35df5f 100644 --- a/common/cosmwasm-smart-contracts/vesting-contract/Cargo.toml +++ b/common/cosmwasm-smart-contracts/vesting-contract/Cargo.toml @@ -9,11 +9,14 @@ repository = { workspace = true } [dependencies] cosmwasm-std = { workspace = true } +cosmwasm-schema = { workspace = true } +cw2 = { workspace = true, optional = true } mixnet-contract-common = { path = "../mixnet-contract", package = "nym-mixnet-contract-common", version = "0.6.0" } contracts-common = { path = "../contracts-common", package = "nym-contracts-common", version = "0.5.0" } serde = { version = "1.0", features = ["derive"] } -schemars = "0.8" +thiserror = "1.0" ts-rs = {version = "6.1.2", optional = true} [features] +schema = ["cw2"] generate-ts = ["ts-rs"] diff --git a/common/cosmwasm-smart-contracts/vesting-contract/src/account.rs b/common/cosmwasm-smart-contracts/vesting-contract/src/account.rs new file mode 100644 index 0000000000..0d1e5b754e --- /dev/null +++ b/common/cosmwasm-smart-contracts/vesting-contract/src/account.rs @@ -0,0 +1,124 @@ +// Copyright 2023 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use crate::{Period, PledgeCap, VestingContractError, VestingPeriod}; +use cosmwasm_schema::cw_serde; +use cosmwasm_std::{Addr, Coin, Timestamp}; + +// this shouldn't really be exposed like this, but we really don't want to migrate the contract just for this... +pub type VestingAccountStorageKey = u32; + +/// Vesting account information. +#[cw_serde] +pub struct Account { + /// Address of the owner of the vesting account. + pub owner_address: Addr, + + /// Optional address of an account allowed to perform staking on behalf of the owner. + pub staking_address: Option, + + /// The starting vesting time. + pub start_time: Timestamp, + + /// All vesting periods for this account. + pub periods: Vec, + + /// The initial amount of coins used creation of this account. + pub coin: Coin, + + /// The id/storage_key of this vesting account. + pub storage_key: VestingAccountStorageKey, + + /// Optional custom pledge cap of this vesting account. + #[serde(default)] + pub pledge_cap: Option, +} + +impl Account { + pub fn pledge_cap(&self) -> PledgeCap { + self.pledge_cap.clone().unwrap_or_default() + } + + pub fn coin(&self) -> Coin { + self.coin.clone() + } + + pub fn num_vesting_periods(&self) -> usize { + self.periods.len() + } + + pub fn period_duration(&self) -> Result { + self.periods + .get(0) + .ok_or(VestingContractError::UnpopulatedVestingPeriods { + owner: self.owner_address.clone(), + }) + .map(|p| p.period_seconds) + } + + pub fn storage_key(&self) -> u32 { + self.storage_key + } + + pub fn owner_address(&self) -> Addr { + self.owner_address.clone() + } + + pub fn staking_address(&self) -> Option<&Addr> { + self.staking_address.as_ref() + } + + pub fn periods(&self) -> Vec { + self.periods.clone() + } + + pub fn start_time(&self) -> Timestamp { + self.start_time + } + + pub fn tokens_per_period(&self) -> Result { + let amount = self.coin.amount.u128(); + if amount < self.num_vesting_periods() as u128 { + Err(VestingContractError::ImprobableVestingAmount(amount)) + } else { + // Remainder tokens will be lumped into the last period. + Ok(amount / self.num_vesting_periods() as u128) + } + } + + /// Returns the index of the next vesting period. Unless the current time is somehow in the past or vesting has not started yet. + /// In case vesting is over it will always return NUM_VESTING_PERIODS. + pub fn get_current_vesting_period( + &self, + block_time: Timestamp, + ) -> Result { + let first_period = + self.periods + .first() + .ok_or(VestingContractError::UnpopulatedVestingPeriods { + owner: self.owner_address.clone(), + })?; + + let last_period = + self.periods + .last() + .ok_or(VestingContractError::UnpopulatedVestingPeriods { + owner: self.owner_address.clone(), + })?; + + if block_time.seconds() < first_period.start_time { + Ok(Period::Before) + } else if last_period.end_time() < block_time { + Ok(Period::After) + } else { + let mut index = 0; + for period in &self.periods { + if block_time < period.end_time() { + break; + } + index += 1; + } + Ok(Period::In(index)) + } + } +} diff --git a/contracts/vesting/src/errors.rs b/common/cosmwasm-smart-contracts/vesting-contract/src/error.rs similarity index 92% rename from contracts/vesting/src/errors.rs rename to common/cosmwasm-smart-contracts/vesting-contract/src/error.rs index 2b92be0c8c..65825c6ad9 100644 --- a/contracts/vesting/src/errors.rs +++ b/common/cosmwasm-smart-contracts/vesting-contract/src/error.rs @@ -1,10 +1,13 @@ -use crate::storage::AccountStorageKey; +// Copyright 2023 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use crate::account::VestingAccountStorageKey; use cosmwasm_std::{Addr, Coin, OverflowError, StdError, Uint128}; use mixnet_contract_common::MixId; use thiserror::Error; #[derive(Error, Debug, PartialEq)] -pub enum ContractError { +pub enum VestingContractError { #[error("VESTING ({}): {0}", line!())] Std(#[from] StdError), @@ -27,7 +30,7 @@ pub enum ContractError { InsufficientSpendable(String, u128), #[error( - "VESTING ({}):Only delegation owner can perform delegation actions, {0} is not the delegation owner" + "VESTING ({}):Only delegation owner can perform delegation actions, {0} is not the delegation owner" , line!())] NotDelegate(String), @@ -91,7 +94,7 @@ pub enum ContractError { #[error("VESTING: {address} ({acc_id} has already performed {num} individual delegations towards {mix_id}. No further delegations are allowed. Please consider consolidating those delegations instead. The current cap is {cap}.")] TooManyDelegations { address: Addr, - acc_id: AccountStorageKey, + acc_id: VestingAccountStorageKey, mix_id: MixId, num: u32, cap: u32, diff --git a/common/cosmwasm-smart-contracts/vesting-contract/src/lib.rs b/common/cosmwasm-smart-contracts/vesting-contract/src/lib.rs index 6c456674a5..31fab7a7ef 100644 --- a/common/cosmwasm-smart-contracts/vesting-contract/src/lib.rs +++ b/common/cosmwasm-smart-contracts/vesting-contract/src/lib.rs @@ -4,83 +4,31 @@ #![warn(clippy::expect_used)] #![warn(clippy::unwrap_used)] -use contracts_common::Percent; -use cosmwasm_std::{Addr, Coin, Timestamp, Uint128}; +use cosmwasm_schema::cw_serde; +use cosmwasm_std::{Addr, Coin}; use mixnet_contract_common::MixId; -use schemars::JsonSchema; -use serde::{Deserialize, Serialize}; -use std::str::FromStr; - -pub use messages::{ExecuteMsg, InitMsg, MigrateMsg, QueryMsg}; +pub mod account; +pub mod error; pub mod events; pub mod messages; +pub mod types; -#[cfg_attr(feature = "generate-ts", derive(ts_rs::TS))] -#[cfg_attr( - feature = "generate-ts", - ts(export_to = "ts-packages/types/src/types/rust/Period.ts") -)] -#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Clone, JsonSchema)] -pub enum Period { - Before, - In(usize), - After, -} +pub use account::Account; +pub use error::VestingContractError; +pub use messages::{ExecuteMsg, InitMsg, MigrateMsg, QueryMsg}; +pub use types::*; -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] -pub struct PledgeData { - pub amount: Coin, - pub block_time: Timestamp, -} - -impl PledgeData { - pub fn amount(&self) -> Coin { - self.amount.clone() - } - - pub fn block_time(&self) -> Timestamp { - self.block_time - } - - pub fn new(amount: Coin, block_time: Timestamp) -> Self { - Self { amount, block_time } - } -} - -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] -pub enum PledgeCap { - Percent(Percent), - Absolute(Uint128), // This has to be in unym -} - -impl FromStr for PledgeCap { - type Err = String; - - fn from_str(cap: &str) -> Result { - let cap = cap.replace('_', "").replace(',', "."); - match Percent::from_str(&cap) { - Ok(p) => Ok(PledgeCap::Percent(p)), - Err(_) => match cap.parse::() { - Ok(i) => Ok(PledgeCap::Absolute(Uint128::from(i))), - Err(_e) => Err(format!("Could not parse {cap} as Percent or Uint128")), - }, - } - } -} - -impl Default for PledgeCap { - fn default() -> Self { - #[allow(clippy::expect_used)] - PledgeCap::Percent(Percent::from_percentage_value(10).expect("This can never fail!")) - } -} - -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] +/// Details about the original vesting specification used when the account was created. +#[cw_serde] pub struct OriginalVestingResponse { + /// The original amount that was used for the creation of this vesting account pub amount: Coin, + + /// The number of vesting periods that the account was created with pub number_of_periods: usize, + + /// Duration of each vesting period in seconds pub period_duration: u64, } @@ -106,57 +54,73 @@ impl OriginalVestingResponse { } } -#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Clone, JsonSchema)] -pub struct VestingDelegation { - pub account_id: u32, - pub mix_id: MixId, - pub block_timestamp: u64, - pub amount: Uint128, -} - -impl VestingDelegation { - pub fn storage_key(&self) -> (u32, MixId, u64) { - (self.account_id, self.mix_id, self.block_timestamp) - } -} - -#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Clone, JsonSchema)] +/// Response containing timestamps of all delegations made towards particular mixnode by given vesting account. +#[cw_serde] pub struct DelegationTimesResponse { + /// Address of this account's owner pub owner: Addr, + + /// Id associated with this account pub account_id: u32, + + /// Id of the mixnode towards which the delegation was made pub mix_id: MixId, + + /// All timestamps where a delegation was made pub delegation_timestamps: Vec, } -#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Clone, JsonSchema)] +/// Response containing paged list of all vesting delegations made using vesting coins. +#[cw_serde] pub struct AllDelegationsResponse { + /// The actual vesting delegations made. pub delegations: Vec, + + /// Field indicating paging information for the following queries if the caller wishes to get further entries. pub start_next_after: Option<(u32, MixId, u64)>, } -#[derive(Debug, Serialize, Deserialize, Clone, JsonSchema)] +/// Basic information regarding particular vesting account alongside the amount of vesting coins. +#[cw_serde] pub struct AccountVestingCoins { + /// Id associated with this account pub account_id: u32, + + /// Address of this account's owner pub owner: Addr, + + /// Coins that are still vesting belonging to this account. pub still_vesting: Coin, } -#[derive(Debug, Serialize, Deserialize, Clone, JsonSchema)] +/// Response containing vesting coins held in this contract +#[cw_serde] pub struct VestingCoinsResponse { + /// The actual accounts, and their vesting coins, returned by the query. pub accounts: Vec, + + /// Field indicating paging information for the following queries if the caller wishes to get further entries. pub start_next_after: Option, } -#[derive(Debug, Serialize, Deserialize, Clone, JsonSchema)] +/// Basic information regarding particular vesting account +#[cw_serde] pub struct BaseVestingAccountInfo { + /// Id associated with this account pub account_id: u32, + + /// Address of this account's owner pub owner: Addr, // TODO: should this particular query/response expose anything else? } -#[derive(Debug, Serialize, Deserialize, Clone, JsonSchema)] +/// Response containing basic vesting account information +#[cw_serde] pub struct AccountsResponse { + /// The actual accounts returned by the query. pub accounts: Vec, + + /// Field indicating paging information for the following queries if the caller wishes to get further entries. pub start_next_after: Option, } diff --git a/common/cosmwasm-smart-contracts/vesting-contract/src/messages.rs b/common/cosmwasm-smart-contracts/vesting-contract/src/messages.rs index f4d7147e2c..6593926445 100644 --- a/common/cosmwasm-smart-contracts/vesting-contract/src/messages.rs +++ b/common/cosmwasm-smart-contracts/vesting-contract/src/messages.rs @@ -1,4 +1,9 @@ +// Copyright 2021-2023 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use crate::{PledgeCap, VestingSpecification}; use contracts_common::signing::MessageSignature; +use cosmwasm_schema::cw_serde; use cosmwasm_std::{Coin, Timestamp}; use mixnet_contract_common::families::FamilyHead; use mixnet_contract_common::{ @@ -6,57 +11,30 @@ use mixnet_contract_common::{ mixnode::{MixNodeConfigUpdate, MixNodeCostParams}, Gateway, IdentityKey, MixId, MixNode, }; -use schemars::JsonSchema; -use serde::{Deserialize, Serialize}; -use crate::PledgeCap; +#[cfg(feature = "schema")] +use contracts_common::ContractBuildInformation; +#[cfg(feature = "schema")] +use cosmwasm_schema::QueryResponses; -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] -#[serde(rename_all = "snake_case")] +#[cfg(feature = "schema")] +use crate::{ + account::Account, + types::{Period, PledgeData, VestingDelegation}, + AccountsResponse, AllDelegationsResponse, DelegationTimesResponse, OriginalVestingResponse, + VestingCoinsResponse, +}; + +#[cw_serde] pub struct InitMsg { pub mixnet_contract_address: String, pub mix_denom: String, } -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] -#[serde(rename_all = "snake_case")] +#[cw_serde] pub struct MigrateMsg {} -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema, Default)] -pub struct VestingSpecification { - start_time: Option, - period_seconds: Option, - num_periods: Option, -} - -impl VestingSpecification { - pub fn new( - start_time: Option, - period_seconds: Option, - num_periods: Option, - ) -> Self { - Self { - start_time, - period_seconds, - num_periods, - } - } - - pub fn start_time(&self) -> Option { - self.start_time - } - - pub fn period_seconds(&self) -> u64 { - self.period_seconds.unwrap_or(3 * 30 * 86400) - } - - pub fn num_periods(&self) -> u64 { - self.num_periods.unwrap_or(8) - } -} - -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] -#[serde(rename_all = "snake_case")] +#[cw_serde] pub enum ExecuteMsg { // Families /// Only owner of the node can crate the family with node as head @@ -197,93 +175,226 @@ impl ExecuteMsg { } } -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] -#[serde(rename_all = "snake_case")] +/// Queries exposed by this contract. +#[cw_serde] +#[cfg_attr(feature = "schema", derive(QueryResponses))] pub enum QueryMsg { + /// Gets build information of this contract, such as the commit hash used for the build or rustc version. + #[cfg_attr(feature = "schema", returns(ContractBuildInformation))] GetContractVersion {}, + + /// Gets the stored contract version information that's required by the CW2 spec interface for migrations. #[serde(rename = "get_cw2_contract_version")] + #[cfg_attr(feature = "schema", returns(cw2::ContractVersion))] GetCW2ContractVersion {}, + + /// Gets the list of vesting accounts held in this contract. + #[cfg_attr(feature = "schema", returns(AccountsResponse))] GetAccountsPaged { + /// Pagination control for the values returned by the query. Note that the provided value itself will **not** be used for the response. start_next_after: Option, + + /// Controls the maximum number of entries returned by the query. Note that too large values will be overwritten by a saner default. limit: Option, }, + + /// Gets the list of coins that are still vesting for each account held in this contract. + #[cfg_attr(feature = "schema", returns(VestingCoinsResponse))] GetAccountsVestingCoinsPaged { + /// Pagination control for the values returned by the query. Note that the provided value itself will **not** be used for the response. start_next_after: Option, + + /// Controls the maximum number of entries returned by the query. Note that too large values will be overwritten by a saner default. limit: Option, }, + + /// Returns the amount of locked coins for the provided vesting account, + /// i.e. coins that are still vesting but have not been staked. + /// `locked_coins = vesting_coins - staked_coins` + #[cfg_attr(feature = "schema", returns(Coin))] LockedCoins { + /// Address of the vesting account in question. vesting_account_address: String, + + /// (deprecated) Optional argument specifying that the query should be performed against non-current block. block_time: Option, }, + + /// Returns the amount of spendable coins for the provided vesting account, + /// i.e. coins that could be withdrawn. + /// `spendable_coins = account_balance - locked_coins` + /// note: `account_balance` is the amount of coins still physically present in this contract, i.e. not withdrawn or staked. + #[cfg_attr(feature = "schema", returns(Coin))] SpendableCoins { + /// Address of the vesting account in question. vesting_account_address: String, + + /// (deprecated) Optional argument specifying that the query should be performed against non-current block. block_time: Option, }, + + /// Returns the amount of coins that have already vested for the provided vesting account address. + #[cfg_attr(feature = "schema", returns(Coin))] GetVestedCoins { + /// Address of the vesting account in question. vesting_account_address: String, + + /// (deprecated) Optional argument specifying that the query should be performed against non-current block. block_time: Option, }, + + /// Returns the amount of coins that are still vesting for the provided vesting account address. + #[cfg_attr(feature = "schema", returns(Coin))] GetVestingCoins { + /// Address of the vesting account in question. vesting_account_address: String, + + /// (deprecated) Optional argument specifying that the query should be performed against non-current block. block_time: Option, }, + + /// Returns the starting vesting time for the provided vesting account, + /// i.e. the beginning of the first vesting period. + #[cfg_attr(feature = "schema", returns(Timestamp))] GetStartTime { + /// Address of the vesting account in question. vesting_account_address: String, }, + + /// Returns the ending vesting time for the provided vesting account, + /// i.e. the end of the last vesting period. + #[cfg_attr(feature = "schema", returns(Timestamp))] GetEndTime { + /// Address of the vesting account in question. vesting_account_address: String, }, + + /// Returns the initial vesting specification used for the provided vesting account address. + #[cfg_attr(feature = "schema", returns(OriginalVestingResponse))] GetOriginalVesting { + /// Address of the vesting account in question. vesting_account_address: String, }, + + /// Returns the total amount of coins accrued through claimed staking rewards by the provided vesting account. + #[cfg_attr(feature = "schema", returns(Coin))] GetHistoricalVestingStakingReward { + /// Address of the vesting account in question. vesting_account_address: String, }, + + /// Returns the amount of spendable vesting coins for the provided vesting account, + /// i.e. coins that could be withdrawn that originated from the vesting specification. + #[cfg_attr(feature = "schema", returns(Coin))] GetSpendableVestedCoins { + /// Address of the vesting account in question. vesting_account_address: String, }, + + /// Returns the amount of spendable reward coins for the provided vesting account, + /// i.e. coins that could be withdrawn that originated from the claimed staking rewards. + #[cfg_attr(feature = "schema", returns(Coin))] GetSpendableRewardCoins { + /// Address of the vesting account in question. vesting_account_address: String, }, + + /// Returns the amount of coins that are currently delegated for the provided vesting account address. + #[cfg_attr(feature = "schema", returns(Coin))] GetDelegatedCoins { + /// Address of the vesting account in question. vesting_account_address: String, }, + + /// Returns the amount of coins that are currently pledged for the provided vesting account address. + #[cfg_attr(feature = "schema", returns(Coin))] GetPledgedCoins { + /// Address of the vesting account in question. vesting_account_address: String, }, + + /// Returns the amount of coins that are currently staked (i.e. delegations + pledges) for the provided vesting account address. + #[cfg_attr(feature = "schema", returns(Coin))] GetStakedCoins { + /// Address of the vesting account in question. vesting_account_address: String, }, + + /// Returns the amount of coins that got withdrawn for the provided vesting account address. + #[cfg_attr(feature = "schema", returns(Coin))] GetWithdrawnCoins { + /// Address of the vesting account in question. vesting_account_address: String, }, + + /// Returns detailed information associated with the account for the provided vesting account address. + #[cfg_attr(feature = "schema", returns(Account))] GetAccount { + /// Address of the vesting account in question. address: String, }, + + /// Returns pledge information (if applicable) for bonded mixnode for the provided vesting account address. + #[cfg_attr(feature = "schema", returns(Option))] GetMixnode { + /// Address of the vesting account in question. address: String, }, + + /// Returns pledge information (if applicable) for bonded gateway for the provided vesting account address. + #[cfg_attr(feature = "schema", returns(Option))] GetGateway { + /// Address of the vesting account in question. address: String, }, + + /// Returns the current vesting period for the provided vesting account address. + #[cfg_attr(feature = "schema", returns(Period))] GetCurrentVestingPeriod { + /// Address of the vesting account in question. address: String, }, + + /// Returns the information about particular vesting delegation. + #[cfg_attr(feature = "schema", returns(VestingDelegation))] GetDelegation { + /// Address of the vesting account in question. address: String, + + /// Id of the mixnode towards which the delegation has been made. mix_id: MixId, + + /// Block timestamp of the delegation. block_timestamp_secs: u64, }, + + /// Returns the total amount of coins delegated towards particular mixnode by the provided vesting account address. + #[cfg_attr(feature = "schema", returns(Coin))] GetTotalDelegationAmount { + /// Address of the vesting account in question. address: String, + + /// Id of the mixnode towards which the delegations have been made. mix_id: MixId, }, + + /// Returns timestamps of delegations made towards particular mixnode by the provided vesting account address. + #[cfg_attr(feature = "schema", returns(DelegationTimesResponse))] GetDelegationTimes { + /// Address of the vesting account in question. address: String, + + /// Id of the mixnode towards which the delegations have been made. mix_id: MixId, }, + + /// Returns all active delegations made with vesting tokens stored in this contract. + #[cfg_attr(feature = "schema", returns(AllDelegationsResponse))] GetAllDelegations { + /// Pagination control for the values returned by the query. Note that the provided value itself will **not** be used for the response. start_after: Option<(u32, MixId, u64)>, + + /// Controls the maximum number of entries returned by the query. Note that too large values will be overwritten by a saner default. limit: Option, }, } diff --git a/common/cosmwasm-smart-contracts/vesting-contract/src/types.rs b/common/cosmwasm-smart-contracts/vesting-contract/src/types.rs new file mode 100644 index 0000000000..60fd3c0aea --- /dev/null +++ b/common/cosmwasm-smart-contracts/vesting-contract/src/types.rs @@ -0,0 +1,166 @@ +// Copyright 2023 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use contracts_common::Percent; +use cosmwasm_schema::cw_serde; +use cosmwasm_std::{Coin, Timestamp, Uint128}; +use mixnet_contract_common::MixId; +use std::str::FromStr; + +#[cfg_attr(feature = "generate-ts", derive(ts_rs::TS))] +#[cfg_attr( + feature = "generate-ts", + ts(export_to = "ts-packages/types/src/types/rust/Period.ts") +)] +#[cw_serde] +/// The vesting period. +pub enum Period { + /// Defines a pre-vesting period. + Before, + + /// Defines currently active vesting period. + In(usize), + + /// Defines a post-vesting period. + After, +} + +/// Information regarding pledge (i.e. mixnode or gateway bonding) made with vesting tokens. +#[cw_serde] +pub struct PledgeData { + /// The amount pledged. + pub amount: Coin, + + /// The block timestamp where the pledge occurred. + pub block_time: Timestamp, +} + +impl PledgeData { + pub fn amount(&self) -> Coin { + self.amount.clone() + } + + pub fn block_time(&self) -> Timestamp { + self.block_time + } + + pub fn new(amount: Coin, block_time: Timestamp) -> Self { + Self { amount, block_time } + } +} + +/// Defines cap for pleding/staking tokens. +#[cw_serde] +pub enum PledgeCap { + /// Specifies a percent-based pledge cap, i.e. only given % of tokens could be pledged/staked. + Percent(Percent), + + /// Specifies an absolute pledge cap, i.e. an explicit value that could be pledged/staked. + Absolute(Uint128), // This has to be in unym +} + +impl FromStr for PledgeCap { + type Err = String; + + fn from_str(cap: &str) -> Result { + let cap = cap.replace('_', "").replace(',', "."); + match Percent::from_str(&cap) { + Ok(p) => Ok(PledgeCap::Percent(p)), + Err(_) => match cap.parse::() { + Ok(i) => Ok(PledgeCap::Absolute(Uint128::from(i))), + Err(_e) => Err(format!("Could not parse {cap} as Percent or Uint128")), + }, + } + } +} + +impl Default for PledgeCap { + fn default() -> Self { + #[allow(clippy::expect_used)] + PledgeCap::Percent(Percent::from_percentage_value(10).expect("This can never fail!")) + } +} + +/// Vesting period details. +#[cw_serde] +pub struct VestingPeriod { + /// The start time of this vesting period, as unix timestamp. + pub start_time: u64, + + /// The duration (in seconds) of the vesting period. + pub period_seconds: u64, +} + +impl VestingPeriod { + pub fn end_time(&self) -> Timestamp { + Timestamp::from_seconds(self.start_time + self.period_seconds) + } +} + +#[cw_serde] +#[derive(Default)] +pub struct VestingSpecification { + start_time: Option, + period_seconds: Option, + num_periods: Option, +} + +impl VestingSpecification { + pub fn new( + start_time: Option, + period_seconds: Option, + num_periods: Option, + ) -> Self { + Self { + start_time, + period_seconds, + num_periods, + } + } + + pub fn start_time(&self) -> Option { + self.start_time + } + + pub fn period_seconds(&self) -> u64 { + self.period_seconds.unwrap_or(3 * 30 * 86400) + } + + pub fn num_periods(&self) -> u64 { + self.num_periods.unwrap_or(8) + } + + pub fn populate_vesting_periods(&self, start_time: u64) -> Vec { + let mut periods = Vec::with_capacity(self.num_periods() as usize); + for i in 0..self.num_periods() { + let period = VestingPeriod { + start_time: start_time + i * self.period_seconds(), + period_seconds: self.period_seconds(), + }; + periods.push(period); + } + periods + } +} + +/// Details about particular vesting delegation. +#[cw_serde] +pub struct VestingDelegation { + /// The id of the vesting account that has made the delegation. + pub account_id: u32, + + /// The id of the mixnode towards which the delegation has been made. + pub mix_id: MixId, + + /// The block timestamp when the delegation has been made. + pub block_timestamp: u64, + + /// The raw amount delegated (interpreted to be in the same denom as the underlying vesting specification) + pub amount: Uint128, +} + +impl VestingDelegation { + pub fn storage_key(&self) -> (u32, MixId, u64) { + (self.account_id, self.mix_id, self.block_timestamp) + } +} diff --git a/common/types/Cargo.toml b/common/types/Cargo.toml index c99bbf3e30..cbf12b4364 100644 --- a/common/types/Cargo.toml +++ b/common/types/Cargo.toml @@ -27,8 +27,7 @@ nym-mixnet-contract-common = { path = "../../common/cosmwasm-smart-contracts/mix nym-vesting-contract-common = { path = "../../common/cosmwasm-smart-contracts/vesting-contract" } nym-config = { path = "../../common/config" } nym-coconut-interface = { path = "../../common/coconut-interface" } -# Used for Type conversion, can be extracted but its a lot of work -nym-vesting-contract = { path = "../../contracts/vesting" } + [dev-dependencies] tempfile = "3.3.0" diff --git a/common/types/src/delegation.rs b/common/types/src/delegation.rs index 399bf7b91b..550f17a261 100644 --- a/common/types/src/delegation.rs +++ b/common/types/src/delegation.rs @@ -41,7 +41,7 @@ impl Delegation { feature = "generate-ts", ts(export_to = "ts-packages/types/src/types/rust/DelegationWithEverything.ts") )] -#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq, JsonSchema)] +#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, JsonSchema)] pub struct DelegationWithEverything { pub owner: String, pub mix_id: MixId, diff --git a/common/types/src/mixnode.rs b/common/types/src/mixnode.rs index 145f9c409f..02246aad68 100644 --- a/common/types/src/mixnode.rs +++ b/common/types/src/mixnode.rs @@ -20,7 +20,7 @@ use std::net::IpAddr; feature = "generate-ts", ts(export_to = "ts-packages/types/src/types/rust/MixNodeDetails.ts") )] -#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize, JsonSchema)] +#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, JsonSchema)] pub struct MixNodeDetails { pub bond_information: MixNodeBond, pub rewarding_details: MixNodeRewarding, @@ -49,7 +49,7 @@ impl MixNodeDetails { feature = "generate-ts", ts(export_to = "ts-packages/types/src/types/rust/MixNodeBond.ts") )] -#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize, JsonSchema)] +#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, JsonSchema)] pub struct MixNodeBond { pub mix_id: MixId, pub owner: String, @@ -85,7 +85,7 @@ impl MixNodeBond { feature = "generate-ts", ts(export_to = "ts-packages/types/src/types/rust/MixNodeRewarding.ts") )] -#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize, JsonSchema)] +#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, JsonSchema)] pub struct MixNodeRewarding { pub cost_params: MixNodeCostParams, @@ -131,7 +131,7 @@ impl MixNodeRewarding { feature = "generate-ts", ts(export_to = "ts-packages/types/src/types/rust/MixNodeCostParams.ts") )] -#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize, JsonSchema)] +#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, JsonSchema)] pub struct MixNodeCostParams { #[cfg_attr(feature = "generate-ts", ts(type = "string"))] pub profit_margin_percent: Percent, diff --git a/common/types/src/pending_events.rs b/common/types/src/pending_events.rs index 23ecdf4f19..f5789890bf 100644 --- a/common/types/src/pending_events.rs +++ b/common/types/src/pending_events.rs @@ -127,7 +127,7 @@ impl PendingEpochEventData { feature = "generate-ts", ts(export_to = "ts-packages/types/src/types/rust/PendingIntervalEvent.ts") )] -#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq, JsonSchema)] +#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, JsonSchema)] pub struct PendingIntervalEvent { pub id: IntervalEventId, pub created_at: BlockHeight, @@ -155,7 +155,7 @@ impl PendingIntervalEvent { feature = "generate-ts", ts(export_to = "ts-packages/types/src/types/rust/PendingIntervalEventData.ts") )] -#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq, JsonSchema)] +#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, JsonSchema)] pub enum PendingIntervalEventData { ChangeMixCostParams { mix_id: MixId, diff --git a/common/types/src/vesting.rs b/common/types/src/vesting.rs index 9be8ef03da..1071b21324 100644 --- a/common/types/src/vesting.rs +++ b/common/types/src/vesting.rs @@ -1,10 +1,10 @@ use crate::currency::{DecCoin, RegisteredCoins}; use crate::error::TypesError; +use nym_vesting_contract_common::account::Account as ContractVestingAccount; +use nym_vesting_contract_common::types::VestingPeriod as ContractVestingPeriod; use nym_vesting_contract_common::OriginalVestingResponse as ContractOriginalVestingResponse; use nym_vesting_contract_common::PledgeData as ContractPledgeData; use serde::{Deserialize, Serialize}; -use vesting_contract::vesting::Account as ContractVestingAccount; -use vesting_contract::vesting::VestingPeriod as ContractVestingPeriod; #[cfg_attr(feature = "generate-ts", derive(ts_rs::TS))] #[cfg_attr( diff --git a/contracts/Cargo.lock b/contracts/Cargo.lock index 3d719dd6dc..1a87652242 100644 --- a/contracts/Cargo.lock +++ b/contracts/Cargo.lock @@ -73,6 +73,12 @@ dependencies = [ "generic-array", ] +[[package]] +name = "bnum" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "845141a4fade3f790628b7daaaa298a25b204fb28907eb54febe5142db6ce653" + [[package]] name = "bs58" version = "0.4.0" @@ -145,9 +151,9 @@ checksum = "520fbf3c07483f94e3e3ca9d0cfd913d7718ef2483d2cfd91c0d9e91474ab913" [[package]] name = "cosmwasm-crypto" -version = "1.2.5" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75836a10cb9654c54e77ee56da94d592923092a10b369cdb0dbd56acefc16340" +checksum = "0d076a08ec01ed23c4396aca98ec73a38fa1fee5f310465add52b4108181c7a8" dependencies = [ "digest 0.10.7", "ed25519-zebra", @@ -158,18 +164,18 @@ dependencies = [ [[package]] name = "cosmwasm-derive" -version = "1.2.5" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c9f7f0e51bfc7295f7b2664fe8513c966428642aa765dad8a74acdab5e0c773" +checksum = "dec361f3c09d7b41221948fc17be9b3c96cb58e55a02f82da36f888a651f2584" dependencies = [ "syn 1.0.109", ] [[package]] name = "cosmwasm-schema" -version = "1.2.5" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f00b363610218eea83f24bbab09e1a7c3920b79f068334fdfcc62f6129ef9fc" +checksum = "bb6b2fb76758ef59cddc77f2e2ae91c22f77da49037e9f182e9c2833f0e959b1" dependencies = [ "cosmwasm-schema-derive", "schemars", @@ -180,9 +186,9 @@ dependencies = [ [[package]] name = "cosmwasm-schema-derive" -version = "1.2.5" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae38f909b2822d32b275c9e2db9728497aa33ffe67dd463bc67c6a3b7092785c" +checksum = "2bfa39422f0d9f1c9a6fd3711573258495314dfa3aae738ea825ecd9964bc659" dependencies = [ "proc-macro2", "quote", @@ -191,11 +197,12 @@ dependencies = [ [[package]] name = "cosmwasm-std" -version = "1.2.5" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a49b85345e811c8e80ec55d0d091e4fcb4f00f97ab058f9be5f614c444a730cb" +checksum = "1f6dc2ee23313add5ecacc3ccac217b9967ad9d2d11bd56e5da6aa65a9da6138" dependencies = [ "base64 0.13.1", + "bnum", "cosmwasm-crypto", "cosmwasm-derive", "derivative", @@ -203,17 +210,16 @@ dependencies = [ "hex", "schemars", "serde", - "serde-json-wasm 0.5.1", + "serde-json-wasm", "sha2 0.10.6", "thiserror", - "uint", ] [[package]] name = "cosmwasm-storage" -version = "1.2.5" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3737a3aac48f5ed883b5b73bfb731e77feebd8fc6b43419844ec2971072164d" +checksum = "7ade8cae79dc08a06bcf119c0854ffaed11bd8cb1013c6b04abfe1f51f36211e" dependencies = [ "cosmwasm-std", "serde", @@ -228,12 +234,6 @@ dependencies = [ "libc", ] -[[package]] -name = "crunchy" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" - [[package]] name = "crypto-bigint" version = "0.4.9" @@ -271,9 +271,9 @@ dependencies = [ [[package]] name = "cw-controllers" -version = "1.0.1" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91440ce8ec4f0642798bc8c8cb6b9b53c1926c6dadaf0eed267a5145cd529071" +checksum = "d5d8edce4b78785f36413f67387e4be7d0cb7d032b5d4164bcc024f9c3f3f2ea" dependencies = [ "cosmwasm-schema", "cosmwasm-std", @@ -305,9 +305,9 @@ dependencies = [ [[package]] name = "cw-storage-plus" -version = "1.0.1" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "053a5083c258acd68386734f428a5a171b29f7d733151ae83090c6fcc9417ffa" +checksum = "3f0e92a069d62067f3472c62e30adedb4cab1754725c0f2a682b3128d2bf3c79" dependencies = [ "cosmwasm-std", "schemars", @@ -331,22 +331,23 @@ dependencies = [ [[package]] name = "cw2" -version = "1.0.1" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fb70cee2cf0b4a8ff7253e6bc6647107905e8eb37208f87d54f67810faa62f8" +checksum = "29ac2dc7a55ad64173ca1e0a46697c31b7a5c51342f55a1e84a724da4eb99908" dependencies = [ "cosmwasm-schema", "cosmwasm-std", "cw-storage-plus", "schemars", "serde", + "thiserror", ] [[package]] name = "cw20" -version = "1.0.1" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91666da6c7b40c8dd5ff94df655a28114efc10c79b70b4d06f13c31e37d60609" +checksum = "011c45920f8200bd5d32d4fe52502506f64f2f75651ab408054d4cfc75ca3a9b" dependencies = [ "cosmwasm-schema", "cosmwasm-std", @@ -375,9 +376,9 @@ dependencies = [ [[package]] name = "cw3" -version = "1.0.1" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fe0b587008aa221cd2a2579a21990a28c4347dc53ad43167c68ad765f5b6efa" +checksum = "171af3d9127de6805a7dd819fb070c7d2f6c3ea85f4193f42cef259f0a7f33d5" dependencies = [ "cosmwasm-schema", "cosmwasm-std", @@ -390,9 +391,9 @@ dependencies = [ [[package]] name = "cw3-fixed-multisig" -version = "1.0.1" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e9e2415adb201e5e89dab34edf59d7dc166bc558526de009a49ae66276c9119a" +checksum = "705875c68f95870e9707e7b72b50631ddc58c07517d6cc43758306e38921118e" dependencies = [ "cosmwasm-schema", "cosmwasm-std", @@ -409,6 +410,7 @@ dependencies = [ name = "cw3-flex-multisig" version = "1.0.0" dependencies = [ + "cosmwasm-schema", "cosmwasm-std", "cw-multi-test", "cw-storage-plus", @@ -422,14 +424,13 @@ dependencies = [ "cw4-group", "nym-group-contract-common", "nym-multisig-contract-common", - "schemars", ] [[package]] name = "cw4" -version = "1.0.1" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c236e0bae02ce97e89235a681dd0e07d099524b369f1ef908d704db3e6b049b" +checksum = "a398696307efadaaa2d0850076f865fa706c959d493cb4203314f72be6b77a64" dependencies = [ "cosmwasm-schema", "cosmwasm-std", @@ -1022,13 +1023,13 @@ dependencies = [ name = "nym-coconut-bandwidth" version = "0.1.0" dependencies = [ + "cosmwasm-schema", "cosmwasm-std", "cosmwasm-storage", "cw-controllers", "cw-storage-plus", "nym-coconut-bandwidth-contract-common", "nym-multisig-contract-common", - "schemars", "serde", "thiserror", ] @@ -1037,16 +1038,17 @@ dependencies = [ name = "nym-coconut-bandwidth-contract-common" version = "0.1.0" dependencies = [ + "cosmwasm-schema", "cosmwasm-std", + "cw2", "nym-multisig-contract-common", - "schemars", - "serde", ] [[package]] name = "nym-coconut-dkg" version = "0.1.0" dependencies = [ + "cosmwasm-schema", "cosmwasm-std", "cosmwasm-storage", "cw-controllers", @@ -1058,7 +1060,6 @@ dependencies = [ "nym-coconut-dkg-common", "nym-group-contract-common", "rusty-fork", - "schemars", "serde", "thiserror", ] @@ -1067,12 +1068,11 @@ dependencies = [ name = "nym-coconut-dkg-common" version = "0.1.0" dependencies = [ + "cosmwasm-schema", "cosmwasm-std", "cw-utils", "nym-contracts-common", "nym-multisig-contract-common", - "schemars", - "serde", ] [[package]] @@ -1080,6 +1080,7 @@ name = "nym-contracts-common" version = "0.5.0" dependencies = [ "bs58", + "cosmwasm-schema", "cosmwasm-std", "schemars", "serde", @@ -1128,7 +1129,6 @@ dependencies = [ "nym-mixnet-contract-common", "nym-vesting-contract-common", "rand_chacha 0.2.2", - "schemars", "semver", "serde", "thiserror", @@ -1141,13 +1141,15 @@ name = "nym-mixnet-contract-common" version = "0.6.0" dependencies = [ "bs58", + "cosmwasm-schema", "cosmwasm-std", + "cw2", "humantime-serde", "log", "nym-contracts-common", "schemars", "serde", - "serde-json-wasm 0.4.1", + "serde-json-wasm", "serde_repr", "thiserror", "time", @@ -1174,6 +1176,7 @@ version = "0.1.0" dependencies = [ "anyhow", "bs58", + "cosmwasm-schema", "cosmwasm-std", "cw-controllers", "cw-multi-test", @@ -1196,11 +1199,12 @@ dependencies = [ name = "nym-name-service-common" version = "0.1.0" dependencies = [ + "cosmwasm-schema", "cosmwasm-std", "cw-controllers", "cw-utils", + "cw2", "nym-contracts-common", - "schemars", "serde", "thiserror", ] @@ -1218,6 +1222,7 @@ version = "0.1.0" dependencies = [ "anyhow", "bs58", + "cosmwasm-schema", "cosmwasm-std", "cw-controllers", "cw-multi-test", @@ -1239,12 +1244,12 @@ dependencies = [ name = "nym-service-provider-directory-common" version = "0.1.0" dependencies = [ + "cosmwasm-schema", "cosmwasm-std", "cw-controllers", "cw-utils", + "cw2", "nym-contracts-common", - "schemars", - "serde", "thiserror", ] @@ -1262,6 +1267,7 @@ dependencies = [ "base64 0.21.0", "cosmwasm-crypto", "cosmwasm-derive", + "cosmwasm-schema", "cosmwasm-std", "cw-storage-plus", "cw2", @@ -1270,7 +1276,6 @@ dependencies = [ "nym-mixnet-contract-common", "nym-vesting-contract-common", "rand_chacha 0.3.1", - "schemars", "semver", "serde", "serde_json", @@ -1282,11 +1287,13 @@ dependencies = [ name = "nym-vesting-contract-common" version = "0.7.0" dependencies = [ + "cosmwasm-schema", "cosmwasm-std", + "cw2", "nym-contracts-common", "nym-mixnet-contract-common", - "schemars", "serde", + "thiserror", ] [[package]] @@ -1657,18 +1664,9 @@ dependencies = [ [[package]] name = "serde-json-wasm" -version = "0.4.1" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "479b4dbc401ca13ee8ce902851b834893251404c4f3c65370a49e047a6be09a5" -dependencies = [ - "serde", -] - -[[package]] -name = "serde-json-wasm" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16a62a1fad1e1828b24acac8f2b468971dade7b8c3c2e672bcadefefb1f8c137" +checksum = "a15bee9b04dd165c3f4e142628982ddde884c2022a89e8ddf99c4829bf2c3a58" dependencies = [ "serde", ] @@ -1770,12 +1768,6 @@ dependencies = [ "der", ] -[[package]] -name = "static_assertions" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" - [[package]] name = "subtle" version = "2.4.1" @@ -1894,18 +1886,6 @@ version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba" -[[package]] -name = "uint" -version = "0.9.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76f64bba2c53b04fcab63c01a7d7427eadc821e3bc48c34dc9ba29c501164b52" -dependencies = [ - "byteorder", - "crunchy", - "hex", - "static_assertions", -] - [[package]] name = "unicode-bidi" version = "0.3.12" diff --git a/contracts/Cargo.toml b/contracts/Cargo.toml index 1b20380ad7..0887794260 100644 --- a/contracts/Cargo.toml +++ b/contracts/Cargo.toml @@ -32,17 +32,17 @@ incremental = false overflow-checks = true [workspace.dependencies] -cosmwasm-crypto = "=1.2.5" -cosmwasm-derive = "=1.2.5" -cosmwasm-schema = "=1.2.5" -cosmwasm-std = "=1.2.5" -cosmwasm-storage = "=1.2.5" -cw-controllers = "=1.0.1" +cosmwasm-crypto = "=1.3.0" +cosmwasm-derive = "=1.3.0" +cosmwasm-schema = "=1.3.0" +cosmwasm-std = "=1.3.0" +cosmwasm-storage = "=1.3.0" +cw-controllers = "=1.1.0" cw-multi-test = "=0.16.4" -cw-storage-plus = "=1.0.1" +cw-storage-plus = "=1.1.0" cw-utils = "=1.0.1" -cw2 = "=1.0.1" -cw3 = "=1.0.1" -cw3-fixed-multisig = "=1.0.1" -cw4 = "=1.0.1" -cw20 = "=1.0.1" +cw2 = "=1.1.0" +cw3 = "=1.1.0" +cw3-fixed-multisig = "=1.1.0" +cw4 = "=1.1.0" +cw20 = "=1.1.0" diff --git a/contracts/Makefile b/contracts/Makefile new file mode 100644 index 0000000000..941959bf0c --- /dev/null +++ b/contracts/Makefile @@ -0,0 +1,25 @@ +schema: coconut-bandwidth-schema coconut-dkg-schema mixnet-schema name-service-schema service-provider-directory-schema vesting-schema multisig-schema group-schema + +coconut-bandwidth-schema: + $(MAKE) -C coconut-bandwidth generate-schema + +coconut-dkg-schema: + $(MAKE) -C coconut-dkg generate-schema + +mixnet-schema: + $(MAKE) -C mixnet generate-schema + +name-service-schema: + $(MAKE) -C name-service generate-schema + +service-provider-directory-schema: + $(MAKE) -C service-provider-directory generate-schema + +vesting-schema: + $(MAKE) -C vesting generate-schema + +multisig-schema: + $(MAKE) -C multisig/cw3-flex-multisig generate-schema + +group-schema: + $(MAKE) -C multisig/cw4-group generate-schema diff --git a/contracts/coconut-bandwidth/.cargo/config b/contracts/coconut-bandwidth/.cargo/config new file mode 100644 index 0000000000..2fb2c1afdb --- /dev/null +++ b/contracts/coconut-bandwidth/.cargo/config @@ -0,0 +1,4 @@ +[alias] +wasm = "build --release --lib --target wasm32-unknown-unknown" +unit-test = "test --lib" +schema = "run --bin schema --features=schema-gen" \ No newline at end of file diff --git a/contracts/coconut-bandwidth/Cargo.toml b/contracts/coconut-bandwidth/Cargo.toml index 69bb7088d5..5ed8e5f98b 100644 --- a/contracts/coconut-bandwidth/Cargo.toml +++ b/contracts/coconut-bandwidth/Cargo.toml @@ -5,6 +5,10 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html +[[bin]] +name = "schema" +required-features = ["schema-gen"] + [lib] crate-type = ["cdylib", "rlib"] @@ -13,10 +17,13 @@ nym-coconut-bandwidth-contract-common = { path = "../../common/cosmwasm-smart-co nym-multisig-contract-common = { path = "../../common/cosmwasm-smart-contracts/multisig-contract" } cosmwasm-std = { workspace = true } +cosmwasm-schema = { workspace = true, optional = true } cosmwasm-storage = { workspace = true } cw-storage-plus = { workspace = true } cw-controllers = { workspace = true } -schemars = "0.8" serde = { version = "1.0.103", default-features = false, features = ["derive"] } thiserror = "1.0.23" + +[features] +schema-gen = ["nym-coconut-bandwidth-contract-common/schema", "cosmwasm-schema"] \ No newline at end of file diff --git a/contracts/coconut-bandwidth/Makefile b/contracts/coconut-bandwidth/Makefile new file mode 100644 index 0000000000..f138e2c879 --- /dev/null +++ b/contracts/coconut-bandwidth/Makefile @@ -0,0 +1,2 @@ +generate-schema: + cargo schema \ No newline at end of file diff --git a/contracts/coconut-bandwidth/schema/nym-coconut-bandwidth.json b/contracts/coconut-bandwidth/schema/nym-coconut-bandwidth.json new file mode 100644 index 0000000000..1b210e85f9 --- /dev/null +++ b/contracts/coconut-bandwidth/schema/nym-coconut-bandwidth.json @@ -0,0 +1,382 @@ +{ + "contract_name": "nym-coconut-bandwidth", + "contract_version": "0.1.0", + "idl_version": "1.0.0", + "instantiate": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "InstantiateMsg", + "type": "object", + "required": [ + "mix_denom", + "multisig_addr", + "pool_addr" + ], + "properties": { + "mix_denom": { + "type": "string" + }, + "multisig_addr": { + "type": "string" + }, + "pool_addr": { + "type": "string" + } + }, + "additionalProperties": false + }, + "execute": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ExecuteMsg", + "oneOf": [ + { + "type": "object", + "required": [ + "deposit_funds" + ], + "properties": { + "deposit_funds": { + "type": "object", + "required": [ + "data" + ], + "properties": { + "data": { + "$ref": "#/definitions/DepositData" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "spend_credential" + ], + "properties": { + "spend_credential": { + "type": "object", + "required": [ + "data" + ], + "properties": { + "data": { + "$ref": "#/definitions/SpendCredentialData" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "release_funds" + ], + "properties": { + "release_funds": { + "type": "object", + "required": [ + "funds" + ], + "properties": { + "funds": { + "$ref": "#/definitions/Coin" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + ], + "definitions": { + "Coin": { + "type": "object", + "required": [ + "amount", + "denom" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "denom": { + "type": "string" + } + } + }, + "DepositData": { + "type": "object", + "required": [ + "deposit_info", + "encryption_key", + "identity_key" + ], + "properties": { + "deposit_info": { + "type": "string" + }, + "encryption_key": { + "type": "string" + }, + "identity_key": { + "type": "string" + } + }, + "additionalProperties": false + }, + "SpendCredentialData": { + "type": "object", + "required": [ + "blinded_serial_number", + "funds", + "gateway_cosmos_address" + ], + "properties": { + "blinded_serial_number": { + "type": "string" + }, + "funds": { + "$ref": "#/definitions/Coin" + }, + "gateway_cosmos_address": { + "type": "string" + } + }, + "additionalProperties": false + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } + }, + "query": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "QueryMsg", + "oneOf": [ + { + "type": "object", + "required": [ + "get_spent_credential" + ], + "properties": { + "get_spent_credential": { + "type": "object", + "required": [ + "blinded_serial_number" + ], + "properties": { + "blinded_serial_number": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "get_all_spent_credentials" + ], + "properties": { + "get_all_spent_credentials": { + "type": "object", + "properties": { + "limit": { + "type": [ + "integer", + "null" + ], + "format": "uint32", + "minimum": 0.0 + }, + "start_after": { + "type": [ + "string", + "null" + ] + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + ] + }, + "migrate": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "MigrateMsg", + "type": "object", + "additionalProperties": false + }, + "sudo": null, + "responses": { + "get_all_spent_credentials": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "PagedSpendCredentialResponse", + "type": "object", + "required": [ + "per_page", + "spend_credentials" + ], + "properties": { + "per_page": { + "type": "integer", + "format": "uint", + "minimum": 0.0 + }, + "spend_credentials": { + "type": "array", + "items": { + "$ref": "#/definitions/SpendCredential" + } + }, + "start_next_after": { + "description": "Field indicating paging information for the following queries if the caller wishes to get further entries.", + "type": [ + "string", + "null" + ] + } + }, + "additionalProperties": false, + "definitions": { + "Addr": { + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + }, + "Coin": { + "type": "object", + "required": [ + "amount", + "denom" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "denom": { + "type": "string" + } + } + }, + "SpendCredential": { + "type": "object", + "required": [ + "blinded_serial_number", + "funds", + "gateway_cosmos_address", + "status" + ], + "properties": { + "blinded_serial_number": { + "type": "string" + }, + "funds": { + "$ref": "#/definitions/Coin" + }, + "gateway_cosmos_address": { + "$ref": "#/definitions/Addr" + }, + "status": { + "$ref": "#/definitions/SpendCredentialStatus" + } + }, + "additionalProperties": false + }, + "SpendCredentialStatus": { + "type": "string", + "enum": [ + "in_progress", + "spent" + ] + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } + }, + "get_spent_credential": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "SpendCredentialResponse", + "type": "object", + "properties": { + "spend_credential": { + "anyOf": [ + { + "$ref": "#/definitions/SpendCredential" + }, + { + "type": "null" + } + ] + } + }, + "additionalProperties": false, + "definitions": { + "Addr": { + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + }, + "Coin": { + "type": "object", + "required": [ + "amount", + "denom" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "denom": { + "type": "string" + } + } + }, + "SpendCredential": { + "type": "object", + "required": [ + "blinded_serial_number", + "funds", + "gateway_cosmos_address", + "status" + ], + "properties": { + "blinded_serial_number": { + "type": "string" + }, + "funds": { + "$ref": "#/definitions/Coin" + }, + "gateway_cosmos_address": { + "$ref": "#/definitions/Addr" + }, + "status": { + "$ref": "#/definitions/SpendCredentialStatus" + } + }, + "additionalProperties": false + }, + "SpendCredentialStatus": { + "type": "string", + "enum": [ + "in_progress", + "spent" + ] + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } + } + } +} diff --git a/contracts/coconut-bandwidth/schema/raw/execute.json b/contracts/coconut-bandwidth/schema/raw/execute.json new file mode 100644 index 0000000000..027b423f7a --- /dev/null +++ b/contracts/coconut-bandwidth/schema/raw/execute.json @@ -0,0 +1,130 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ExecuteMsg", + "oneOf": [ + { + "type": "object", + "required": [ + "deposit_funds" + ], + "properties": { + "deposit_funds": { + "type": "object", + "required": [ + "data" + ], + "properties": { + "data": { + "$ref": "#/definitions/DepositData" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "spend_credential" + ], + "properties": { + "spend_credential": { + "type": "object", + "required": [ + "data" + ], + "properties": { + "data": { + "$ref": "#/definitions/SpendCredentialData" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "release_funds" + ], + "properties": { + "release_funds": { + "type": "object", + "required": [ + "funds" + ], + "properties": { + "funds": { + "$ref": "#/definitions/Coin" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + ], + "definitions": { + "Coin": { + "type": "object", + "required": [ + "amount", + "denom" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "denom": { + "type": "string" + } + } + }, + "DepositData": { + "type": "object", + "required": [ + "deposit_info", + "encryption_key", + "identity_key" + ], + "properties": { + "deposit_info": { + "type": "string" + }, + "encryption_key": { + "type": "string" + }, + "identity_key": { + "type": "string" + } + }, + "additionalProperties": false + }, + "SpendCredentialData": { + "type": "object", + "required": [ + "blinded_serial_number", + "funds", + "gateway_cosmos_address" + ], + "properties": { + "blinded_serial_number": { + "type": "string" + }, + "funds": { + "$ref": "#/definitions/Coin" + }, + "gateway_cosmos_address": { + "type": "string" + } + }, + "additionalProperties": false + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } +} diff --git a/contracts/coconut-bandwidth/schema/raw/instantiate.json b/contracts/coconut-bandwidth/schema/raw/instantiate.json new file mode 100644 index 0000000000..172dc76d79 --- /dev/null +++ b/contracts/coconut-bandwidth/schema/raw/instantiate.json @@ -0,0 +1,22 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "InstantiateMsg", + "type": "object", + "required": [ + "mix_denom", + "multisig_addr", + "pool_addr" + ], + "properties": { + "mix_denom": { + "type": "string" + }, + "multisig_addr": { + "type": "string" + }, + "pool_addr": { + "type": "string" + } + }, + "additionalProperties": false +} diff --git a/contracts/coconut-bandwidth/schema/raw/migrate.json b/contracts/coconut-bandwidth/schema/raw/migrate.json new file mode 100644 index 0000000000..7fbe8c5708 --- /dev/null +++ b/contracts/coconut-bandwidth/schema/raw/migrate.json @@ -0,0 +1,6 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "MigrateMsg", + "type": "object", + "additionalProperties": false +} diff --git a/contracts/coconut-bandwidth/schema/raw/query.json b/contracts/coconut-bandwidth/schema/raw/query.json new file mode 100644 index 0000000000..afdfae331d --- /dev/null +++ b/contracts/coconut-bandwidth/schema/raw/query.json @@ -0,0 +1,56 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "QueryMsg", + "oneOf": [ + { + "type": "object", + "required": [ + "get_spent_credential" + ], + "properties": { + "get_spent_credential": { + "type": "object", + "required": [ + "blinded_serial_number" + ], + "properties": { + "blinded_serial_number": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "get_all_spent_credentials" + ], + "properties": { + "get_all_spent_credentials": { + "type": "object", + "properties": { + "limit": { + "type": [ + "integer", + "null" + ], + "format": "uint32", + "minimum": 0.0 + }, + "start_after": { + "type": [ + "string", + "null" + ] + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + ] +} diff --git a/contracts/coconut-bandwidth/schema/raw/response_to_get_all_spent_credentials.json b/contracts/coconut-bandwidth/schema/raw/response_to_get_all_spent_credentials.json new file mode 100644 index 0000000000..733da87081 --- /dev/null +++ b/contracts/coconut-bandwidth/schema/raw/response_to_get_all_spent_credentials.json @@ -0,0 +1,86 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "PagedSpendCredentialResponse", + "type": "object", + "required": [ + "per_page", + "spend_credentials" + ], + "properties": { + "per_page": { + "type": "integer", + "format": "uint", + "minimum": 0.0 + }, + "spend_credentials": { + "type": "array", + "items": { + "$ref": "#/definitions/SpendCredential" + } + }, + "start_next_after": { + "description": "Field indicating paging information for the following queries if the caller wishes to get further entries.", + "type": [ + "string", + "null" + ] + } + }, + "additionalProperties": false, + "definitions": { + "Addr": { + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + }, + "Coin": { + "type": "object", + "required": [ + "amount", + "denom" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "denom": { + "type": "string" + } + } + }, + "SpendCredential": { + "type": "object", + "required": [ + "blinded_serial_number", + "funds", + "gateway_cosmos_address", + "status" + ], + "properties": { + "blinded_serial_number": { + "type": "string" + }, + "funds": { + "$ref": "#/definitions/Coin" + }, + "gateway_cosmos_address": { + "$ref": "#/definitions/Addr" + }, + "status": { + "$ref": "#/definitions/SpendCredentialStatus" + } + }, + "additionalProperties": false + }, + "SpendCredentialStatus": { + "type": "string", + "enum": [ + "in_progress", + "spent" + ] + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } +} diff --git a/contracts/coconut-bandwidth/schema/raw/response_to_get_spent_credential.json b/contracts/coconut-bandwidth/schema/raw/response_to_get_spent_credential.json new file mode 100644 index 0000000000..9af4def85b --- /dev/null +++ b/contracts/coconut-bandwidth/schema/raw/response_to_get_spent_credential.json @@ -0,0 +1,74 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "SpendCredentialResponse", + "type": "object", + "properties": { + "spend_credential": { + "anyOf": [ + { + "$ref": "#/definitions/SpendCredential" + }, + { + "type": "null" + } + ] + } + }, + "additionalProperties": false, + "definitions": { + "Addr": { + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + }, + "Coin": { + "type": "object", + "required": [ + "amount", + "denom" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "denom": { + "type": "string" + } + } + }, + "SpendCredential": { + "type": "object", + "required": [ + "blinded_serial_number", + "funds", + "gateway_cosmos_address", + "status" + ], + "properties": { + "blinded_serial_number": { + "type": "string" + }, + "funds": { + "$ref": "#/definitions/Coin" + }, + "gateway_cosmos_address": { + "$ref": "#/definitions/Addr" + }, + "status": { + "$ref": "#/definitions/SpendCredentialStatus" + } + }, + "additionalProperties": false + }, + "SpendCredentialStatus": { + "type": "string", + "enum": [ + "in_progress", + "spent" + ] + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } +} diff --git a/contracts/coconut-bandwidth/src/bin/schema.rs b/contracts/coconut-bandwidth/src/bin/schema.rs new file mode 100644 index 0000000000..4956eb33f4 --- /dev/null +++ b/contracts/coconut-bandwidth/src/bin/schema.rs @@ -0,0 +1,16 @@ +// Copyright 2023 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use cosmwasm_schema::write_api; +use nym_coconut_bandwidth_contract_common::msg::{ + ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg, +}; + +fn main() { + write_api! { + instantiate: InstantiateMsg, + query: QueryMsg, + execute: ExecuteMsg, + migrate: MigrateMsg, + } +} diff --git a/contracts/coconut-bandwidth/src/state.rs b/contracts/coconut-bandwidth/src/state.rs index 74b969341c..855d9fa07a 100644 --- a/contracts/coconut-bandwidth/src/state.rs +++ b/contracts/coconut-bandwidth/src/state.rs @@ -4,12 +4,11 @@ use cosmwasm_std::Addr; use cw_controllers::Admin; use cw_storage_plus::Item; -use schemars::JsonSchema; use serde::{Deserialize, Serialize}; pub const ADMIN: Admin = Admin::new("admin"); -#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, JsonSchema, Debug)] +#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug)] pub struct Config { pub multisig_addr: Addr, pub pool_addr: Addr, diff --git a/contracts/coconut-dkg/.cargo/config b/contracts/coconut-dkg/.cargo/config new file mode 100644 index 0000000000..2f698e22b3 --- /dev/null +++ b/contracts/coconut-dkg/.cargo/config @@ -0,0 +1,5 @@ +[alias] +wasm = "build --release --lib --target wasm32-unknown-unknown" +wasm-debug = "build --target wasm32-unknown-unknown" +unit-test = "test --lib" +schema = "run --bin schema --features=schema-gen" \ No newline at end of file diff --git a/contracts/coconut-dkg/Cargo.toml b/contracts/coconut-dkg/Cargo.toml index f877df138d..794c1f3a0f 100644 --- a/contracts/coconut-dkg/Cargo.toml +++ b/contracts/coconut-dkg/Cargo.toml @@ -5,19 +5,22 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html +[[bin]] +name = "schema" +required-features = ["schema-gen"] + [lib] crate-type = ["cdylib", "rlib"] [dependencies] nym-coconut-dkg-common = { path = "../../common/cosmwasm-smart-contracts/coconut-dkg" } +cosmwasm-schema = { workspace = true, optional = true } cosmwasm-std = { workspace = true } cosmwasm-storage = { workspace = true } cw-storage-plus = { workspace = true } cw-controllers = { workspace = true } cw4 = { workspace = true } - -schemars = "0.8" serde = { version = "1.0.103", default-features = false, features = ["derive"] } thiserror = "1.0.23" @@ -27,3 +30,7 @@ cw4-group = { path = "../multisig/cw4-group" } nym-group-contract-common = { path = "../../common/cosmwasm-smart-contracts/group-contract" } lazy_static = "1.4" rusty-fork = "0.3" + +[features] +schema-gen = ["nym-coconut-dkg-common/schema", "cosmwasm-schema"] + diff --git a/contracts/coconut-dkg/Makefile b/contracts/coconut-dkg/Makefile new file mode 100644 index 0000000000..f138e2c879 --- /dev/null +++ b/contracts/coconut-dkg/Makefile @@ -0,0 +1,2 @@ +generate-schema: + cargo schema \ No newline at end of file diff --git a/contracts/coconut-dkg/schema/nym-coconut-dkg.json b/contracts/coconut-dkg/schema/nym-coconut-dkg.json new file mode 100644 index 0000000000..035f1bfe8f --- /dev/null +++ b/contracts/coconut-dkg/schema/nym-coconut-dkg.json @@ -0,0 +1,1023 @@ +{ + "contract_name": "nym-coconut-dkg", + "contract_version": "0.1.0", + "idl_version": "1.0.0", + "instantiate": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "InstantiateMsg", + "type": "object", + "required": [ + "group_addr", + "mix_denom", + "multisig_addr" + ], + "properties": { + "group_addr": { + "type": "string" + }, + "mix_denom": { + "type": "string" + }, + "multisig_addr": { + "type": "string" + }, + "time_configuration": { + "anyOf": [ + { + "$ref": "#/definitions/TimeConfiguration" + }, + { + "type": "null" + } + ] + } + }, + "additionalProperties": false, + "definitions": { + "TimeConfiguration": { + "type": "object", + "required": [ + "dealing_exchange_time_secs", + "in_progress_time_secs", + "public_key_submission_time_secs", + "verification_key_finalization_time_secs", + "verification_key_submission_time_secs", + "verification_key_validation_time_secs" + ], + "properties": { + "dealing_exchange_time_secs": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "in_progress_time_secs": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "public_key_submission_time_secs": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "verification_key_finalization_time_secs": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "verification_key_submission_time_secs": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "verification_key_validation_time_secs": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + } + }, + "execute": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ExecuteMsg", + "oneOf": [ + { + "type": "object", + "required": [ + "register_dealer" + ], + "properties": { + "register_dealer": { + "type": "object", + "required": [ + "announce_address", + "bte_key_with_proof", + "resharing" + ], + "properties": { + "announce_address": { + "type": "string" + }, + "bte_key_with_proof": { + "type": "string" + }, + "resharing": { + "type": "boolean" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "commit_dealing" + ], + "properties": { + "commit_dealing": { + "type": "object", + "required": [ + "dealing_bytes", + "resharing" + ], + "properties": { + "dealing_bytes": { + "$ref": "#/definitions/ContractSafeBytes" + }, + "resharing": { + "type": "boolean" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "commit_verification_key_share" + ], + "properties": { + "commit_verification_key_share": { + "type": "object", + "required": [ + "resharing", + "share" + ], + "properties": { + "resharing": { + "type": "boolean" + }, + "share": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "verify_verification_key_share" + ], + "properties": { + "verify_verification_key_share": { + "type": "object", + "required": [ + "owner", + "resharing" + ], + "properties": { + "owner": { + "$ref": "#/definitions/Addr" + }, + "resharing": { + "type": "boolean" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "surpassed_threshold" + ], + "properties": { + "surpassed_threshold": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "advance_epoch_state" + ], + "properties": { + "advance_epoch_state": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + } + ], + "definitions": { + "Addr": { + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + }, + "ContractSafeBytes": { + "type": "array", + "items": { + "type": "integer", + "format": "uint8", + "minimum": 0.0 + } + } + } + }, + "query": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "QueryMsg", + "oneOf": [ + { + "type": "object", + "required": [ + "get_current_epoch_state" + ], + "properties": { + "get_current_epoch_state": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "get_current_epoch_threshold" + ], + "properties": { + "get_current_epoch_threshold": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "get_initial_dealers" + ], + "properties": { + "get_initial_dealers": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "get_dealer_details" + ], + "properties": { + "get_dealer_details": { + "type": "object", + "required": [ + "dealer_address" + ], + "properties": { + "dealer_address": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "get_current_dealers" + ], + "properties": { + "get_current_dealers": { + "type": "object", + "properties": { + "limit": { + "type": [ + "integer", + "null" + ], + "format": "uint32", + "minimum": 0.0 + }, + "start_after": { + "type": [ + "string", + "null" + ] + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "get_past_dealers" + ], + "properties": { + "get_past_dealers": { + "type": "object", + "properties": { + "limit": { + "type": [ + "integer", + "null" + ], + "format": "uint32", + "minimum": 0.0 + }, + "start_after": { + "type": [ + "string", + "null" + ] + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "get_dealing" + ], + "properties": { + "get_dealing": { + "type": "object", + "required": [ + "idx" + ], + "properties": { + "idx": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "limit": { + "type": [ + "integer", + "null" + ], + "format": "uint32", + "minimum": 0.0 + }, + "start_after": { + "type": [ + "string", + "null" + ] + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "get_verification_keys" + ], + "properties": { + "get_verification_keys": { + "type": "object", + "required": [ + "epoch_id" + ], + "properties": { + "epoch_id": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "limit": { + "type": [ + "integer", + "null" + ], + "format": "uint32", + "minimum": 0.0 + }, + "start_after": { + "type": [ + "string", + "null" + ] + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + ] + }, + "migrate": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "MigrateMsg", + "type": "object", + "additionalProperties": false + }, + "sudo": null, + "responses": { + "get_current_dealers": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "PagedDealerResponse", + "type": "object", + "required": [ + "dealers", + "per_page" + ], + "properties": { + "dealers": { + "type": "array", + "items": { + "$ref": "#/definitions/DealerDetails" + } + }, + "per_page": { + "type": "integer", + "format": "uint", + "minimum": 0.0 + }, + "start_next_after": { + "description": "Field indicating paging information for the following queries if the caller wishes to get further entries.", + "anyOf": [ + { + "$ref": "#/definitions/Addr" + }, + { + "type": "null" + } + ] + } + }, + "additionalProperties": false, + "definitions": { + "Addr": { + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + }, + "DealerDetails": { + "type": "object", + "required": [ + "address", + "announce_address", + "assigned_index", + "bte_public_key_with_proof" + ], + "properties": { + "address": { + "$ref": "#/definitions/Addr" + }, + "announce_address": { + "type": "string" + }, + "assigned_index": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "bte_public_key_with_proof": { + "type": "string" + } + }, + "additionalProperties": false + } + } + }, + "get_current_epoch_state": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Epoch", + "type": "object", + "required": [ + "epoch_id", + "finish_timestamp", + "state", + "time_configuration" + ], + "properties": { + "epoch_id": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "finish_timestamp": { + "$ref": "#/definitions/Timestamp" + }, + "state": { + "$ref": "#/definitions/EpochState" + }, + "time_configuration": { + "$ref": "#/definitions/TimeConfiguration" + } + }, + "additionalProperties": false, + "definitions": { + "EpochState": { + "oneOf": [ + { + "type": "string", + "enum": [ + "in_progress" + ] + }, + { + "type": "object", + "required": [ + "public_key_submission" + ], + "properties": { + "public_key_submission": { + "type": "object", + "required": [ + "resharing" + ], + "properties": { + "resharing": { + "type": "boolean" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "dealing_exchange" + ], + "properties": { + "dealing_exchange": { + "type": "object", + "required": [ + "resharing" + ], + "properties": { + "resharing": { + "type": "boolean" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "verification_key_submission" + ], + "properties": { + "verification_key_submission": { + "type": "object", + "required": [ + "resharing" + ], + "properties": { + "resharing": { + "type": "boolean" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "verification_key_validation" + ], + "properties": { + "verification_key_validation": { + "type": "object", + "required": [ + "resharing" + ], + "properties": { + "resharing": { + "type": "boolean" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "verification_key_finalization" + ], + "properties": { + "verification_key_finalization": { + "type": "object", + "required": [ + "resharing" + ], + "properties": { + "resharing": { + "type": "boolean" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + ] + }, + "TimeConfiguration": { + "type": "object", + "required": [ + "dealing_exchange_time_secs", + "in_progress_time_secs", + "public_key_submission_time_secs", + "verification_key_finalization_time_secs", + "verification_key_submission_time_secs", + "verification_key_validation_time_secs" + ], + "properties": { + "dealing_exchange_time_secs": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "in_progress_time_secs": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "public_key_submission_time_secs": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "verification_key_finalization_time_secs": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "verification_key_submission_time_secs": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "verification_key_validation_time_secs": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + }, + "Timestamp": { + "description": "A point in time in nanosecond precision.\n\nThis type can represent times from 1970-01-01T00:00:00Z to 2554-07-21T23:34:33Z.\n\n## Examples\n\n``` # use cosmwasm_std::Timestamp; let ts = Timestamp::from_nanos(1_000_000_202); assert_eq!(ts.nanos(), 1_000_000_202); assert_eq!(ts.seconds(), 1); assert_eq!(ts.subsec_nanos(), 202);\n\nlet ts = ts.plus_seconds(2); assert_eq!(ts.nanos(), 3_000_000_202); assert_eq!(ts.seconds(), 3); assert_eq!(ts.subsec_nanos(), 202); ```", + "allOf": [ + { + "$ref": "#/definitions/Uint64" + } + ] + }, + "Uint64": { + "description": "A thin wrapper around u64 that is using strings for JSON encoding/decoding, such that the full u64 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u64` to get the value out:\n\n``` # use cosmwasm_std::Uint64; let a = Uint64::from(42u64); assert_eq!(a.u64(), 42);\n\nlet b = Uint64::from(70u32); assert_eq!(b.u64(), 70); ```", + "type": "string" + } + } + }, + "get_current_epoch_threshold": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "uint64", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "get_dealer_details": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "DealerDetailsResponse", + "type": "object", + "required": [ + "dealer_type" + ], + "properties": { + "dealer_type": { + "$ref": "#/definitions/DealerType" + }, + "details": { + "anyOf": [ + { + "$ref": "#/definitions/DealerDetails" + }, + { + "type": "null" + } + ] + } + }, + "additionalProperties": false, + "definitions": { + "Addr": { + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + }, + "DealerDetails": { + "type": "object", + "required": [ + "address", + "announce_address", + "assigned_index", + "bte_public_key_with_proof" + ], + "properties": { + "address": { + "$ref": "#/definitions/Addr" + }, + "announce_address": { + "type": "string" + }, + "assigned_index": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "bte_public_key_with_proof": { + "type": "string" + } + }, + "additionalProperties": false + }, + "DealerType": { + "type": "string", + "enum": [ + "current", + "past", + "unknown" + ] + } + } + }, + "get_dealing": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "PagedDealingsResponse", + "type": "object", + "required": [ + "dealings", + "per_page" + ], + "properties": { + "dealings": { + "type": "array", + "items": { + "$ref": "#/definitions/ContractDealing" + } + }, + "per_page": { + "type": "integer", + "format": "uint", + "minimum": 0.0 + }, + "start_next_after": { + "description": "Field indicating paging information for the following queries if the caller wishes to get further entries.", + "anyOf": [ + { + "$ref": "#/definitions/Addr" + }, + { + "type": "null" + } + ] + } + }, + "additionalProperties": false, + "definitions": { + "Addr": { + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + }, + "ContractDealing": { + "type": "object", + "required": [ + "dealer", + "dealing" + ], + "properties": { + "dealer": { + "$ref": "#/definitions/Addr" + }, + "dealing": { + "$ref": "#/definitions/ContractSafeBytes" + } + }, + "additionalProperties": false + }, + "ContractSafeBytes": { + "type": "array", + "items": { + "type": "integer", + "format": "uint8", + "minimum": 0.0 + } + } + } + }, + "get_initial_dealers": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Nullable_InitialReplacementData", + "anyOf": [ + { + "$ref": "#/definitions/InitialReplacementData" + }, + { + "type": "null" + } + ], + "definitions": { + "Addr": { + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + }, + "InitialReplacementData": { + "type": "object", + "required": [ + "initial_dealers", + "initial_height" + ], + "properties": { + "initial_dealers": { + "type": "array", + "items": { + "$ref": "#/definitions/Addr" + } + }, + "initial_height": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + } + }, + "get_past_dealers": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "PagedDealerResponse", + "type": "object", + "required": [ + "dealers", + "per_page" + ], + "properties": { + "dealers": { + "type": "array", + "items": { + "$ref": "#/definitions/DealerDetails" + } + }, + "per_page": { + "type": "integer", + "format": "uint", + "minimum": 0.0 + }, + "start_next_after": { + "description": "Field indicating paging information for the following queries if the caller wishes to get further entries.", + "anyOf": [ + { + "$ref": "#/definitions/Addr" + }, + { + "type": "null" + } + ] + } + }, + "additionalProperties": false, + "definitions": { + "Addr": { + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + }, + "DealerDetails": { + "type": "object", + "required": [ + "address", + "announce_address", + "assigned_index", + "bte_public_key_with_proof" + ], + "properties": { + "address": { + "$ref": "#/definitions/Addr" + }, + "announce_address": { + "type": "string" + }, + "assigned_index": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "bte_public_key_with_proof": { + "type": "string" + } + }, + "additionalProperties": false + } + } + }, + "get_verification_keys": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "PagedVKSharesResponse", + "type": "object", + "required": [ + "per_page", + "shares" + ], + "properties": { + "per_page": { + "type": "integer", + "format": "uint", + "minimum": 0.0 + }, + "shares": { + "type": "array", + "items": { + "$ref": "#/definitions/ContractVKShare" + } + }, + "start_next_after": { + "description": "Field indicating paging information for the following queries if the caller wishes to get further entries.", + "anyOf": [ + { + "$ref": "#/definitions/Addr" + }, + { + "type": "null" + } + ] + } + }, + "additionalProperties": false, + "definitions": { + "Addr": { + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + }, + "ContractVKShare": { + "type": "object", + "required": [ + "announce_address", + "epoch_id", + "node_index", + "owner", + "share", + "verified" + ], + "properties": { + "announce_address": { + "type": "string" + }, + "epoch_id": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "node_index": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "owner": { + "$ref": "#/definitions/Addr" + }, + "share": { + "type": "string" + }, + "verified": { + "type": "boolean" + } + }, + "additionalProperties": false + } + } + } + } +} diff --git a/contracts/coconut-dkg/schema/raw/execute.json b/contracts/coconut-dkg/schema/raw/execute.json new file mode 100644 index 0000000000..5e87d1dc07 --- /dev/null +++ b/contracts/coconut-dkg/schema/raw/execute.json @@ -0,0 +1,150 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ExecuteMsg", + "oneOf": [ + { + "type": "object", + "required": [ + "register_dealer" + ], + "properties": { + "register_dealer": { + "type": "object", + "required": [ + "announce_address", + "bte_key_with_proof", + "resharing" + ], + "properties": { + "announce_address": { + "type": "string" + }, + "bte_key_with_proof": { + "type": "string" + }, + "resharing": { + "type": "boolean" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "commit_dealing" + ], + "properties": { + "commit_dealing": { + "type": "object", + "required": [ + "dealing_bytes", + "resharing" + ], + "properties": { + "dealing_bytes": { + "$ref": "#/definitions/ContractSafeBytes" + }, + "resharing": { + "type": "boolean" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "commit_verification_key_share" + ], + "properties": { + "commit_verification_key_share": { + "type": "object", + "required": [ + "resharing", + "share" + ], + "properties": { + "resharing": { + "type": "boolean" + }, + "share": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "verify_verification_key_share" + ], + "properties": { + "verify_verification_key_share": { + "type": "object", + "required": [ + "owner", + "resharing" + ], + "properties": { + "owner": { + "$ref": "#/definitions/Addr" + }, + "resharing": { + "type": "boolean" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "surpassed_threshold" + ], + "properties": { + "surpassed_threshold": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "advance_epoch_state" + ], + "properties": { + "advance_epoch_state": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + } + ], + "definitions": { + "Addr": { + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + }, + "ContractSafeBytes": { + "type": "array", + "items": { + "type": "integer", + "format": "uint8", + "minimum": 0.0 + } + } + } +} diff --git a/contracts/coconut-dkg/schema/raw/instantiate.json b/contracts/coconut-dkg/schema/raw/instantiate.json new file mode 100644 index 0000000000..af468be6c6 --- /dev/null +++ b/contracts/coconut-dkg/schema/raw/instantiate.json @@ -0,0 +1,78 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "InstantiateMsg", + "type": "object", + "required": [ + "group_addr", + "mix_denom", + "multisig_addr" + ], + "properties": { + "group_addr": { + "type": "string" + }, + "mix_denom": { + "type": "string" + }, + "multisig_addr": { + "type": "string" + }, + "time_configuration": { + "anyOf": [ + { + "$ref": "#/definitions/TimeConfiguration" + }, + { + "type": "null" + } + ] + } + }, + "additionalProperties": false, + "definitions": { + "TimeConfiguration": { + "type": "object", + "required": [ + "dealing_exchange_time_secs", + "in_progress_time_secs", + "public_key_submission_time_secs", + "verification_key_finalization_time_secs", + "verification_key_submission_time_secs", + "verification_key_validation_time_secs" + ], + "properties": { + "dealing_exchange_time_secs": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "in_progress_time_secs": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "public_key_submission_time_secs": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "verification_key_finalization_time_secs": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "verification_key_submission_time_secs": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "verification_key_validation_time_secs": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + } +} diff --git a/contracts/coconut-dkg/schema/raw/migrate.json b/contracts/coconut-dkg/schema/raw/migrate.json new file mode 100644 index 0000000000..7fbe8c5708 --- /dev/null +++ b/contracts/coconut-dkg/schema/raw/migrate.json @@ -0,0 +1,6 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "MigrateMsg", + "type": "object", + "additionalProperties": false +} diff --git a/contracts/coconut-dkg/schema/raw/query.json b/contracts/coconut-dkg/schema/raw/query.json new file mode 100644 index 0000000000..c165069598 --- /dev/null +++ b/contracts/coconut-dkg/schema/raw/query.json @@ -0,0 +1,198 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "QueryMsg", + "oneOf": [ + { + "type": "object", + "required": [ + "get_current_epoch_state" + ], + "properties": { + "get_current_epoch_state": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "get_current_epoch_threshold" + ], + "properties": { + "get_current_epoch_threshold": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "get_initial_dealers" + ], + "properties": { + "get_initial_dealers": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "get_dealer_details" + ], + "properties": { + "get_dealer_details": { + "type": "object", + "required": [ + "dealer_address" + ], + "properties": { + "dealer_address": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "get_current_dealers" + ], + "properties": { + "get_current_dealers": { + "type": "object", + "properties": { + "limit": { + "type": [ + "integer", + "null" + ], + "format": "uint32", + "minimum": 0.0 + }, + "start_after": { + "type": [ + "string", + "null" + ] + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "get_past_dealers" + ], + "properties": { + "get_past_dealers": { + "type": "object", + "properties": { + "limit": { + "type": [ + "integer", + "null" + ], + "format": "uint32", + "minimum": 0.0 + }, + "start_after": { + "type": [ + "string", + "null" + ] + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "get_dealing" + ], + "properties": { + "get_dealing": { + "type": "object", + "required": [ + "idx" + ], + "properties": { + "idx": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "limit": { + "type": [ + "integer", + "null" + ], + "format": "uint32", + "minimum": 0.0 + }, + "start_after": { + "type": [ + "string", + "null" + ] + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "get_verification_keys" + ], + "properties": { + "get_verification_keys": { + "type": "object", + "required": [ + "epoch_id" + ], + "properties": { + "epoch_id": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "limit": { + "type": [ + "integer", + "null" + ], + "format": "uint32", + "minimum": 0.0 + }, + "start_after": { + "type": [ + "string", + "null" + ] + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + ] +} diff --git a/contracts/coconut-dkg/schema/raw/response_to_get_current_dealers.json b/contracts/coconut-dkg/schema/raw/response_to_get_current_dealers.json new file mode 100644 index 0000000000..e08ceedf09 --- /dev/null +++ b/contracts/coconut-dkg/schema/raw/response_to_get_current_dealers.json @@ -0,0 +1,66 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "PagedDealerResponse", + "type": "object", + "required": [ + "dealers", + "per_page" + ], + "properties": { + "dealers": { + "type": "array", + "items": { + "$ref": "#/definitions/DealerDetails" + } + }, + "per_page": { + "type": "integer", + "format": "uint", + "minimum": 0.0 + }, + "start_next_after": { + "description": "Field indicating paging information for the following queries if the caller wishes to get further entries.", + "anyOf": [ + { + "$ref": "#/definitions/Addr" + }, + { + "type": "null" + } + ] + } + }, + "additionalProperties": false, + "definitions": { + "Addr": { + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + }, + "DealerDetails": { + "type": "object", + "required": [ + "address", + "announce_address", + "assigned_index", + "bte_public_key_with_proof" + ], + "properties": { + "address": { + "$ref": "#/definitions/Addr" + }, + "announce_address": { + "type": "string" + }, + "assigned_index": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "bte_public_key_with_proof": { + "type": "string" + } + }, + "additionalProperties": false + } + } +} diff --git a/contracts/coconut-dkg/schema/raw/response_to_get_current_epoch_state.json b/contracts/coconut-dkg/schema/raw/response_to_get_current_epoch_state.json new file mode 100644 index 0000000000..8af981b9db --- /dev/null +++ b/contracts/coconut-dkg/schema/raw/response_to_get_current_epoch_state.json @@ -0,0 +1,201 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Epoch", + "type": "object", + "required": [ + "epoch_id", + "finish_timestamp", + "state", + "time_configuration" + ], + "properties": { + "epoch_id": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "finish_timestamp": { + "$ref": "#/definitions/Timestamp" + }, + "state": { + "$ref": "#/definitions/EpochState" + }, + "time_configuration": { + "$ref": "#/definitions/TimeConfiguration" + } + }, + "additionalProperties": false, + "definitions": { + "EpochState": { + "oneOf": [ + { + "type": "string", + "enum": [ + "in_progress" + ] + }, + { + "type": "object", + "required": [ + "public_key_submission" + ], + "properties": { + "public_key_submission": { + "type": "object", + "required": [ + "resharing" + ], + "properties": { + "resharing": { + "type": "boolean" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "dealing_exchange" + ], + "properties": { + "dealing_exchange": { + "type": "object", + "required": [ + "resharing" + ], + "properties": { + "resharing": { + "type": "boolean" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "verification_key_submission" + ], + "properties": { + "verification_key_submission": { + "type": "object", + "required": [ + "resharing" + ], + "properties": { + "resharing": { + "type": "boolean" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "verification_key_validation" + ], + "properties": { + "verification_key_validation": { + "type": "object", + "required": [ + "resharing" + ], + "properties": { + "resharing": { + "type": "boolean" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "verification_key_finalization" + ], + "properties": { + "verification_key_finalization": { + "type": "object", + "required": [ + "resharing" + ], + "properties": { + "resharing": { + "type": "boolean" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + ] + }, + "TimeConfiguration": { + "type": "object", + "required": [ + "dealing_exchange_time_secs", + "in_progress_time_secs", + "public_key_submission_time_secs", + "verification_key_finalization_time_secs", + "verification_key_submission_time_secs", + "verification_key_validation_time_secs" + ], + "properties": { + "dealing_exchange_time_secs": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "in_progress_time_secs": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "public_key_submission_time_secs": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "verification_key_finalization_time_secs": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "verification_key_submission_time_secs": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "verification_key_validation_time_secs": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + }, + "Timestamp": { + "description": "A point in time in nanosecond precision.\n\nThis type can represent times from 1970-01-01T00:00:00Z to 2554-07-21T23:34:33Z.\n\n## Examples\n\n``` # use cosmwasm_std::Timestamp; let ts = Timestamp::from_nanos(1_000_000_202); assert_eq!(ts.nanos(), 1_000_000_202); assert_eq!(ts.seconds(), 1); assert_eq!(ts.subsec_nanos(), 202);\n\nlet ts = ts.plus_seconds(2); assert_eq!(ts.nanos(), 3_000_000_202); assert_eq!(ts.seconds(), 3); assert_eq!(ts.subsec_nanos(), 202); ```", + "allOf": [ + { + "$ref": "#/definitions/Uint64" + } + ] + }, + "Uint64": { + "description": "A thin wrapper around u64 that is using strings for JSON encoding/decoding, such that the full u64 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u64` to get the value out:\n\n``` # use cosmwasm_std::Uint64; let a = Uint64::from(42u64); assert_eq!(a.u64(), 42);\n\nlet b = Uint64::from(70u32); assert_eq!(b.u64(), 70); ```", + "type": "string" + } + } +} diff --git a/contracts/coconut-dkg/schema/raw/response_to_get_current_epoch_threshold.json b/contracts/coconut-dkg/schema/raw/response_to_get_current_epoch_threshold.json new file mode 100644 index 0000000000..7b729a7b96 --- /dev/null +++ b/contracts/coconut-dkg/schema/raw/response_to_get_current_epoch_threshold.json @@ -0,0 +1,7 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "uint64", + "type": "integer", + "format": "uint64", + "minimum": 0.0 +} diff --git a/contracts/coconut-dkg/schema/raw/response_to_get_dealer_details.json b/contracts/coconut-dkg/schema/raw/response_to_get_dealer_details.json new file mode 100644 index 0000000000..474a247a99 --- /dev/null +++ b/contracts/coconut-dkg/schema/raw/response_to_get_dealer_details.json @@ -0,0 +1,64 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "DealerDetailsResponse", + "type": "object", + "required": [ + "dealer_type" + ], + "properties": { + "dealer_type": { + "$ref": "#/definitions/DealerType" + }, + "details": { + "anyOf": [ + { + "$ref": "#/definitions/DealerDetails" + }, + { + "type": "null" + } + ] + } + }, + "additionalProperties": false, + "definitions": { + "Addr": { + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + }, + "DealerDetails": { + "type": "object", + "required": [ + "address", + "announce_address", + "assigned_index", + "bte_public_key_with_proof" + ], + "properties": { + "address": { + "$ref": "#/definitions/Addr" + }, + "announce_address": { + "type": "string" + }, + "assigned_index": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "bte_public_key_with_proof": { + "type": "string" + } + }, + "additionalProperties": false + }, + "DealerType": { + "type": "string", + "enum": [ + "current", + "past", + "unknown" + ] + } + } +} diff --git a/contracts/coconut-dkg/schema/raw/response_to_get_dealing.json b/contracts/coconut-dkg/schema/raw/response_to_get_dealing.json new file mode 100644 index 0000000000..8fc350b74c --- /dev/null +++ b/contracts/coconut-dkg/schema/raw/response_to_get_dealing.json @@ -0,0 +1,64 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "PagedDealingsResponse", + "type": "object", + "required": [ + "dealings", + "per_page" + ], + "properties": { + "dealings": { + "type": "array", + "items": { + "$ref": "#/definitions/ContractDealing" + } + }, + "per_page": { + "type": "integer", + "format": "uint", + "minimum": 0.0 + }, + "start_next_after": { + "description": "Field indicating paging information for the following queries if the caller wishes to get further entries.", + "anyOf": [ + { + "$ref": "#/definitions/Addr" + }, + { + "type": "null" + } + ] + } + }, + "additionalProperties": false, + "definitions": { + "Addr": { + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + }, + "ContractDealing": { + "type": "object", + "required": [ + "dealer", + "dealing" + ], + "properties": { + "dealer": { + "$ref": "#/definitions/Addr" + }, + "dealing": { + "$ref": "#/definitions/ContractSafeBytes" + } + }, + "additionalProperties": false + }, + "ContractSafeBytes": { + "type": "array", + "items": { + "type": "integer", + "format": "uint8", + "minimum": 0.0 + } + } + } +} diff --git a/contracts/coconut-dkg/schema/raw/response_to_get_initial_dealers.json b/contracts/coconut-dkg/schema/raw/response_to_get_initial_dealers.json new file mode 100644 index 0000000000..cd8c3df351 --- /dev/null +++ b/contracts/coconut-dkg/schema/raw/response_to_get_initial_dealers.json @@ -0,0 +1,39 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Nullable_InitialReplacementData", + "anyOf": [ + { + "$ref": "#/definitions/InitialReplacementData" + }, + { + "type": "null" + } + ], + "definitions": { + "Addr": { + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + }, + "InitialReplacementData": { + "type": "object", + "required": [ + "initial_dealers", + "initial_height" + ], + "properties": { + "initial_dealers": { + "type": "array", + "items": { + "$ref": "#/definitions/Addr" + } + }, + "initial_height": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + } +} diff --git a/contracts/coconut-dkg/schema/raw/response_to_get_past_dealers.json b/contracts/coconut-dkg/schema/raw/response_to_get_past_dealers.json new file mode 100644 index 0000000000..e08ceedf09 --- /dev/null +++ b/contracts/coconut-dkg/schema/raw/response_to_get_past_dealers.json @@ -0,0 +1,66 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "PagedDealerResponse", + "type": "object", + "required": [ + "dealers", + "per_page" + ], + "properties": { + "dealers": { + "type": "array", + "items": { + "$ref": "#/definitions/DealerDetails" + } + }, + "per_page": { + "type": "integer", + "format": "uint", + "minimum": 0.0 + }, + "start_next_after": { + "description": "Field indicating paging information for the following queries if the caller wishes to get further entries.", + "anyOf": [ + { + "$ref": "#/definitions/Addr" + }, + { + "type": "null" + } + ] + } + }, + "additionalProperties": false, + "definitions": { + "Addr": { + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + }, + "DealerDetails": { + "type": "object", + "required": [ + "address", + "announce_address", + "assigned_index", + "bte_public_key_with_proof" + ], + "properties": { + "address": { + "$ref": "#/definitions/Addr" + }, + "announce_address": { + "type": "string" + }, + "assigned_index": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "bte_public_key_with_proof": { + "type": "string" + } + }, + "additionalProperties": false + } + } +} diff --git a/contracts/coconut-dkg/schema/raw/response_to_get_verification_keys.json b/contracts/coconut-dkg/schema/raw/response_to_get_verification_keys.json new file mode 100644 index 0000000000..d0fc6e1deb --- /dev/null +++ b/contracts/coconut-dkg/schema/raw/response_to_get_verification_keys.json @@ -0,0 +1,76 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "PagedVKSharesResponse", + "type": "object", + "required": [ + "per_page", + "shares" + ], + "properties": { + "per_page": { + "type": "integer", + "format": "uint", + "minimum": 0.0 + }, + "shares": { + "type": "array", + "items": { + "$ref": "#/definitions/ContractVKShare" + } + }, + "start_next_after": { + "description": "Field indicating paging information for the following queries if the caller wishes to get further entries.", + "anyOf": [ + { + "$ref": "#/definitions/Addr" + }, + { + "type": "null" + } + ] + } + }, + "additionalProperties": false, + "definitions": { + "Addr": { + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + }, + "ContractVKShare": { + "type": "object", + "required": [ + "announce_address", + "epoch_id", + "node_index", + "owner", + "share", + "verified" + ], + "properties": { + "announce_address": { + "type": "string" + }, + "epoch_id": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "node_index": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "owner": { + "$ref": "#/definitions/Addr" + }, + "share": { + "type": "string" + }, + "verified": { + "type": "boolean" + } + }, + "additionalProperties": false + } + } +} diff --git a/contracts/coconut-dkg/src/bin/schema.rs b/contracts/coconut-dkg/src/bin/schema.rs new file mode 100644 index 0000000000..7a66216cfa --- /dev/null +++ b/contracts/coconut-dkg/src/bin/schema.rs @@ -0,0 +1,14 @@ +// Copyright 2023 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use cosmwasm_schema::write_api; +use nym_coconut_dkg_common::msg::{ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg}; + +fn main() { + write_api! { + instantiate: InstantiateMsg, + query: QueryMsg, + execute: ExecuteMsg, + migrate: MigrateMsg, + } +} diff --git a/contracts/coconut-dkg/src/state/mod.rs b/contracts/coconut-dkg/src/state/mod.rs index 533bd98a0a..90abdc3711 100644 --- a/contracts/coconut-dkg/src/state/mod.rs +++ b/contracts/coconut-dkg/src/state/mod.rs @@ -5,14 +5,13 @@ use cosmwasm_std::Addr; use cw4::Cw4Contract; use cw_controllers::Admin; use cw_storage_plus::Item; -use schemars::JsonSchema; use serde::{Deserialize, Serialize}; // unique items pub const STATE: Item = Item::new("state"); pub const MULTISIG: Admin = Admin::new("multisig"); -#[derive(Serialize, Deserialize, Clone, PartialEq, JsonSchema, Debug)] +#[derive(Serialize, Deserialize, Clone, PartialEq, Debug)] pub struct State { pub mix_denom: String, pub multisig_addr: Addr, diff --git a/contracts/mixnet-vesting-integration-tests/src/decrease_mixnode_pledge.rs b/contracts/mixnet-vesting-integration-tests/src/decrease_mixnode_pledge.rs index c2ec5bd71b..a9930e23f7 100644 --- a/contracts/mixnet-vesting-integration-tests/src/decrease_mixnode_pledge.rs +++ b/contracts/mixnet-vesting-integration-tests/src/decrease_mixnode_pledge.rs @@ -9,8 +9,7 @@ use nym_contracts_common::Percent; use nym_mixnet_contract_common::error::MixnetContractError; use nym_mixnet_contract_common::{ContractStateParams, MixNodeCostParams}; use nym_mixnet_contract_common::{MixOwnershipResponse, QueryMsg as MixnetQueryMsg}; -use nym_vesting_contract_common::ExecuteMsg as VestingExecuteMsg; -use vesting_contract::errors::ContractError as VestingContractError; +use nym_vesting_contract_common::{ExecuteMsg as VestingExecuteMsg, VestingContractError}; #[test] fn decrease_mixnode_pledge_from_vesting_account_with_minimum_pledge() { diff --git a/contracts/mixnet/.cargo/config b/contracts/mixnet/.cargo/config index 336b618a17..2fb2c1afdb 100644 --- a/contracts/mixnet/.cargo/config +++ b/contracts/mixnet/.cargo/config @@ -1,4 +1,4 @@ [alias] -wasm = "build --release --target wasm32-unknown-unknown" +wasm = "build --release --lib --target wasm32-unknown-unknown" unit-test = "test --lib" -schema = "run --example schema" +schema = "run --bin schema --features=schema-gen" \ No newline at end of file diff --git a/contracts/mixnet/Cargo.toml b/contracts/mixnet/Cargo.toml index acdbfd4617..999ef93c8b 100644 --- a/contracts/mixnet/Cargo.toml +++ b/contracts/mixnet/Cargo.toml @@ -17,6 +17,10 @@ exclude = [ # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html +[[bin]] +name = "schema" +required-features = ["schema-gen"] + [lib] name = "mixnet_contract" crate-type = ["cdylib", "rlib"] @@ -26,6 +30,7 @@ mixnet-contract-common = { path = "../../common/cosmwasm-smart-contracts/mixnet- vesting-contract-common = { path = "../../common/cosmwasm-smart-contracts/vesting-contract", package = "nym-vesting-contract-common", version = "0.7.0" } nym-contracts-common = { path = "../../common/cosmwasm-smart-contracts/contracts-common", version = "0.5.0" } +cosmwasm-schema = { workspace = true, optional = true } cosmwasm-std = { workspace = true } cosmwasm-storage = { workspace = true } cosmwasm-derive = { workspace = true } @@ -33,14 +38,12 @@ cw2 = { workspace = true } cw-storage-plus = { workspace = true } bs58 = "0.4.0" -schemars = "0.8" serde = { version = "1.0.103", default-features = false, features = ["derive"] } thiserror = { version = "1.0.23" } time = { version = "0.3", features = ["macros"] } semver = { version = "1.0.16", default-features = false } [dev-dependencies] -cosmwasm-schema = { workspace = true } rand_chacha = "0.2" nym-crypto = { path = "../../common/crypto", features = ["asymmetric", "rand"] } @@ -50,6 +53,9 @@ vergen = { version = "=7.4.3", default-features = false, features = ["build", "g [features] default = [] contract-testing = ["mixnet-contract-common/contract-testing"] +schema-gen = ["mixnet-contract-common/schema", "cosmwasm-schema"] [profile.release] overflow-checks = true + + diff --git a/contracts/mixnet/Makefile b/contracts/mixnet/Makefile index 7741d05913..01452be701 100644 --- a/contracts/mixnet/Makefile +++ b/contracts/mixnet/Makefile @@ -3,3 +3,6 @@ opt: wasm wasm: RUSTFLAGS='-C link-arg=-s' cargo build --release --target wasm32-unknown-unknown + +generate-schema: + cargo schema \ No newline at end of file diff --git a/contracts/mixnet/examples/schema.rs b/contracts/mixnet/examples/schema.rs deleted file mode 100644 index 46e555aff3..0000000000 --- a/contracts/mixnet/examples/schema.rs +++ /dev/null @@ -1,19 +0,0 @@ -// Copyright 2021 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 - -use cosmwasm_schema::{export_schema, remove_schemas, schema_for}; -use mixnet_contract_common::{ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg}; -use std::env::current_dir; -use std::fs::create_dir_all; - -fn main() { - let mut out_dir = current_dir().unwrap(); - out_dir.push("schema"); - create_dir_all(&out_dir).unwrap(); - remove_schemas(&out_dir).unwrap(); - - export_schema(&schema_for!(InstantiateMsg), &out_dir); - export_schema(&schema_for!(ExecuteMsg), &out_dir); - export_schema(&schema_for!(QueryMsg), &out_dir); - export_schema(&schema_for!(MigrateMsg), &out_dir); -} diff --git a/contracts/mixnet/schema/nym-mixnet-contract.json b/contracts/mixnet/schema/nym-mixnet-contract.json new file mode 100644 index 0000000000..22cf2a118d --- /dev/null +++ b/contracts/mixnet/schema/nym-mixnet-contract.json @@ -0,0 +1,8507 @@ +{ + "contract_name": "nym-mixnet-contract", + "contract_version": "1.4.0", + "idl_version": "1.0.0", + "instantiate": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "InstantiateMsg", + "type": "object", + "required": [ + "epoch_duration", + "epochs_in_interval", + "initial_rewarding_params", + "rewarding_denom", + "rewarding_validator_address", + "vesting_contract_address" + ], + "properties": { + "epoch_duration": { + "$ref": "#/definitions/Duration" + }, + "epochs_in_interval": { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "initial_rewarding_params": { + "$ref": "#/definitions/InitialRewardingParams" + }, + "rewarding_denom": { + "type": "string" + }, + "rewarding_validator_address": { + "type": "string" + }, + "vesting_contract_address": { + "type": "string" + } + }, + "additionalProperties": false, + "definitions": { + "Decimal": { + "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", + "type": "string" + }, + "Duration": { + "type": "object", + "required": [ + "nanos", + "secs" + ], + "properties": { + "nanos": { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "secs": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + } + }, + "InitialRewardingParams": { + "type": "object", + "required": [ + "active_set_size", + "active_set_work_factor", + "initial_reward_pool", + "initial_staking_supply", + "interval_pool_emission", + "rewarded_set_size", + "staking_supply_scale_factor", + "sybil_resistance" + ], + "properties": { + "active_set_size": { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "active_set_work_factor": { + "$ref": "#/definitions/Decimal" + }, + "initial_reward_pool": { + "$ref": "#/definitions/Decimal" + }, + "initial_staking_supply": { + "$ref": "#/definitions/Decimal" + }, + "interval_pool_emission": { + "$ref": "#/definitions/Percent" + }, + "rewarded_set_size": { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "staking_supply_scale_factor": { + "$ref": "#/definitions/Percent" + }, + "sybil_resistance": { + "$ref": "#/definitions/Percent" + } + }, + "additionalProperties": false + }, + "Percent": { + "description": "Percent represents a value between 0 and 100% (i.e. between 0.0 and 1.0)", + "allOf": [ + { + "$ref": "#/definitions/Decimal" + } + ] + } + } + }, + "execute": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ExecuteMsg", + "oneOf": [ + { + "type": "object", + "required": [ + "assign_node_layer" + ], + "properties": { + "assign_node_layer": { + "type": "object", + "required": [ + "layer", + "mix_id" + ], + "properties": { + "layer": { + "$ref": "#/definitions/Layer" + }, + "mix_id": { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Only owner of the node can crate the family with node as head", + "type": "object", + "required": [ + "create_family" + ], + "properties": { + "create_family": { + "type": "object", + "required": [ + "label" + ], + "properties": { + "label": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Family head needs to sign the joining node IdentityKey", + "type": "object", + "required": [ + "join_family" + ], + "properties": { + "join_family": { + "type": "object", + "required": [ + "family_head", + "join_permit" + ], + "properties": { + "family_head": { + "$ref": "#/definitions/FamilyHead" + }, + "join_permit": { + "$ref": "#/definitions/MessageSignature" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "leave_family" + ], + "properties": { + "leave_family": { + "type": "object", + "required": [ + "family_head" + ], + "properties": { + "family_head": { + "$ref": "#/definitions/FamilyHead" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "kick_family_member" + ], + "properties": { + "kick_family_member": { + "type": "object", + "required": [ + "member" + ], + "properties": { + "member": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "create_family_on_behalf" + ], + "properties": { + "create_family_on_behalf": { + "type": "object", + "required": [ + "label", + "owner_address" + ], + "properties": { + "label": { + "type": "string" + }, + "owner_address": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Family head needs to sign the joining node IdentityKey, MixNode needs to provide its signature proving that it wants to join the family", + "type": "object", + "required": [ + "join_family_on_behalf" + ], + "properties": { + "join_family_on_behalf": { + "type": "object", + "required": [ + "family_head", + "join_permit", + "member_address" + ], + "properties": { + "family_head": { + "$ref": "#/definitions/FamilyHead" + }, + "join_permit": { + "$ref": "#/definitions/MessageSignature" + }, + "member_address": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "leave_family_on_behalf" + ], + "properties": { + "leave_family_on_behalf": { + "type": "object", + "required": [ + "family_head", + "member_address" + ], + "properties": { + "family_head": { + "$ref": "#/definitions/FamilyHead" + }, + "member_address": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "kick_family_member_on_behalf" + ], + "properties": { + "kick_family_member_on_behalf": { + "type": "object", + "required": [ + "head_address", + "member" + ], + "properties": { + "head_address": { + "type": "string" + }, + "member": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "update_rewarding_validator_address" + ], + "properties": { + "update_rewarding_validator_address": { + "type": "object", + "required": [ + "address" + ], + "properties": { + "address": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "update_contract_state_params" + ], + "properties": { + "update_contract_state_params": { + "type": "object", + "required": [ + "updated_parameters" + ], + "properties": { + "updated_parameters": { + "$ref": "#/definitions/ContractStateParams" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "update_active_set_size" + ], + "properties": { + "update_active_set_size": { + "type": "object", + "required": [ + "active_set_size", + "force_immediately" + ], + "properties": { + "active_set_size": { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "force_immediately": { + "type": "boolean" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "update_rewarding_params" + ], + "properties": { + "update_rewarding_params": { + "type": "object", + "required": [ + "force_immediately", + "updated_params" + ], + "properties": { + "force_immediately": { + "type": "boolean" + }, + "updated_params": { + "$ref": "#/definitions/IntervalRewardingParamsUpdate" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "update_interval_config" + ], + "properties": { + "update_interval_config": { + "type": "object", + "required": [ + "epoch_duration_secs", + "epochs_in_interval", + "force_immediately" + ], + "properties": { + "epoch_duration_secs": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "epochs_in_interval": { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "force_immediately": { + "type": "boolean" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "begin_epoch_transition" + ], + "properties": { + "begin_epoch_transition": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "advance_current_epoch" + ], + "properties": { + "advance_current_epoch": { + "type": "object", + "required": [ + "expected_active_set_size", + "new_rewarded_set" + ], + "properties": { + "expected_active_set_size": { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "new_rewarded_set": { + "type": "array", + "items": { + "$ref": "#/definitions/LayerAssignment" + } + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "reconcile_epoch_events" + ], + "properties": { + "reconcile_epoch_events": { + "type": "object", + "properties": { + "limit": { + "type": [ + "integer", + "null" + ], + "format": "uint32", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "bond_mixnode" + ], + "properties": { + "bond_mixnode": { + "type": "object", + "required": [ + "cost_params", + "mix_node", + "owner_signature" + ], + "properties": { + "cost_params": { + "$ref": "#/definitions/MixNodeCostParams" + }, + "mix_node": { + "$ref": "#/definitions/MixNode" + }, + "owner_signature": { + "$ref": "#/definitions/MessageSignature" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "bond_mixnode_on_behalf" + ], + "properties": { + "bond_mixnode_on_behalf": { + "type": "object", + "required": [ + "cost_params", + "mix_node", + "owner", + "owner_signature" + ], + "properties": { + "cost_params": { + "$ref": "#/definitions/MixNodeCostParams" + }, + "mix_node": { + "$ref": "#/definitions/MixNode" + }, + "owner": { + "type": "string" + }, + "owner_signature": { + "$ref": "#/definitions/MessageSignature" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "pledge_more" + ], + "properties": { + "pledge_more": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "pledge_more_on_behalf" + ], + "properties": { + "pledge_more_on_behalf": { + "type": "object", + "required": [ + "owner" + ], + "properties": { + "owner": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "decrease_pledge" + ], + "properties": { + "decrease_pledge": { + "type": "object", + "required": [ + "decrease_by" + ], + "properties": { + "decrease_by": { + "$ref": "#/definitions/Coin" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "decrease_pledge_on_behalf" + ], + "properties": { + "decrease_pledge_on_behalf": { + "type": "object", + "required": [ + "decrease_by", + "owner" + ], + "properties": { + "decrease_by": { + "$ref": "#/definitions/Coin" + }, + "owner": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "unbond_mixnode" + ], + "properties": { + "unbond_mixnode": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "unbond_mixnode_on_behalf" + ], + "properties": { + "unbond_mixnode_on_behalf": { + "type": "object", + "required": [ + "owner" + ], + "properties": { + "owner": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "update_mixnode_cost_params" + ], + "properties": { + "update_mixnode_cost_params": { + "type": "object", + "required": [ + "new_costs" + ], + "properties": { + "new_costs": { + "$ref": "#/definitions/MixNodeCostParams" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "update_mixnode_cost_params_on_behalf" + ], + "properties": { + "update_mixnode_cost_params_on_behalf": { + "type": "object", + "required": [ + "new_costs", + "owner" + ], + "properties": { + "new_costs": { + "$ref": "#/definitions/MixNodeCostParams" + }, + "owner": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "update_mixnode_config" + ], + "properties": { + "update_mixnode_config": { + "type": "object", + "required": [ + "new_config" + ], + "properties": { + "new_config": { + "$ref": "#/definitions/MixNodeConfigUpdate" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "update_mixnode_config_on_behalf" + ], + "properties": { + "update_mixnode_config_on_behalf": { + "type": "object", + "required": [ + "new_config", + "owner" + ], + "properties": { + "new_config": { + "$ref": "#/definitions/MixNodeConfigUpdate" + }, + "owner": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "bond_gateway" + ], + "properties": { + "bond_gateway": { + "type": "object", + "required": [ + "gateway", + "owner_signature" + ], + "properties": { + "gateway": { + "$ref": "#/definitions/Gateway" + }, + "owner_signature": { + "$ref": "#/definitions/MessageSignature" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "bond_gateway_on_behalf" + ], + "properties": { + "bond_gateway_on_behalf": { + "type": "object", + "required": [ + "gateway", + "owner", + "owner_signature" + ], + "properties": { + "gateway": { + "$ref": "#/definitions/Gateway" + }, + "owner": { + "type": "string" + }, + "owner_signature": { + "$ref": "#/definitions/MessageSignature" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "unbond_gateway" + ], + "properties": { + "unbond_gateway": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "unbond_gateway_on_behalf" + ], + "properties": { + "unbond_gateway_on_behalf": { + "type": "object", + "required": [ + "owner" + ], + "properties": { + "owner": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "update_gateway_config" + ], + "properties": { + "update_gateway_config": { + "type": "object", + "required": [ + "new_config" + ], + "properties": { + "new_config": { + "$ref": "#/definitions/GatewayConfigUpdate" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "update_gateway_config_on_behalf" + ], + "properties": { + "update_gateway_config_on_behalf": { + "type": "object", + "required": [ + "new_config", + "owner" + ], + "properties": { + "new_config": { + "$ref": "#/definitions/GatewayConfigUpdate" + }, + "owner": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "delegate_to_mixnode" + ], + "properties": { + "delegate_to_mixnode": { + "type": "object", + "required": [ + "mix_id" + ], + "properties": { + "mix_id": { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "delegate_to_mixnode_on_behalf" + ], + "properties": { + "delegate_to_mixnode_on_behalf": { + "type": "object", + "required": [ + "delegate", + "mix_id" + ], + "properties": { + "delegate": { + "type": "string" + }, + "mix_id": { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "undelegate_from_mixnode" + ], + "properties": { + "undelegate_from_mixnode": { + "type": "object", + "required": [ + "mix_id" + ], + "properties": { + "mix_id": { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "undelegate_from_mixnode_on_behalf" + ], + "properties": { + "undelegate_from_mixnode_on_behalf": { + "type": "object", + "required": [ + "delegate", + "mix_id" + ], + "properties": { + "delegate": { + "type": "string" + }, + "mix_id": { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "reward_mixnode" + ], + "properties": { + "reward_mixnode": { + "type": "object", + "required": [ + "mix_id", + "performance" + ], + "properties": { + "mix_id": { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "performance": { + "$ref": "#/definitions/Percent" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "withdraw_operator_reward" + ], + "properties": { + "withdraw_operator_reward": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "withdraw_operator_reward_on_behalf" + ], + "properties": { + "withdraw_operator_reward_on_behalf": { + "type": "object", + "required": [ + "owner" + ], + "properties": { + "owner": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "withdraw_delegator_reward" + ], + "properties": { + "withdraw_delegator_reward": { + "type": "object", + "required": [ + "mix_id" + ], + "properties": { + "mix_id": { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "withdraw_delegator_reward_on_behalf" + ], + "properties": { + "withdraw_delegator_reward_on_behalf": { + "type": "object", + "required": [ + "mix_id", + "owner" + ], + "properties": { + "mix_id": { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "owner": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + ], + "definitions": { + "Coin": { + "type": "object", + "required": [ + "amount", + "denom" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "denom": { + "type": "string" + } + } + }, + "ContractStateParams": { + "description": "Contract parameters that could be adjusted in a transaction by the contract admin.", + "type": "object", + "required": [ + "minimum_gateway_pledge", + "minimum_mixnode_pledge" + ], + "properties": { + "minimum_gateway_pledge": { + "description": "Minimum amount a gateway must pledge to get into the system.", + "allOf": [ + { + "$ref": "#/definitions/Coin" + } + ] + }, + "minimum_mixnode_delegation": { + "description": "Minimum amount a delegator must stake in orders for his delegation to get accepted.", + "anyOf": [ + { + "$ref": "#/definitions/Coin" + }, + { + "type": "null" + } + ] + }, + "minimum_mixnode_pledge": { + "description": "Minimum amount a mixnode must pledge to get into the system.", + "allOf": [ + { + "$ref": "#/definitions/Coin" + } + ] + } + }, + "additionalProperties": false + }, + "Decimal": { + "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", + "type": "string" + }, + "FamilyHead": { + "description": "Head of particular family as identified by its identity key (i.e. public component of its ed25519 keypair stringified into base58).", + "type": "string" + }, + "Gateway": { + "description": "Information provided by the node operator during bonding that are used to allow other entities to use the services of this node.", + "type": "object", + "required": [ + "clients_port", + "host", + "identity_key", + "location", + "mix_port", + "sphinx_key", + "version" + ], + "properties": { + "clients_port": { + "description": "Port used by this gateway for listening for client requests.", + "type": "integer", + "format": "uint16", + "minimum": 0.0 + }, + "host": { + "description": "Network address of this gateway, for example 1.1.1.1 or foo.gateway.com", + "type": "string" + }, + "identity_key": { + "description": "Base58 encoded ed25519 EdDSA public key of the gateway used to derive shared keys with clients", + "type": "string" + }, + "location": { + "description": "The physical, self-reported, location of this gateway.", + "type": "string" + }, + "mix_port": { + "description": "Port used by this gateway for listening for mix packets.", + "type": "integer", + "format": "uint16", + "minimum": 0.0 + }, + "sphinx_key": { + "description": "Base58-encoded x25519 public key used for sphinx key derivation.", + "type": "string" + }, + "version": { + "description": "The self-reported semver version of this gateway.", + "type": "string" + } + }, + "additionalProperties": false + }, + "GatewayConfigUpdate": { + "type": "object", + "required": [ + "clients_port", + "host", + "location", + "mix_port", + "version" + ], + "properties": { + "clients_port": { + "type": "integer", + "format": "uint16", + "minimum": 0.0 + }, + "host": { + "type": "string" + }, + "location": { + "type": "string" + }, + "mix_port": { + "type": "integer", + "format": "uint16", + "minimum": 0.0 + }, + "version": { + "type": "string" + } + }, + "additionalProperties": false + }, + "IntervalRewardingParamsUpdate": { + "description": "Specification on how the rewarding params should be updated.", + "type": "object", + "properties": { + "active_set_work_factor": { + "description": "Defines the new value of the active set work factor.", + "anyOf": [ + { + "$ref": "#/definitions/Decimal" + }, + { + "type": "null" + } + ] + }, + "interval_pool_emission": { + "description": "Defines the new value of the interval pool emission rate.", + "anyOf": [ + { + "$ref": "#/definitions/Percent" + }, + { + "type": "null" + } + ] + }, + "reward_pool": { + "description": "Defines the new value of the reward pool.", + "anyOf": [ + { + "$ref": "#/definitions/Decimal" + }, + { + "type": "null" + } + ] + }, + "rewarded_set_size": { + "description": "Defines the new size of the rewarded set.", + "type": [ + "integer", + "null" + ], + "format": "uint32", + "minimum": 0.0 + }, + "staking_supply": { + "description": "Defines the new value of the staking supply.", + "anyOf": [ + { + "$ref": "#/definitions/Decimal" + }, + { + "type": "null" + } + ] + }, + "staking_supply_scale_factor": { + "description": "Defines the new value of the staking supply scale factor.", + "anyOf": [ + { + "$ref": "#/definitions/Percent" + }, + { + "type": "null" + } + ] + }, + "sybil_resistance_percent": { + "description": "Defines the new value of the sybil resistance percent.", + "anyOf": [ + { + "$ref": "#/definitions/Percent" + }, + { + "type": "null" + } + ] + } + }, + "additionalProperties": false + }, + "Layer": { + "type": "string", + "enum": [ + "One", + "Two", + "Three" + ] + }, + "LayerAssignment": { + "description": "Specifies layer assignment for the given mixnode.", + "type": "object", + "required": [ + "layer", + "mix_id" + ], + "properties": { + "layer": { + "description": "The layer to which it's going to be assigned", + "allOf": [ + { + "$ref": "#/definitions/Layer" + } + ] + }, + "mix_id": { + "description": "The id of the mixnode.", + "type": "integer", + "format": "uint32", + "minimum": 0.0 + } + }, + "additionalProperties": false + }, + "MessageSignature": { + "type": "array", + "items": { + "type": "integer", + "format": "uint8", + "minimum": 0.0 + } + }, + "MixNode": { + "description": "Information provided by the node operator during bonding that are used to allow other entities to use the services of this node.", + "type": "object", + "required": [ + "host", + "http_api_port", + "identity_key", + "mix_port", + "sphinx_key", + "verloc_port", + "version" + ], + "properties": { + "host": { + "description": "Network address of this mixnode, for example 1.1.1.1 or foo.mixnode.com", + "type": "string" + }, + "http_api_port": { + "description": "Port used by this mixnode for its http(s) API", + "type": "integer", + "format": "uint16", + "minimum": 0.0 + }, + "identity_key": { + "description": "Base58-encoded ed25519 EdDSA public key.", + "type": "string" + }, + "mix_port": { + "description": "Port used by this mixnode for listening for mix packets.", + "type": "integer", + "format": "uint16", + "minimum": 0.0 + }, + "sphinx_key": { + "description": "Base58-encoded x25519 public key used for sphinx key derivation.", + "type": "string" + }, + "verloc_port": { + "description": "Port used by this mixnode for listening for verloc requests.", + "type": "integer", + "format": "uint16", + "minimum": 0.0 + }, + "version": { + "description": "The self-reported semver version of this mixnode.", + "type": "string" + } + }, + "additionalProperties": false + }, + "MixNodeConfigUpdate": { + "type": "object", + "required": [ + "host", + "http_api_port", + "mix_port", + "verloc_port", + "version" + ], + "properties": { + "host": { + "type": "string" + }, + "http_api_port": { + "type": "integer", + "format": "uint16", + "minimum": 0.0 + }, + "mix_port": { + "type": "integer", + "format": "uint16", + "minimum": 0.0 + }, + "verloc_port": { + "type": "integer", + "format": "uint16", + "minimum": 0.0 + }, + "version": { + "type": "string" + } + }, + "additionalProperties": false + }, + "MixNodeCostParams": { + "description": "The cost parameters, or the cost function, defined for the particular mixnode that influences how the rewards should be split between the node operator and its delegators.", + "type": "object", + "required": [ + "interval_operating_cost", + "profit_margin_percent" + ], + "properties": { + "interval_operating_cost": { + "description": "Operating cost of the associated mixnode per the entire interval.", + "allOf": [ + { + "$ref": "#/definitions/Coin" + } + ] + }, + "profit_margin_percent": { + "description": "The profit margin of the associated mixnode, i.e. the desired percent of the reward to be distributed to the operator.", + "allOf": [ + { + "$ref": "#/definitions/Percent" + } + ] + } + }, + "additionalProperties": false + }, + "Percent": { + "description": "Percent represents a value between 0 and 100% (i.e. between 0.0 and 1.0)", + "allOf": [ + { + "$ref": "#/definitions/Decimal" + } + ] + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } + }, + "query": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "QueryMsg", + "oneOf": [ + { + "description": "Gets the list of families registered in this contract.", + "type": "object", + "required": [ + "get_all_families_paged" + ], + "properties": { + "get_all_families_paged": { + "type": "object", + "properties": { + "limit": { + "description": "Controls the maximum number of entries returned by the query. Note that too large values will be overwritten by a saner default.", + "type": [ + "integer", + "null" + ], + "format": "uint32", + "minimum": 0.0 + }, + "start_after": { + "description": "Pagination control for the values returned by the query. Note that the provided value itself will **not** be used for the response.", + "type": [ + "string", + "null" + ] + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Gets the list of all family members registered in this contract.", + "type": "object", + "required": [ + "get_all_members_paged" + ], + "properties": { + "get_all_members_paged": { + "type": "object", + "properties": { + "limit": { + "description": "Controls the maximum number of entries returned by the query. Note that too large values will be overwritten by a saner default.", + "type": [ + "integer", + "null" + ], + "format": "uint32", + "minimum": 0.0 + }, + "start_after": { + "description": "Pagination control for the values returned by the query. Note that the provided value itself will **not** be used for the response.", + "type": [ + "string", + "null" + ] + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Attempts to lookup family information given the family head.", + "type": "object", + "required": [ + "get_family_by_head" + ], + "properties": { + "get_family_by_head": { + "type": "object", + "required": [ + "head" + ], + "properties": { + "head": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Attempts to lookup family information given the family label.", + "type": "object", + "required": [ + "get_family_by_label" + ], + "properties": { + "get_family_by_label": { + "type": "object", + "required": [ + "label" + ], + "properties": { + "label": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Attempts to retrieve family members given the family head.", + "type": "object", + "required": [ + "get_family_members_by_head" + ], + "properties": { + "get_family_members_by_head": { + "type": "object", + "required": [ + "head" + ], + "properties": { + "head": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Attempts to retrieve family members given the family label.", + "type": "object", + "required": [ + "get_family_members_by_label" + ], + "properties": { + "get_family_members_by_label": { + "type": "object", + "required": [ + "label" + ], + "properties": { + "label": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Gets build information of this contract, such as the commit hash used for the build or rustc version.", + "type": "object", + "required": [ + "get_contract_version" + ], + "properties": { + "get_contract_version": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Gets the stored contract version information that's required by the CW2 spec interface for migrations.", + "type": "object", + "required": [ + "get_cw2_contract_version" + ], + "properties": { + "get_cw2_contract_version": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Gets the address of the validator that's allowed to send rewarding transactions and transition the epoch.", + "type": "object", + "required": [ + "get_rewarding_validator_address" + ], + "properties": { + "get_rewarding_validator_address": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Gets the contract parameters that could be adjusted in a transaction by the contract admin.", + "type": "object", + "required": [ + "get_state_params" + ], + "properties": { + "get_state_params": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Gets the current state of the contract.", + "type": "object", + "required": [ + "get_state" + ], + "properties": { + "get_state": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Gets the current parameters used for reward calculation.", + "type": "object", + "required": [ + "get_rewarding_params" + ], + "properties": { + "get_rewarding_params": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Gets the status of the current rewarding epoch.", + "type": "object", + "required": [ + "get_epoch_status" + ], + "properties": { + "get_epoch_status": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Get the details of the current rewarding interval.", + "type": "object", + "required": [ + "get_current_interval_details" + ], + "properties": { + "get_current_interval_details": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Gets the current list of mixnodes in the rewarded set.", + "type": "object", + "required": [ + "get_rewarded_set" + ], + "properties": { + "get_rewarded_set": { + "type": "object", + "properties": { + "limit": { + "description": "Controls the maximum number of entries returned by the query. Note that too large values will be overwritten by a saner default.", + "type": [ + "integer", + "null" + ], + "format": "uint32", + "minimum": 0.0 + }, + "start_after": { + "description": "Pagination control for the values returned by the query. Note that the provided value itself will **not** be used for the response.", + "type": [ + "integer", + "null" + ], + "format": "uint32", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Gets the basic list of all currently bonded mixnodes.", + "type": "object", + "required": [ + "get_mix_node_bonds" + ], + "properties": { + "get_mix_node_bonds": { + "type": "object", + "properties": { + "limit": { + "description": "Controls the maximum number of entries returned by the query. Note that too large values will be overwritten by a saner default.", + "type": [ + "integer", + "null" + ], + "format": "uint32", + "minimum": 0.0 + }, + "start_after": { + "description": "Pagination control for the values returned by the query. Note that the provided value itself will **not** be used for the response.", + "type": [ + "integer", + "null" + ], + "format": "uint32", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Gets the detailed list of all currently bonded mixnodes.", + "type": "object", + "required": [ + "get_mix_nodes_detailed" + ], + "properties": { + "get_mix_nodes_detailed": { + "type": "object", + "properties": { + "limit": { + "description": "Controls the maximum number of entries returned by the query. Note that too large values will be overwritten by a saner default.", + "type": [ + "integer", + "null" + ], + "format": "uint32", + "minimum": 0.0 + }, + "start_after": { + "description": "Pagination control for the values returned by the query. Note that the provided value itself will **not** be used for the response.", + "type": [ + "integer", + "null" + ], + "format": "uint32", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Gets the basic list of all unbonded mixnodes.", + "type": "object", + "required": [ + "get_unbonded_mix_nodes" + ], + "properties": { + "get_unbonded_mix_nodes": { + "type": "object", + "properties": { + "limit": { + "description": "Controls the maximum number of entries returned by the query. Note that too large values will be overwritten by a saner default.", + "type": [ + "integer", + "null" + ], + "format": "uint32", + "minimum": 0.0 + }, + "start_after": { + "description": "Pagination control for the values returned by the query. Note that the provided value itself will **not** be used for the response.", + "type": [ + "integer", + "null" + ], + "format": "uint32", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Gets the basic list of all unbonded mixnodes that belonged to a particular owner.", + "type": "object", + "required": [ + "get_unbonded_mix_nodes_by_owner" + ], + "properties": { + "get_unbonded_mix_nodes_by_owner": { + "type": "object", + "required": [ + "owner" + ], + "properties": { + "limit": { + "description": "Controls the maximum number of entries returned by the query. Note that too large values will be overwritten by a saner default.", + "type": [ + "integer", + "null" + ], + "format": "uint32", + "minimum": 0.0 + }, + "owner": { + "description": "The address of the owner of the the mixnodes used for the query.", + "type": "string" + }, + "start_after": { + "description": "Pagination control for the values returned by the query. Note that the provided value itself will **not** be used for the response.", + "type": [ + "integer", + "null" + ], + "format": "uint32", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Gets the basic list of all unbonded mixnodes that used the particular identity key.", + "type": "object", + "required": [ + "get_unbonded_mix_nodes_by_identity_key" + ], + "properties": { + "get_unbonded_mix_nodes_by_identity_key": { + "type": "object", + "required": [ + "identity_key" + ], + "properties": { + "identity_key": { + "description": "The identity key (base58-encoded ed25519 public key) of the mixnode used for the query.", + "type": "string" + }, + "limit": { + "description": "Controls the maximum number of entries returned by the query. Note that too large values will be overwritten by a saner default.", + "type": [ + "integer", + "null" + ], + "format": "uint32", + "minimum": 0.0 + }, + "start_after": { + "description": "Pagination control for the values returned by the query. Note that the provided value itself will **not** be used for the response.", + "type": [ + "integer", + "null" + ], + "format": "uint32", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Gets the detailed mixnode information belonging to the particular owner.", + "type": "object", + "required": [ + "get_owned_mixnode" + ], + "properties": { + "get_owned_mixnode": { + "type": "object", + "required": [ + "address" + ], + "properties": { + "address": { + "description": "Address of the mixnode owner to use for the query.", + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Gets the detailed mixnode information of a node with the provided id.", + "type": "object", + "required": [ + "get_mixnode_details" + ], + "properties": { + "get_mixnode_details": { + "type": "object", + "required": [ + "mix_id" + ], + "properties": { + "mix_id": { + "description": "Id of the node to query.", + "type": "integer", + "format": "uint32", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Gets the rewarding information of a mixnode with the provided id.", + "type": "object", + "required": [ + "get_mixnode_rewarding_details" + ], + "properties": { + "get_mixnode_rewarding_details": { + "type": "object", + "required": [ + "mix_id" + ], + "properties": { + "mix_id": { + "description": "Id of the node to query.", + "type": "integer", + "format": "uint32", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Gets the stake saturation of a mixnode with the provided id.", + "type": "object", + "required": [ + "get_stake_saturation" + ], + "properties": { + "get_stake_saturation": { + "type": "object", + "required": [ + "mix_id" + ], + "properties": { + "mix_id": { + "description": "Id of the node to query.", + "type": "integer", + "format": "uint32", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Gets the basic information of an unbonded mixnode with the provided id.", + "type": "object", + "required": [ + "get_unbonded_mix_node_information" + ], + "properties": { + "get_unbonded_mix_node_information": { + "type": "object", + "required": [ + "mix_id" + ], + "properties": { + "mix_id": { + "description": "Id of the node to query.", + "type": "integer", + "format": "uint32", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Gets the detailed mixnode information of a node given its current identity key.", + "type": "object", + "required": [ + "get_bonded_mixnode_details_by_identity" + ], + "properties": { + "get_bonded_mixnode_details_by_identity": { + "type": "object", + "required": [ + "mix_identity" + ], + "properties": { + "mix_identity": { + "description": "The identity key (base58-encoded ed25519 public key) of the mixnode used for the query.", + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Gets the current layer configuration of the mix network.", + "type": "object", + "required": [ + "get_layer_distribution" + ], + "properties": { + "get_layer_distribution": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Gets the basic list of all currently bonded gateways.", + "type": "object", + "required": [ + "get_gateways" + ], + "properties": { + "get_gateways": { + "type": "object", + "properties": { + "limit": { + "description": "Controls the maximum number of entries returned by the query. Note that too large values will be overwritten by a saner default.", + "type": [ + "integer", + "null" + ], + "format": "uint32", + "minimum": 0.0 + }, + "start_after": { + "description": "Pagination control for the values returned by the query. Note that the provided value itself will **not** be used for the response.", + "type": [ + "string", + "null" + ] + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Gets the gateway details of a node given its identity key.", + "type": "object", + "required": [ + "get_gateway_bond" + ], + "properties": { + "get_gateway_bond": { + "type": "object", + "required": [ + "identity" + ], + "properties": { + "identity": { + "description": "The identity key (base58-encoded ed25519 public key) of the gateway used for the query.", + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Gets the detailed gateway information belonging to the particular owner.", + "type": "object", + "required": [ + "get_owned_gateway" + ], + "properties": { + "get_owned_gateway": { + "type": "object", + "required": [ + "address" + ], + "properties": { + "address": { + "description": "Address of the gateway owner to use for the query.", + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Gets all delegations associated with particular mixnode", + "type": "object", + "required": [ + "get_mixnode_delegations" + ], + "properties": { + "get_mixnode_delegations": { + "type": "object", + "required": [ + "mix_id" + ], + "properties": { + "limit": { + "description": "Controls the maximum number of entries returned by the query. Note that too large values will be overwritten by a saner default.", + "type": [ + "integer", + "null" + ], + "format": "uint32", + "minimum": 0.0 + }, + "mix_id": { + "description": "Id of the node to query.", + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "start_after": { + "description": "Pagination control for the values returned by the query. Note that the provided value itself will **not** be used for the response.", + "type": [ + "string", + "null" + ] + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Gets all delegations associated with particular delegator", + "type": "object", + "required": [ + "get_delegator_delegations" + ], + "properties": { + "get_delegator_delegations": { + "type": "object", + "required": [ + "delegator" + ], + "properties": { + "delegator": { + "description": "The address of the owner of the delegations.", + "type": "string" + }, + "limit": { + "description": "Controls the maximum number of entries returned by the query. Note that too large values will be overwritten by a saner default.", + "type": [ + "integer", + "null" + ], + "format": "uint32", + "minimum": 0.0 + }, + "start_after": { + "description": "Pagination control for the values returned by the query. Note that the provided value itself will **not** be used for the response.", + "type": [ + "array", + "null" + ], + "items": [ + { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + { + "type": "string" + } + ], + "maxItems": 2, + "minItems": 2 + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Gets delegation information associated with particular mixnode - delegator pair", + "type": "object", + "required": [ + "get_delegation_details" + ], + "properties": { + "get_delegation_details": { + "type": "object", + "required": [ + "delegator", + "mix_id" + ], + "properties": { + "delegator": { + "description": "The address of the owner of the delegation.", + "type": "string" + }, + "mix_id": { + "description": "Id of the node to query.", + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "proxy": { + "description": "Entity who made the delegation on behalf of the owner. If present, it's most likely the address of the vesting contract.", + "type": [ + "string", + "null" + ] + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Gets all delegations in the system", + "type": "object", + "required": [ + "get_all_delegations" + ], + "properties": { + "get_all_delegations": { + "type": "object", + "properties": { + "limit": { + "description": "Controls the maximum number of entries returned by the query. Note that too large values will be overwritten by a saner default.", + "type": [ + "integer", + "null" + ], + "format": "uint32", + "minimum": 0.0 + }, + "start_after": { + "description": "Pagination control for the values returned by the query. Note that the provided value itself will **not** be used for the response.", + "type": [ + "array", + "null" + ], + "items": [ + { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + { + "type": "string" + } + ], + "maxItems": 2, + "minItems": 2 + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Gets the reward amount accrued by the node operator that has not yet been claimed.", + "type": "object", + "required": [ + "get_pending_operator_reward" + ], + "properties": { + "get_pending_operator_reward": { + "type": "object", + "required": [ + "address" + ], + "properties": { + "address": { + "description": "Address of the operator to use for the query.", + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Gets the reward amount accrued by the particular mixnode that has not yet been claimed.", + "type": "object", + "required": [ + "get_pending_mix_node_operator_reward" + ], + "properties": { + "get_pending_mix_node_operator_reward": { + "type": "object", + "required": [ + "mix_id" + ], + "properties": { + "mix_id": { + "description": "Id of the node to query.", + "type": "integer", + "format": "uint32", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Gets the reward amount accrued by the particular delegator that has not yet been claimed.", + "type": "object", + "required": [ + "get_pending_delegator_reward" + ], + "properties": { + "get_pending_delegator_reward": { + "type": "object", + "required": [ + "address", + "mix_id" + ], + "properties": { + "address": { + "description": "Address of the delegator to use for the query.", + "type": "string" + }, + "mix_id": { + "description": "Id of the node to query.", + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "proxy": { + "description": "Entity who made the delegation on behalf of the owner. If present, it's most likely the address of the vesting contract.", + "type": [ + "string", + "null" + ] + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Given the provided node performance, attempt to estimate the operator reward for the current epoch.", + "type": "object", + "required": [ + "get_estimated_current_epoch_operator_reward" + ], + "properties": { + "get_estimated_current_epoch_operator_reward": { + "type": "object", + "required": [ + "estimated_performance", + "mix_id" + ], + "properties": { + "estimated_performance": { + "description": "The estimated performance for the current epoch of the given node.", + "allOf": [ + { + "$ref": "#/definitions/Percent" + } + ] + }, + "mix_id": { + "description": "Id of the node to query.", + "type": "integer", + "format": "uint32", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Given the provided node performance, attempt to estimate the delegator reward for the current epoch.", + "type": "object", + "required": [ + "get_estimated_current_epoch_delegator_reward" + ], + "properties": { + "get_estimated_current_epoch_delegator_reward": { + "type": "object", + "required": [ + "address", + "estimated_performance", + "mix_id" + ], + "properties": { + "address": { + "description": "Address of the delegator to use for the query.", + "type": "string" + }, + "estimated_performance": { + "description": "The estimated performance for the current epoch of the given node.", + "allOf": [ + { + "$ref": "#/definitions/Percent" + } + ] + }, + "mix_id": { + "description": "Id of the node to query.", + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "proxy": { + "description": "Entity who made the delegation on behalf of the owner. If present, it's most likely the address of the vesting contract.", + "type": [ + "string", + "null" + ] + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Gets the list of all currently pending epoch events that will be resolved once the current epoch finishes.", + "type": "object", + "required": [ + "get_pending_epoch_events" + ], + "properties": { + "get_pending_epoch_events": { + "type": "object", + "properties": { + "limit": { + "description": "Controls the maximum number of entries returned by the query. Note that too large values will be overwritten by a saner default.", + "type": [ + "integer", + "null" + ], + "format": "uint32", + "minimum": 0.0 + }, + "start_after": { + "description": "Pagination control for the values returned by the query. Note that the provided value itself will **not** be used for the response.", + "type": [ + "integer", + "null" + ], + "format": "uint32", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Gets the list of all currently pending interval events that will be resolved once the current interval finishes.", + "type": "object", + "required": [ + "get_pending_interval_events" + ], + "properties": { + "get_pending_interval_events": { + "type": "object", + "properties": { + "limit": { + "description": "Controls the maximum number of entries returned by the query. Note that too large values will be overwritten by a saner default.", + "type": [ + "integer", + "null" + ], + "format": "uint32", + "minimum": 0.0 + }, + "start_after": { + "description": "Pagination control for the values returned by the query. Note that the provided value itself will **not** be used for the response.", + "type": [ + "integer", + "null" + ], + "format": "uint32", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Gets detailed information about a pending epoch event given its id.", + "type": "object", + "required": [ + "get_pending_epoch_event" + ], + "properties": { + "get_pending_epoch_event": { + "type": "object", + "required": [ + "event_id" + ], + "properties": { + "event_id": { + "description": "The unique id associated with the event.", + "type": "integer", + "format": "uint32", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Gets detailed information about a pending interval event given its id.", + "type": "object", + "required": [ + "get_pending_interval_event" + ], + "properties": { + "get_pending_interval_event": { + "type": "object", + "required": [ + "event_id" + ], + "properties": { + "event_id": { + "description": "The unique id associated with the event.", + "type": "integer", + "format": "uint32", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Gets the information about the number of currently pending epoch and interval events.", + "type": "object", + "required": [ + "get_number_of_pending_events" + ], + "properties": { + "get_number_of_pending_events": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Gets the signing nonce associated with the particular cosmos address.", + "type": "object", + "required": [ + "get_signing_nonce" + ], + "properties": { + "get_signing_nonce": { + "type": "object", + "required": [ + "address" + ], + "properties": { + "address": { + "description": "Cosmos address used for the query of the signing nonce.", + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + ], + "definitions": { + "Decimal": { + "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", + "type": "string" + }, + "Percent": { + "description": "Percent represents a value between 0 and 100% (i.e. between 0.0 and 1.0)", + "allOf": [ + { + "$ref": "#/definitions/Decimal" + } + ] + } + } + }, + "migrate": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "MigrateMsg", + "type": "object", + "properties": { + "vesting_contract_address": { + "type": [ + "string", + "null" + ] + } + }, + "additionalProperties": false + }, + "sudo": null, + "responses": { + "get_all_delegations": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "PagedAllDelegationsResponse", + "description": "Response containing paged list of all delegations currently active.", + "type": "object", + "required": [ + "delegations" + ], + "properties": { + "delegations": { + "description": "Each individual delegation made.", + "type": "array", + "items": { + "$ref": "#/definitions/Delegation" + } + }, + "start_next_after": { + "description": "Field indicating paging information for the following queries if the caller wishes to get further entries.", + "type": [ + "array", + "null" + ], + "items": [ + { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + { + "type": "string" + } + ], + "maxItems": 2, + "minItems": 2 + } + }, + "additionalProperties": false, + "definitions": { + "Addr": { + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + }, + "Coin": { + "type": "object", + "required": [ + "amount", + "denom" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "denom": { + "type": "string" + } + } + }, + "Decimal": { + "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", + "type": "string" + }, + "Delegation": { + "description": "Information about tokens being delegated towards given mixnode in order to accrue rewards with their work.", + "type": "object", + "required": [ + "amount", + "cumulative_reward_ratio", + "height", + "mix_id", + "owner" + ], + "properties": { + "amount": { + "description": "Original delegation amount. Note that it is never mutated as delegation accumulates rewards.", + "allOf": [ + { + "$ref": "#/definitions/Coin" + } + ] + }, + "cumulative_reward_ratio": { + "description": "Value of the \"unit delegation\" associated with the mixnode at the time of delegation.", + "allOf": [ + { + "$ref": "#/definitions/Decimal" + } + ] + }, + "height": { + "description": "Block height where this delegation occurred.", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "mix_id": { + "description": "Id of the MixNode that this delegation was performed against.", + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "owner": { + "description": "Address of the owner of this delegation.", + "allOf": [ + { + "$ref": "#/definitions/Addr" + } + ] + }, + "proxy": { + "description": "Proxy address used to delegate the funds on behalf of another address", + "anyOf": [ + { + "$ref": "#/definitions/Addr" + }, + { + "type": "null" + } + ] + } + }, + "additionalProperties": false + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } + }, + "get_all_families_paged": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "PagedFamiliesResponse", + "description": "Response containing paged list of all families registered in the contract.", + "type": "object", + "required": [ + "families" + ], + "properties": { + "families": { + "description": "The families registered in the contract.", + "type": "array", + "items": { + "$ref": "#/definitions/Family" + } + }, + "start_next_after": { + "description": "Field indicating paging information for the following queries if the caller wishes to get further entries.", + "type": [ + "string", + "null" + ] + } + }, + "additionalProperties": false, + "definitions": { + "Family": { + "description": "A group of mixnodes associated with particular staking entity. When defined all nodes belonging to the same family will be prioritised to be put onto the same layer.", + "type": "object", + "required": [ + "head", + "label" + ], + "properties": { + "head": { + "description": "Owner of this family.", + "allOf": [ + { + "$ref": "#/definitions/FamilyHead" + } + ] + }, + "label": { + "description": "Human readable label for this family.", + "type": "string" + }, + "proxy": { + "description": "Optional proxy (i.e. vesting contract address) used when creating the family.", + "type": [ + "string", + "null" + ] + } + }, + "additionalProperties": false + }, + "FamilyHead": { + "description": "Head of particular family as identified by its identity key (i.e. public component of its ed25519 keypair stringified into base58).", + "type": "string" + } + } + }, + "get_all_members_paged": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "PagedMembersResponse", + "description": "Response containing paged list of all family members (of ALL families) registered in the contract.", + "type": "object", + "required": [ + "members" + ], + "properties": { + "members": { + "description": "The members alongside their family heads.", + "type": "array", + "items": { + "type": "array", + "items": [ + { + "type": "string" + }, + { + "$ref": "#/definitions/FamilyHead" + } + ], + "maxItems": 2, + "minItems": 2 + } + }, + "start_next_after": { + "description": "Field indicating paging information for the following queries if the caller wishes to get further entries.", + "type": [ + "string", + "null" + ] + } + }, + "additionalProperties": false, + "definitions": { + "FamilyHead": { + "description": "Head of particular family as identified by its identity key (i.e. public component of its ed25519 keypair stringified into base58).", + "type": "string" + } + } + }, + "get_bonded_mixnode_details_by_identity": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "MixnodeDetailsByIdentityResponse", + "description": "Response containing details of a bonded mixnode with the provided identity key.", + "type": "object", + "required": [ + "identity_key" + ], + "properties": { + "identity_key": { + "description": "The identity key (base58-encoded ed25519 public key) of the mixnode.", + "type": "string" + }, + "mixnode_details": { + "description": "If there exists a bonded mixnode with the provided identity key, this field contains its detailed information.", + "anyOf": [ + { + "$ref": "#/definitions/MixNodeDetails" + }, + { + "type": "null" + } + ] + } + }, + "additionalProperties": false, + "definitions": { + "Addr": { + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + }, + "Coin": { + "type": "object", + "required": [ + "amount", + "denom" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "denom": { + "type": "string" + } + } + }, + "Decimal": { + "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", + "type": "string" + }, + "Layer": { + "type": "string", + "enum": [ + "One", + "Two", + "Three" + ] + }, + "MixNode": { + "description": "Information provided by the node operator during bonding that are used to allow other entities to use the services of this node.", + "type": "object", + "required": [ + "host", + "http_api_port", + "identity_key", + "mix_port", + "sphinx_key", + "verloc_port", + "version" + ], + "properties": { + "host": { + "description": "Network address of this mixnode, for example 1.1.1.1 or foo.mixnode.com", + "type": "string" + }, + "http_api_port": { + "description": "Port used by this mixnode for its http(s) API", + "type": "integer", + "format": "uint16", + "minimum": 0.0 + }, + "identity_key": { + "description": "Base58-encoded ed25519 EdDSA public key.", + "type": "string" + }, + "mix_port": { + "description": "Port used by this mixnode for listening for mix packets.", + "type": "integer", + "format": "uint16", + "minimum": 0.0 + }, + "sphinx_key": { + "description": "Base58-encoded x25519 public key used for sphinx key derivation.", + "type": "string" + }, + "verloc_port": { + "description": "Port used by this mixnode for listening for verloc requests.", + "type": "integer", + "format": "uint16", + "minimum": 0.0 + }, + "version": { + "description": "The self-reported semver version of this mixnode.", + "type": "string" + } + }, + "additionalProperties": false + }, + "MixNodeBond": { + "description": "Basic mixnode information provided by the node operator.", + "type": "object", + "required": [ + "bonding_height", + "is_unbonding", + "layer", + "mix_id", + "mix_node", + "original_pledge", + "owner" + ], + "properties": { + "bonding_height": { + "description": "Block height at which this mixnode has been bonded.", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "is_unbonding": { + "description": "Flag to indicate whether this node is in the process of unbonding, that will conclude upon the epoch finishing.", + "type": "boolean" + }, + "layer": { + "description": "Layer assigned to this mixnode.", + "allOf": [ + { + "$ref": "#/definitions/Layer" + } + ] + }, + "mix_id": { + "description": "Unique id assigned to the bonded mixnode.", + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "mix_node": { + "description": "Information provided by the operator for the purposes of bonding.", + "allOf": [ + { + "$ref": "#/definitions/MixNode" + } + ] + }, + "original_pledge": { + "description": "Original amount pledged by the operator of this node.", + "allOf": [ + { + "$ref": "#/definitions/Coin" + } + ] + }, + "owner": { + "description": "Address of the owner of this mixnode.", + "allOf": [ + { + "$ref": "#/definitions/Addr" + } + ] + }, + "proxy": { + "description": "Entity who bonded this mixnode on behalf of the owner. If exists, it's most likely the address of the vesting contract.", + "anyOf": [ + { + "$ref": "#/definitions/Addr" + }, + { + "type": "null" + } + ] + } + }, + "additionalProperties": false + }, + "MixNodeCostParams": { + "description": "The cost parameters, or the cost function, defined for the particular mixnode that influences how the rewards should be split between the node operator and its delegators.", + "type": "object", + "required": [ + "interval_operating_cost", + "profit_margin_percent" + ], + "properties": { + "interval_operating_cost": { + "description": "Operating cost of the associated mixnode per the entire interval.", + "allOf": [ + { + "$ref": "#/definitions/Coin" + } + ] + }, + "profit_margin_percent": { + "description": "The profit margin of the associated mixnode, i.e. the desired percent of the reward to be distributed to the operator.", + "allOf": [ + { + "$ref": "#/definitions/Percent" + } + ] + } + }, + "additionalProperties": false + }, + "MixNodeDetails": { + "description": "Full details associated with given mixnode.", + "type": "object", + "required": [ + "bond_information", + "rewarding_details" + ], + "properties": { + "bond_information": { + "description": "Basic bond information of this mixnode, such as owner address, original pledge, etc.", + "allOf": [ + { + "$ref": "#/definitions/MixNodeBond" + } + ] + }, + "pending_changes": { + "description": "Adjustments to the mixnode that are ought to happen during future epoch transitions.", + "default": { + "pledge_change": null + }, + "allOf": [ + { + "$ref": "#/definitions/PendingMixNodeChanges" + } + ] + }, + "rewarding_details": { + "description": "Details used for computation of rewarding related data.", + "allOf": [ + { + "$ref": "#/definitions/MixNodeRewarding" + } + ] + } + }, + "additionalProperties": false + }, + "MixNodeRewarding": { + "type": "object", + "required": [ + "cost_params", + "delegates", + "last_rewarded_epoch", + "operator", + "total_unit_reward", + "unique_delegations", + "unit_delegation" + ], + "properties": { + "cost_params": { + "description": "Information provided by the operator that influence the cost function.", + "allOf": [ + { + "$ref": "#/definitions/MixNodeCostParams" + } + ] + }, + "delegates": { + "description": "Total delegation and compounded reward earned by all node delegators.", + "allOf": [ + { + "$ref": "#/definitions/Decimal" + } + ] + }, + "last_rewarded_epoch": { + "description": "Marks the epoch when this node was last rewarded so that we wouldn't accidentally attempt to reward it multiple times in the same epoch.", + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "operator": { + "description": "Total pledge and compounded reward earned by the node operator.", + "allOf": [ + { + "$ref": "#/definitions/Decimal" + } + ] + }, + "total_unit_reward": { + "description": "Cumulative reward earned by the \"unit delegation\" since the block 0.", + "allOf": [ + { + "$ref": "#/definitions/Decimal" + } + ] + }, + "unique_delegations": { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "unit_delegation": { + "description": "Value of the theoretical \"unit delegation\" that has delegated to this mixnode at block 0.", + "allOf": [ + { + "$ref": "#/definitions/Decimal" + } + ] + } + }, + "additionalProperties": false + }, + "PendingMixNodeChanges": { + "type": "object", + "properties": { + "pledge_change": { + "type": [ + "integer", + "null" + ], + "format": "uint32", + "minimum": 0.0 + } + }, + "additionalProperties": false + }, + "Percent": { + "description": "Percent represents a value between 0 and 100% (i.e. between 0.0 and 1.0)", + "allOf": [ + { + "$ref": "#/definitions/Decimal" + } + ] + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } + }, + "get_c_w2_contract_version": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ContractVersion", + "type": "object", + "required": [ + "contract", + "version" + ], + "properties": { + "contract": { + "description": "contract is the crate name of the implementing contract, eg. `crate:cw20-base` we will use other prefixes for other languages, and their standard global namespacing", + "type": "string" + }, + "version": { + "description": "version is any string that this implementation knows. It may be simple counter \"1\", \"2\". or semantic version on release tags \"v0.7.0\", or some custom feature flag list. the only code that needs to understand the version parsing is code that knows how to migrate from the given contract (and is tied to it's implementation somehow)", + "type": "string" + } + }, + "additionalProperties": false + }, + "get_contract_version": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ContractBuildInformation", + "type": "object", + "required": [ + "build_timestamp", + "build_version", + "commit_branch", + "commit_sha", + "commit_timestamp", + "rustc_version" + ], + "properties": { + "build_timestamp": { + "description": "Provides the build timestamp, for example `2021-02-23T20:14:46.558472672+00:00`.", + "type": "string" + }, + "build_version": { + "description": "Provides the build version, for example `0.1.0-9-g46f83e1`.", + "type": "string" + }, + "commit_branch": { + "description": "Provides the name of the git branch that was used for the build, for example `master`.", + "type": "string" + }, + "commit_sha": { + "description": "Provides the hash of the commit that was used for the build, for example `46f83e112520533338245862d366f6a02cef07d4`.", + "type": "string" + }, + "commit_timestamp": { + "description": "Provides the timestamp of the commit that was used for the build, for example `2021-02-23T08:08:02-05:00`.", + "type": "string" + }, + "rustc_version": { + "description": "Provides the rustc version that was used for the build, for example `1.52.0-nightly`.", + "type": "string" + } + }, + "additionalProperties": false + }, + "get_current_interval_details": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "CurrentIntervalResponse", + "description": "Information about the current rewarding interval.", + "type": "object", + "required": [ + "current_blocktime", + "interval", + "is_current_epoch_over", + "is_current_interval_over" + ], + "properties": { + "current_blocktime": { + "description": "The current blocktime", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "interval": { + "description": "Detailed information about the underlying interval.", + "allOf": [ + { + "$ref": "#/definitions/Interval" + } + ] + }, + "is_current_epoch_over": { + "description": "Flag indicating whether the current epoch is over and it should be advanced.", + "type": "boolean" + }, + "is_current_interval_over": { + "description": "Flag indicating whether the current interval is over and it should be advanced.", + "type": "boolean" + } + }, + "additionalProperties": false, + "definitions": { + "Duration": { + "type": "object", + "required": [ + "nanos", + "secs" + ], + "properties": { + "nanos": { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "secs": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + } + }, + "Interval": { + "type": "object", + "required": [ + "current_epoch_id", + "current_epoch_start", + "epoch_length", + "epochs_in_interval", + "id", + "total_elapsed_epochs" + ], + "properties": { + "current_epoch_id": { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "current_epoch_start": { + "type": "string" + }, + "epoch_length": { + "$ref": "#/definitions/Duration" + }, + "epochs_in_interval": { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "id": { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "total_elapsed_epochs": { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + } + } + } + } + }, + "get_delegation_details": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "MixNodeDelegationResponse", + "description": "Response containing delegation details.", + "type": "object", + "required": [ + "mixnode_still_bonded" + ], + "properties": { + "delegation": { + "description": "If the delegation exists, this field contains its detailed information.", + "anyOf": [ + { + "$ref": "#/definitions/Delegation" + }, + { + "type": "null" + } + ] + }, + "mixnode_still_bonded": { + "description": "Flag indicating whether the node towards which the delegation was made is still bonded in the network.", + "type": "boolean" + } + }, + "additionalProperties": false, + "definitions": { + "Addr": { + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + }, + "Coin": { + "type": "object", + "required": [ + "amount", + "denom" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "denom": { + "type": "string" + } + } + }, + "Decimal": { + "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", + "type": "string" + }, + "Delegation": { + "description": "Information about tokens being delegated towards given mixnode in order to accrue rewards with their work.", + "type": "object", + "required": [ + "amount", + "cumulative_reward_ratio", + "height", + "mix_id", + "owner" + ], + "properties": { + "amount": { + "description": "Original delegation amount. Note that it is never mutated as delegation accumulates rewards.", + "allOf": [ + { + "$ref": "#/definitions/Coin" + } + ] + }, + "cumulative_reward_ratio": { + "description": "Value of the \"unit delegation\" associated with the mixnode at the time of delegation.", + "allOf": [ + { + "$ref": "#/definitions/Decimal" + } + ] + }, + "height": { + "description": "Block height where this delegation occurred.", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "mix_id": { + "description": "Id of the MixNode that this delegation was performed against.", + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "owner": { + "description": "Address of the owner of this delegation.", + "allOf": [ + { + "$ref": "#/definitions/Addr" + } + ] + }, + "proxy": { + "description": "Proxy address used to delegate the funds on behalf of another address", + "anyOf": [ + { + "$ref": "#/definitions/Addr" + }, + { + "type": "null" + } + ] + } + }, + "additionalProperties": false + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } + }, + "get_delegator_delegations": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "PagedDelegatorDelegationsResponse", + "description": "Response containing paged list of all delegations made by the particular address.", + "type": "object", + "required": [ + "delegations" + ], + "properties": { + "delegations": { + "description": "Each individual delegation made.", + "type": "array", + "items": { + "$ref": "#/definitions/Delegation" + } + }, + "start_next_after": { + "description": "Field indicating paging information for the following queries if the caller wishes to get further entries.", + "type": [ + "array", + "null" + ], + "items": [ + { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + { + "type": "string" + } + ], + "maxItems": 2, + "minItems": 2 + } + }, + "additionalProperties": false, + "definitions": { + "Addr": { + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + }, + "Coin": { + "type": "object", + "required": [ + "amount", + "denom" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "denom": { + "type": "string" + } + } + }, + "Decimal": { + "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", + "type": "string" + }, + "Delegation": { + "description": "Information about tokens being delegated towards given mixnode in order to accrue rewards with their work.", + "type": "object", + "required": [ + "amount", + "cumulative_reward_ratio", + "height", + "mix_id", + "owner" + ], + "properties": { + "amount": { + "description": "Original delegation amount. Note that it is never mutated as delegation accumulates rewards.", + "allOf": [ + { + "$ref": "#/definitions/Coin" + } + ] + }, + "cumulative_reward_ratio": { + "description": "Value of the \"unit delegation\" associated with the mixnode at the time of delegation.", + "allOf": [ + { + "$ref": "#/definitions/Decimal" + } + ] + }, + "height": { + "description": "Block height where this delegation occurred.", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "mix_id": { + "description": "Id of the MixNode that this delegation was performed against.", + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "owner": { + "description": "Address of the owner of this delegation.", + "allOf": [ + { + "$ref": "#/definitions/Addr" + } + ] + }, + "proxy": { + "description": "Proxy address used to delegate the funds on behalf of another address", + "anyOf": [ + { + "$ref": "#/definitions/Addr" + }, + { + "type": "null" + } + ] + } + }, + "additionalProperties": false + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } + }, + "get_epoch_status": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "EpochStatus", + "description": "The status of the current rewarding epoch.", + "type": "object", + "required": [ + "being_advanced_by", + "state" + ], + "properties": { + "being_advanced_by": { + "description": "Specifies either, which validator is currently performing progression into the following epoch (if the epoch is currently being progressed), or which validator was responsible for progressing into the current epoch (if the epoch is currently in progress)", + "allOf": [ + { + "$ref": "#/definitions/Addr" + } + ] + }, + "state": { + "description": "The concrete state of the epoch.", + "allOf": [ + { + "$ref": "#/definitions/EpochState" + } + ] + } + }, + "additionalProperties": false, + "definitions": { + "Addr": { + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + }, + "EpochState": { + "description": "The state of the current rewarding epoch.", + "oneOf": [ + { + "description": "Represents the state of an epoch that's in progress (well, duh.) All actions are allowed to be issued.", + "type": "string", + "enum": [ + "in_progress" + ] + }, + { + "description": "Represents the state of an epoch when the rewarding entity has been decided on, and the mixnodes are in the process of being rewarded for their work in this epoch.", + "type": "object", + "required": [ + "rewarding" + ], + "properties": { + "rewarding": { + "type": "object", + "required": [ + "final_node_id", + "last_rewarded" + ], + "properties": { + "final_node_id": { + "description": "The id of the last node that's going to be rewarded before progressing into the next state.", + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "last_rewarded": { + "description": "The id of the last node that has already received its rewards.", + "type": "integer", + "format": "uint32", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Represents the state of an epoch when all mixnodes have already been rewarded for their work in this epoch and all issued actions should now get resolved before being allowed to advance into the next epoch.", + "type": "string", + "enum": [ + "reconciling_events" + ] + }, + { + "description": "Represents the state of an epoch when all mixnodes have already been rewarded for their work in this epoch, all issued actions got resolved and the epoch should now be advanced whilst assigning new rewarded set.", + "type": "string", + "enum": [ + "advancing_epoch" + ] + } + ] + } + } + }, + "get_estimated_current_epoch_delegator_reward": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "EstimatedCurrentEpochRewardResponse", + "description": "Response containing estimation of node rewards for the current epoch.", + "type": "object", + "properties": { + "current_stake_value": { + "description": "The current stake value given all past rewarding and compounding since the original staking was performed.", + "anyOf": [ + { + "$ref": "#/definitions/Coin" + }, + { + "type": "null" + } + ] + }, + "current_stake_value_detailed_amount": { + "description": "The current stake value. Note that it's nearly identical to `current_stake_value`, however, it contains few additional decimal points for more accurate reward calculation.", + "anyOf": [ + { + "$ref": "#/definitions/Decimal" + }, + { + "type": "null" + } + ] + }, + "detailed_estimation_amount": { + "description": "The full reward estimation. Note that it's nearly identical to `estimation`, however, it contains few additional decimal points for more accurate reward calculation.", + "anyOf": [ + { + "$ref": "#/definitions/Decimal" + }, + { + "type": "null" + } + ] + }, + "estimation": { + "description": "The reward estimation for the current epoch, i.e. the amount of tokens that could be claimable after the epoch finishes and the state of the network does not change.", + "anyOf": [ + { + "$ref": "#/definitions/Coin" + }, + { + "type": "null" + } + ] + }, + "original_stake": { + "description": "The amount of tokens initially staked.", + "anyOf": [ + { + "$ref": "#/definitions/Coin" + }, + { + "type": "null" + } + ] + } + }, + "additionalProperties": false, + "definitions": { + "Coin": { + "type": "object", + "required": [ + "amount", + "denom" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "denom": { + "type": "string" + } + } + }, + "Decimal": { + "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", + "type": "string" + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } + }, + "get_estimated_current_epoch_operator_reward": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "EstimatedCurrentEpochRewardResponse", + "description": "Response containing estimation of node rewards for the current epoch.", + "type": "object", + "properties": { + "current_stake_value": { + "description": "The current stake value given all past rewarding and compounding since the original staking was performed.", + "anyOf": [ + { + "$ref": "#/definitions/Coin" + }, + { + "type": "null" + } + ] + }, + "current_stake_value_detailed_amount": { + "description": "The current stake value. Note that it's nearly identical to `current_stake_value`, however, it contains few additional decimal points for more accurate reward calculation.", + "anyOf": [ + { + "$ref": "#/definitions/Decimal" + }, + { + "type": "null" + } + ] + }, + "detailed_estimation_amount": { + "description": "The full reward estimation. Note that it's nearly identical to `estimation`, however, it contains few additional decimal points for more accurate reward calculation.", + "anyOf": [ + { + "$ref": "#/definitions/Decimal" + }, + { + "type": "null" + } + ] + }, + "estimation": { + "description": "The reward estimation for the current epoch, i.e. the amount of tokens that could be claimable after the epoch finishes and the state of the network does not change.", + "anyOf": [ + { + "$ref": "#/definitions/Coin" + }, + { + "type": "null" + } + ] + }, + "original_stake": { + "description": "The amount of tokens initially staked.", + "anyOf": [ + { + "$ref": "#/definitions/Coin" + }, + { + "type": "null" + } + ] + } + }, + "additionalProperties": false, + "definitions": { + "Coin": { + "type": "object", + "required": [ + "amount", + "denom" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "denom": { + "type": "string" + } + } + }, + "Decimal": { + "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", + "type": "string" + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } + }, + "get_family_by_head": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "FamilyByHeadResponse", + "description": "Response containing family information.", + "type": "object", + "required": [ + "head" + ], + "properties": { + "family": { + "description": "If applicable, the family associated with the provided head.", + "anyOf": [ + { + "$ref": "#/definitions/Family" + }, + { + "type": "null" + } + ] + }, + "head": { + "description": "The family head used for the query.", + "allOf": [ + { + "$ref": "#/definitions/FamilyHead" + } + ] + } + }, + "additionalProperties": false, + "definitions": { + "Family": { + "description": "A group of mixnodes associated with particular staking entity. When defined all nodes belonging to the same family will be prioritised to be put onto the same layer.", + "type": "object", + "required": [ + "head", + "label" + ], + "properties": { + "head": { + "description": "Owner of this family.", + "allOf": [ + { + "$ref": "#/definitions/FamilyHead" + } + ] + }, + "label": { + "description": "Human readable label for this family.", + "type": "string" + }, + "proxy": { + "description": "Optional proxy (i.e. vesting contract address) used when creating the family.", + "type": [ + "string", + "null" + ] + } + }, + "additionalProperties": false + }, + "FamilyHead": { + "description": "Head of particular family as identified by its identity key (i.e. public component of its ed25519 keypair stringified into base58).", + "type": "string" + } + } + }, + "get_family_by_label": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "FamilyByLabelResponse", + "description": "Response containing family information.", + "type": "object", + "required": [ + "label" + ], + "properties": { + "family": { + "description": "If applicable, the family associated with the provided label.", + "anyOf": [ + { + "$ref": "#/definitions/Family" + }, + { + "type": "null" + } + ] + }, + "label": { + "description": "The family label used for the query.", + "type": "string" + } + }, + "additionalProperties": false, + "definitions": { + "Family": { + "description": "A group of mixnodes associated with particular staking entity. When defined all nodes belonging to the same family will be prioritised to be put onto the same layer.", + "type": "object", + "required": [ + "head", + "label" + ], + "properties": { + "head": { + "description": "Owner of this family.", + "allOf": [ + { + "$ref": "#/definitions/FamilyHead" + } + ] + }, + "label": { + "description": "Human readable label for this family.", + "type": "string" + }, + "proxy": { + "description": "Optional proxy (i.e. vesting contract address) used when creating the family.", + "type": [ + "string", + "null" + ] + } + }, + "additionalProperties": false + }, + "FamilyHead": { + "description": "Head of particular family as identified by its identity key (i.e. public component of its ed25519 keypair stringified into base58).", + "type": "string" + } + } + }, + "get_family_members_by_head": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "FamilyMembersByHeadResponse", + "description": "Response containing family members information.", + "type": "object", + "required": [ + "head", + "members" + ], + "properties": { + "head": { + "description": "The family head used for the query.", + "allOf": [ + { + "$ref": "#/definitions/FamilyHead" + } + ] + }, + "members": { + "description": "All members belonging to the specified family.", + "type": "array", + "items": { + "type": "string" + } + } + }, + "additionalProperties": false, + "definitions": { + "FamilyHead": { + "description": "Head of particular family as identified by its identity key (i.e. public component of its ed25519 keypair stringified into base58).", + "type": "string" + } + } + }, + "get_family_members_by_label": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "FamilyMembersByLabelResponse", + "description": "Response containing family members information.", + "type": "object", + "required": [ + "label", + "members" + ], + "properties": { + "label": { + "description": "The family label used for the query.", + "type": "string" + }, + "members": { + "description": "All members belonging to the specified family.", + "type": "array", + "items": { + "type": "string" + } + } + }, + "additionalProperties": false + }, + "get_gateway_bond": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "GatewayBondResponse", + "description": "Response containing details of a gateway with the provided identity key.", + "type": "object", + "required": [ + "identity" + ], + "properties": { + "gateway": { + "description": "If there exists a gateway with the provided identity key, this field contains its details.", + "anyOf": [ + { + "$ref": "#/definitions/GatewayBond" + }, + { + "type": "null" + } + ] + }, + "identity": { + "description": "The identity key (base58-encoded ed25519 public key) of the gateway.", + "type": "string" + } + }, + "additionalProperties": false, + "definitions": { + "Addr": { + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + }, + "Coin": { + "type": "object", + "required": [ + "amount", + "denom" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "denom": { + "type": "string" + } + } + }, + "Gateway": { + "description": "Information provided by the node operator during bonding that are used to allow other entities to use the services of this node.", + "type": "object", + "required": [ + "clients_port", + "host", + "identity_key", + "location", + "mix_port", + "sphinx_key", + "version" + ], + "properties": { + "clients_port": { + "description": "Port used by this gateway for listening for client requests.", + "type": "integer", + "format": "uint16", + "minimum": 0.0 + }, + "host": { + "description": "Network address of this gateway, for example 1.1.1.1 or foo.gateway.com", + "type": "string" + }, + "identity_key": { + "description": "Base58 encoded ed25519 EdDSA public key of the gateway used to derive shared keys with clients", + "type": "string" + }, + "location": { + "description": "The physical, self-reported, location of this gateway.", + "type": "string" + }, + "mix_port": { + "description": "Port used by this gateway for listening for mix packets.", + "type": "integer", + "format": "uint16", + "minimum": 0.0 + }, + "sphinx_key": { + "description": "Base58-encoded x25519 public key used for sphinx key derivation.", + "type": "string" + }, + "version": { + "description": "The self-reported semver version of this gateway.", + "type": "string" + } + }, + "additionalProperties": false + }, + "GatewayBond": { + "description": "Basic gateway information provided by the node operator.", + "type": "object", + "required": [ + "block_height", + "gateway", + "owner", + "pledge_amount" + ], + "properties": { + "block_height": { + "description": "Block height at which this gateway has been bonded.", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "gateway": { + "description": "Information provided by the operator for the purposes of bonding.", + "allOf": [ + { + "$ref": "#/definitions/Gateway" + } + ] + }, + "owner": { + "description": "Address of the owner of this gateway.", + "allOf": [ + { + "$ref": "#/definitions/Addr" + } + ] + }, + "pledge_amount": { + "description": "Original amount pledged by the operator of this node.", + "allOf": [ + { + "$ref": "#/definitions/Coin" + } + ] + }, + "proxy": { + "description": "Entity who bonded this gateway on behalf of the owner. If exists, it's most likely the address of the vesting contract.", + "anyOf": [ + { + "$ref": "#/definitions/Addr" + }, + { + "type": "null" + } + ] + } + }, + "additionalProperties": false + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } + }, + "get_gateways": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "PagedGatewayResponse", + "description": "Response containing paged list of all gateway bonds in the contract.", + "type": "object", + "required": [ + "nodes", + "per_page" + ], + "properties": { + "nodes": { + "description": "The gateway bond information present in the contract.", + "type": "array", + "items": { + "$ref": "#/definitions/GatewayBond" + } + }, + "per_page": { + "description": "Maximum number of entries that could be included in a response. `per_page <= nodes.len()`", + "type": "integer", + "format": "uint", + "minimum": 0.0 + }, + "start_next_after": { + "description": "Field indicating paging information for the following queries if the caller wishes to get further entries.", + "type": [ + "string", + "null" + ] + } + }, + "additionalProperties": false, + "definitions": { + "Addr": { + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + }, + "Coin": { + "type": "object", + "required": [ + "amount", + "denom" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "denom": { + "type": "string" + } + } + }, + "Gateway": { + "description": "Information provided by the node operator during bonding that are used to allow other entities to use the services of this node.", + "type": "object", + "required": [ + "clients_port", + "host", + "identity_key", + "location", + "mix_port", + "sphinx_key", + "version" + ], + "properties": { + "clients_port": { + "description": "Port used by this gateway for listening for client requests.", + "type": "integer", + "format": "uint16", + "minimum": 0.0 + }, + "host": { + "description": "Network address of this gateway, for example 1.1.1.1 or foo.gateway.com", + "type": "string" + }, + "identity_key": { + "description": "Base58 encoded ed25519 EdDSA public key of the gateway used to derive shared keys with clients", + "type": "string" + }, + "location": { + "description": "The physical, self-reported, location of this gateway.", + "type": "string" + }, + "mix_port": { + "description": "Port used by this gateway for listening for mix packets.", + "type": "integer", + "format": "uint16", + "minimum": 0.0 + }, + "sphinx_key": { + "description": "Base58-encoded x25519 public key used for sphinx key derivation.", + "type": "string" + }, + "version": { + "description": "The self-reported semver version of this gateway.", + "type": "string" + } + }, + "additionalProperties": false + }, + "GatewayBond": { + "description": "Basic gateway information provided by the node operator.", + "type": "object", + "required": [ + "block_height", + "gateway", + "owner", + "pledge_amount" + ], + "properties": { + "block_height": { + "description": "Block height at which this gateway has been bonded.", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "gateway": { + "description": "Information provided by the operator for the purposes of bonding.", + "allOf": [ + { + "$ref": "#/definitions/Gateway" + } + ] + }, + "owner": { + "description": "Address of the owner of this gateway.", + "allOf": [ + { + "$ref": "#/definitions/Addr" + } + ] + }, + "pledge_amount": { + "description": "Original amount pledged by the operator of this node.", + "allOf": [ + { + "$ref": "#/definitions/Coin" + } + ] + }, + "proxy": { + "description": "Entity who bonded this gateway on behalf of the owner. If exists, it's most likely the address of the vesting contract.", + "anyOf": [ + { + "$ref": "#/definitions/Addr" + }, + { + "type": "null" + } + ] + } + }, + "additionalProperties": false + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } + }, + "get_layer_distribution": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "LayerDistribution", + "description": "The current layer distribution of the mix network.", + "type": "object", + "required": [ + "layer1", + "layer2", + "layer3" + ], + "properties": { + "layer1": { + "description": "Number of nodes on the first layer.", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "layer2": { + "description": "Number of nodes on the second layer.", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "layer3": { + "description": "Number of nodes on the third layer.", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + }, + "get_mix_node_bonds": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "PagedMixnodeBondsResponse", + "description": "Response containing paged list of all mixnode bonds in the contract.", + "type": "object", + "required": [ + "nodes", + "per_page" + ], + "properties": { + "nodes": { + "description": "The mixnode bond information present in the contract.", + "type": "array", + "items": { + "$ref": "#/definitions/MixNodeBond" + } + }, + "per_page": { + "description": "Maximum number of entries that could be included in a response. `per_page <= nodes.len()`", + "type": "integer", + "format": "uint", + "minimum": 0.0 + }, + "start_next_after": { + "description": "Field indicating paging information for the following queries if the caller wishes to get further entries.", + "type": [ + "integer", + "null" + ], + "format": "uint32", + "minimum": 0.0 + } + }, + "additionalProperties": false, + "definitions": { + "Addr": { + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + }, + "Coin": { + "type": "object", + "required": [ + "amount", + "denom" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "denom": { + "type": "string" + } + } + }, + "Layer": { + "type": "string", + "enum": [ + "One", + "Two", + "Three" + ] + }, + "MixNode": { + "description": "Information provided by the node operator during bonding that are used to allow other entities to use the services of this node.", + "type": "object", + "required": [ + "host", + "http_api_port", + "identity_key", + "mix_port", + "sphinx_key", + "verloc_port", + "version" + ], + "properties": { + "host": { + "description": "Network address of this mixnode, for example 1.1.1.1 or foo.mixnode.com", + "type": "string" + }, + "http_api_port": { + "description": "Port used by this mixnode for its http(s) API", + "type": "integer", + "format": "uint16", + "minimum": 0.0 + }, + "identity_key": { + "description": "Base58-encoded ed25519 EdDSA public key.", + "type": "string" + }, + "mix_port": { + "description": "Port used by this mixnode for listening for mix packets.", + "type": "integer", + "format": "uint16", + "minimum": 0.0 + }, + "sphinx_key": { + "description": "Base58-encoded x25519 public key used for sphinx key derivation.", + "type": "string" + }, + "verloc_port": { + "description": "Port used by this mixnode for listening for verloc requests.", + "type": "integer", + "format": "uint16", + "minimum": 0.0 + }, + "version": { + "description": "The self-reported semver version of this mixnode.", + "type": "string" + } + }, + "additionalProperties": false + }, + "MixNodeBond": { + "description": "Basic mixnode information provided by the node operator.", + "type": "object", + "required": [ + "bonding_height", + "is_unbonding", + "layer", + "mix_id", + "mix_node", + "original_pledge", + "owner" + ], + "properties": { + "bonding_height": { + "description": "Block height at which this mixnode has been bonded.", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "is_unbonding": { + "description": "Flag to indicate whether this node is in the process of unbonding, that will conclude upon the epoch finishing.", + "type": "boolean" + }, + "layer": { + "description": "Layer assigned to this mixnode.", + "allOf": [ + { + "$ref": "#/definitions/Layer" + } + ] + }, + "mix_id": { + "description": "Unique id assigned to the bonded mixnode.", + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "mix_node": { + "description": "Information provided by the operator for the purposes of bonding.", + "allOf": [ + { + "$ref": "#/definitions/MixNode" + } + ] + }, + "original_pledge": { + "description": "Original amount pledged by the operator of this node.", + "allOf": [ + { + "$ref": "#/definitions/Coin" + } + ] + }, + "owner": { + "description": "Address of the owner of this mixnode.", + "allOf": [ + { + "$ref": "#/definitions/Addr" + } + ] + }, + "proxy": { + "description": "Entity who bonded this mixnode on behalf of the owner. If exists, it's most likely the address of the vesting contract.", + "anyOf": [ + { + "$ref": "#/definitions/Addr" + }, + { + "type": "null" + } + ] + } + }, + "additionalProperties": false + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } + }, + "get_mix_nodes_detailed": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "PagedMixnodesDetailsResponse", + "description": "Response containing paged list of all mixnode details in the contract.", + "type": "object", + "required": [ + "nodes", + "per_page" + ], + "properties": { + "nodes": { + "description": "All mixnode details stored in the contract. Apart from the basic bond information it also contains details required for all future reward calculation as well as any pending changes requested by the operator.", + "type": "array", + "items": { + "$ref": "#/definitions/MixNodeDetails" + } + }, + "per_page": { + "description": "Maximum number of entries that could be included in a response. `per_page <= nodes.len()`", + "type": "integer", + "format": "uint", + "minimum": 0.0 + }, + "start_next_after": { + "description": "Field indicating paging information for the following queries if the caller wishes to get further entries.", + "type": [ + "integer", + "null" + ], + "format": "uint32", + "minimum": 0.0 + } + }, + "additionalProperties": false, + "definitions": { + "Addr": { + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + }, + "Coin": { + "type": "object", + "required": [ + "amount", + "denom" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "denom": { + "type": "string" + } + } + }, + "Decimal": { + "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", + "type": "string" + }, + "Layer": { + "type": "string", + "enum": [ + "One", + "Two", + "Three" + ] + }, + "MixNode": { + "description": "Information provided by the node operator during bonding that are used to allow other entities to use the services of this node.", + "type": "object", + "required": [ + "host", + "http_api_port", + "identity_key", + "mix_port", + "sphinx_key", + "verloc_port", + "version" + ], + "properties": { + "host": { + "description": "Network address of this mixnode, for example 1.1.1.1 or foo.mixnode.com", + "type": "string" + }, + "http_api_port": { + "description": "Port used by this mixnode for its http(s) API", + "type": "integer", + "format": "uint16", + "minimum": 0.0 + }, + "identity_key": { + "description": "Base58-encoded ed25519 EdDSA public key.", + "type": "string" + }, + "mix_port": { + "description": "Port used by this mixnode for listening for mix packets.", + "type": "integer", + "format": "uint16", + "minimum": 0.0 + }, + "sphinx_key": { + "description": "Base58-encoded x25519 public key used for sphinx key derivation.", + "type": "string" + }, + "verloc_port": { + "description": "Port used by this mixnode for listening for verloc requests.", + "type": "integer", + "format": "uint16", + "minimum": 0.0 + }, + "version": { + "description": "The self-reported semver version of this mixnode.", + "type": "string" + } + }, + "additionalProperties": false + }, + "MixNodeBond": { + "description": "Basic mixnode information provided by the node operator.", + "type": "object", + "required": [ + "bonding_height", + "is_unbonding", + "layer", + "mix_id", + "mix_node", + "original_pledge", + "owner" + ], + "properties": { + "bonding_height": { + "description": "Block height at which this mixnode has been bonded.", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "is_unbonding": { + "description": "Flag to indicate whether this node is in the process of unbonding, that will conclude upon the epoch finishing.", + "type": "boolean" + }, + "layer": { + "description": "Layer assigned to this mixnode.", + "allOf": [ + { + "$ref": "#/definitions/Layer" + } + ] + }, + "mix_id": { + "description": "Unique id assigned to the bonded mixnode.", + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "mix_node": { + "description": "Information provided by the operator for the purposes of bonding.", + "allOf": [ + { + "$ref": "#/definitions/MixNode" + } + ] + }, + "original_pledge": { + "description": "Original amount pledged by the operator of this node.", + "allOf": [ + { + "$ref": "#/definitions/Coin" + } + ] + }, + "owner": { + "description": "Address of the owner of this mixnode.", + "allOf": [ + { + "$ref": "#/definitions/Addr" + } + ] + }, + "proxy": { + "description": "Entity who bonded this mixnode on behalf of the owner. If exists, it's most likely the address of the vesting contract.", + "anyOf": [ + { + "$ref": "#/definitions/Addr" + }, + { + "type": "null" + } + ] + } + }, + "additionalProperties": false + }, + "MixNodeCostParams": { + "description": "The cost parameters, or the cost function, defined for the particular mixnode that influences how the rewards should be split between the node operator and its delegators.", + "type": "object", + "required": [ + "interval_operating_cost", + "profit_margin_percent" + ], + "properties": { + "interval_operating_cost": { + "description": "Operating cost of the associated mixnode per the entire interval.", + "allOf": [ + { + "$ref": "#/definitions/Coin" + } + ] + }, + "profit_margin_percent": { + "description": "The profit margin of the associated mixnode, i.e. the desired percent of the reward to be distributed to the operator.", + "allOf": [ + { + "$ref": "#/definitions/Percent" + } + ] + } + }, + "additionalProperties": false + }, + "MixNodeDetails": { + "description": "Full details associated with given mixnode.", + "type": "object", + "required": [ + "bond_information", + "rewarding_details" + ], + "properties": { + "bond_information": { + "description": "Basic bond information of this mixnode, such as owner address, original pledge, etc.", + "allOf": [ + { + "$ref": "#/definitions/MixNodeBond" + } + ] + }, + "pending_changes": { + "description": "Adjustments to the mixnode that are ought to happen during future epoch transitions.", + "default": { + "pledge_change": null + }, + "allOf": [ + { + "$ref": "#/definitions/PendingMixNodeChanges" + } + ] + }, + "rewarding_details": { + "description": "Details used for computation of rewarding related data.", + "allOf": [ + { + "$ref": "#/definitions/MixNodeRewarding" + } + ] + } + }, + "additionalProperties": false + }, + "MixNodeRewarding": { + "type": "object", + "required": [ + "cost_params", + "delegates", + "last_rewarded_epoch", + "operator", + "total_unit_reward", + "unique_delegations", + "unit_delegation" + ], + "properties": { + "cost_params": { + "description": "Information provided by the operator that influence the cost function.", + "allOf": [ + { + "$ref": "#/definitions/MixNodeCostParams" + } + ] + }, + "delegates": { + "description": "Total delegation and compounded reward earned by all node delegators.", + "allOf": [ + { + "$ref": "#/definitions/Decimal" + } + ] + }, + "last_rewarded_epoch": { + "description": "Marks the epoch when this node was last rewarded so that we wouldn't accidentally attempt to reward it multiple times in the same epoch.", + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "operator": { + "description": "Total pledge and compounded reward earned by the node operator.", + "allOf": [ + { + "$ref": "#/definitions/Decimal" + } + ] + }, + "total_unit_reward": { + "description": "Cumulative reward earned by the \"unit delegation\" since the block 0.", + "allOf": [ + { + "$ref": "#/definitions/Decimal" + } + ] + }, + "unique_delegations": { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "unit_delegation": { + "description": "Value of the theoretical \"unit delegation\" that has delegated to this mixnode at block 0.", + "allOf": [ + { + "$ref": "#/definitions/Decimal" + } + ] + } + }, + "additionalProperties": false + }, + "PendingMixNodeChanges": { + "type": "object", + "properties": { + "pledge_change": { + "type": [ + "integer", + "null" + ], + "format": "uint32", + "minimum": 0.0 + } + }, + "additionalProperties": false + }, + "Percent": { + "description": "Percent represents a value between 0 and 100% (i.e. between 0.0 and 1.0)", + "allOf": [ + { + "$ref": "#/definitions/Decimal" + } + ] + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } + }, + "get_mixnode_delegations": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "PagedMixNodeDelegationsResponse", + "description": "Response containing paged list of all delegations made towards particular mixnode.", + "type": "object", + "required": [ + "delegations" + ], + "properties": { + "delegations": { + "description": "Each individual delegation made.", + "type": "array", + "items": { + "$ref": "#/definitions/Delegation" + } + }, + "start_next_after": { + "description": "Field indicating paging information for the following queries if the caller wishes to get further entries.", + "type": [ + "string", + "null" + ] + } + }, + "additionalProperties": false, + "definitions": { + "Addr": { + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + }, + "Coin": { + "type": "object", + "required": [ + "amount", + "denom" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "denom": { + "type": "string" + } + } + }, + "Decimal": { + "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", + "type": "string" + }, + "Delegation": { + "description": "Information about tokens being delegated towards given mixnode in order to accrue rewards with their work.", + "type": "object", + "required": [ + "amount", + "cumulative_reward_ratio", + "height", + "mix_id", + "owner" + ], + "properties": { + "amount": { + "description": "Original delegation amount. Note that it is never mutated as delegation accumulates rewards.", + "allOf": [ + { + "$ref": "#/definitions/Coin" + } + ] + }, + "cumulative_reward_ratio": { + "description": "Value of the \"unit delegation\" associated with the mixnode at the time of delegation.", + "allOf": [ + { + "$ref": "#/definitions/Decimal" + } + ] + }, + "height": { + "description": "Block height where this delegation occurred.", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "mix_id": { + "description": "Id of the MixNode that this delegation was performed against.", + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "owner": { + "description": "Address of the owner of this delegation.", + "allOf": [ + { + "$ref": "#/definitions/Addr" + } + ] + }, + "proxy": { + "description": "Proxy address used to delegate the funds on behalf of another address", + "anyOf": [ + { + "$ref": "#/definitions/Addr" + }, + { + "type": "null" + } + ] + } + }, + "additionalProperties": false + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } + }, + "get_mixnode_details": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "MixnodeDetailsResponse", + "description": "Response containing details of a mixnode with the provided id.", + "type": "object", + "required": [ + "mix_id" + ], + "properties": { + "mix_id": { + "description": "Id of the requested mixnode.", + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "mixnode_details": { + "description": "If there exists a mixnode with the provided id, this field contains its detailed information.", + "anyOf": [ + { + "$ref": "#/definitions/MixNodeDetails" + }, + { + "type": "null" + } + ] + } + }, + "additionalProperties": false, + "definitions": { + "Addr": { + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + }, + "Coin": { + "type": "object", + "required": [ + "amount", + "denom" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "denom": { + "type": "string" + } + } + }, + "Decimal": { + "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", + "type": "string" + }, + "Layer": { + "type": "string", + "enum": [ + "One", + "Two", + "Three" + ] + }, + "MixNode": { + "description": "Information provided by the node operator during bonding that are used to allow other entities to use the services of this node.", + "type": "object", + "required": [ + "host", + "http_api_port", + "identity_key", + "mix_port", + "sphinx_key", + "verloc_port", + "version" + ], + "properties": { + "host": { + "description": "Network address of this mixnode, for example 1.1.1.1 or foo.mixnode.com", + "type": "string" + }, + "http_api_port": { + "description": "Port used by this mixnode for its http(s) API", + "type": "integer", + "format": "uint16", + "minimum": 0.0 + }, + "identity_key": { + "description": "Base58-encoded ed25519 EdDSA public key.", + "type": "string" + }, + "mix_port": { + "description": "Port used by this mixnode for listening for mix packets.", + "type": "integer", + "format": "uint16", + "minimum": 0.0 + }, + "sphinx_key": { + "description": "Base58-encoded x25519 public key used for sphinx key derivation.", + "type": "string" + }, + "verloc_port": { + "description": "Port used by this mixnode for listening for verloc requests.", + "type": "integer", + "format": "uint16", + "minimum": 0.0 + }, + "version": { + "description": "The self-reported semver version of this mixnode.", + "type": "string" + } + }, + "additionalProperties": false + }, + "MixNodeBond": { + "description": "Basic mixnode information provided by the node operator.", + "type": "object", + "required": [ + "bonding_height", + "is_unbonding", + "layer", + "mix_id", + "mix_node", + "original_pledge", + "owner" + ], + "properties": { + "bonding_height": { + "description": "Block height at which this mixnode has been bonded.", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "is_unbonding": { + "description": "Flag to indicate whether this node is in the process of unbonding, that will conclude upon the epoch finishing.", + "type": "boolean" + }, + "layer": { + "description": "Layer assigned to this mixnode.", + "allOf": [ + { + "$ref": "#/definitions/Layer" + } + ] + }, + "mix_id": { + "description": "Unique id assigned to the bonded mixnode.", + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "mix_node": { + "description": "Information provided by the operator for the purposes of bonding.", + "allOf": [ + { + "$ref": "#/definitions/MixNode" + } + ] + }, + "original_pledge": { + "description": "Original amount pledged by the operator of this node.", + "allOf": [ + { + "$ref": "#/definitions/Coin" + } + ] + }, + "owner": { + "description": "Address of the owner of this mixnode.", + "allOf": [ + { + "$ref": "#/definitions/Addr" + } + ] + }, + "proxy": { + "description": "Entity who bonded this mixnode on behalf of the owner. If exists, it's most likely the address of the vesting contract.", + "anyOf": [ + { + "$ref": "#/definitions/Addr" + }, + { + "type": "null" + } + ] + } + }, + "additionalProperties": false + }, + "MixNodeCostParams": { + "description": "The cost parameters, or the cost function, defined for the particular mixnode that influences how the rewards should be split between the node operator and its delegators.", + "type": "object", + "required": [ + "interval_operating_cost", + "profit_margin_percent" + ], + "properties": { + "interval_operating_cost": { + "description": "Operating cost of the associated mixnode per the entire interval.", + "allOf": [ + { + "$ref": "#/definitions/Coin" + } + ] + }, + "profit_margin_percent": { + "description": "The profit margin of the associated mixnode, i.e. the desired percent of the reward to be distributed to the operator.", + "allOf": [ + { + "$ref": "#/definitions/Percent" + } + ] + } + }, + "additionalProperties": false + }, + "MixNodeDetails": { + "description": "Full details associated with given mixnode.", + "type": "object", + "required": [ + "bond_information", + "rewarding_details" + ], + "properties": { + "bond_information": { + "description": "Basic bond information of this mixnode, such as owner address, original pledge, etc.", + "allOf": [ + { + "$ref": "#/definitions/MixNodeBond" + } + ] + }, + "pending_changes": { + "description": "Adjustments to the mixnode that are ought to happen during future epoch transitions.", + "default": { + "pledge_change": null + }, + "allOf": [ + { + "$ref": "#/definitions/PendingMixNodeChanges" + } + ] + }, + "rewarding_details": { + "description": "Details used for computation of rewarding related data.", + "allOf": [ + { + "$ref": "#/definitions/MixNodeRewarding" + } + ] + } + }, + "additionalProperties": false + }, + "MixNodeRewarding": { + "type": "object", + "required": [ + "cost_params", + "delegates", + "last_rewarded_epoch", + "operator", + "total_unit_reward", + "unique_delegations", + "unit_delegation" + ], + "properties": { + "cost_params": { + "description": "Information provided by the operator that influence the cost function.", + "allOf": [ + { + "$ref": "#/definitions/MixNodeCostParams" + } + ] + }, + "delegates": { + "description": "Total delegation and compounded reward earned by all node delegators.", + "allOf": [ + { + "$ref": "#/definitions/Decimal" + } + ] + }, + "last_rewarded_epoch": { + "description": "Marks the epoch when this node was last rewarded so that we wouldn't accidentally attempt to reward it multiple times in the same epoch.", + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "operator": { + "description": "Total pledge and compounded reward earned by the node operator.", + "allOf": [ + { + "$ref": "#/definitions/Decimal" + } + ] + }, + "total_unit_reward": { + "description": "Cumulative reward earned by the \"unit delegation\" since the block 0.", + "allOf": [ + { + "$ref": "#/definitions/Decimal" + } + ] + }, + "unique_delegations": { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "unit_delegation": { + "description": "Value of the theoretical \"unit delegation\" that has delegated to this mixnode at block 0.", + "allOf": [ + { + "$ref": "#/definitions/Decimal" + } + ] + } + }, + "additionalProperties": false + }, + "PendingMixNodeChanges": { + "type": "object", + "properties": { + "pledge_change": { + "type": [ + "integer", + "null" + ], + "format": "uint32", + "minimum": 0.0 + } + }, + "additionalProperties": false + }, + "Percent": { + "description": "Percent represents a value between 0 and 100% (i.e. between 0.0 and 1.0)", + "allOf": [ + { + "$ref": "#/definitions/Decimal" + } + ] + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } + }, + "get_mixnode_rewarding_details": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "MixnodeRewardingDetailsResponse", + "description": "Response containing rewarding information of a mixnode with the provided id.", + "type": "object", + "required": [ + "mix_id" + ], + "properties": { + "mix_id": { + "description": "Id of the requested mixnode.", + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "rewarding_details": { + "description": "If there exists a mixnode with the provided id, this field contains its rewarding information.", + "anyOf": [ + { + "$ref": "#/definitions/MixNodeRewarding" + }, + { + "type": "null" + } + ] + } + }, + "additionalProperties": false, + "definitions": { + "Coin": { + "type": "object", + "required": [ + "amount", + "denom" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "denom": { + "type": "string" + } + } + }, + "Decimal": { + "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", + "type": "string" + }, + "MixNodeCostParams": { + "description": "The cost parameters, or the cost function, defined for the particular mixnode that influences how the rewards should be split between the node operator and its delegators.", + "type": "object", + "required": [ + "interval_operating_cost", + "profit_margin_percent" + ], + "properties": { + "interval_operating_cost": { + "description": "Operating cost of the associated mixnode per the entire interval.", + "allOf": [ + { + "$ref": "#/definitions/Coin" + } + ] + }, + "profit_margin_percent": { + "description": "The profit margin of the associated mixnode, i.e. the desired percent of the reward to be distributed to the operator.", + "allOf": [ + { + "$ref": "#/definitions/Percent" + } + ] + } + }, + "additionalProperties": false + }, + "MixNodeRewarding": { + "type": "object", + "required": [ + "cost_params", + "delegates", + "last_rewarded_epoch", + "operator", + "total_unit_reward", + "unique_delegations", + "unit_delegation" + ], + "properties": { + "cost_params": { + "description": "Information provided by the operator that influence the cost function.", + "allOf": [ + { + "$ref": "#/definitions/MixNodeCostParams" + } + ] + }, + "delegates": { + "description": "Total delegation and compounded reward earned by all node delegators.", + "allOf": [ + { + "$ref": "#/definitions/Decimal" + } + ] + }, + "last_rewarded_epoch": { + "description": "Marks the epoch when this node was last rewarded so that we wouldn't accidentally attempt to reward it multiple times in the same epoch.", + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "operator": { + "description": "Total pledge and compounded reward earned by the node operator.", + "allOf": [ + { + "$ref": "#/definitions/Decimal" + } + ] + }, + "total_unit_reward": { + "description": "Cumulative reward earned by the \"unit delegation\" since the block 0.", + "allOf": [ + { + "$ref": "#/definitions/Decimal" + } + ] + }, + "unique_delegations": { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "unit_delegation": { + "description": "Value of the theoretical \"unit delegation\" that has delegated to this mixnode at block 0.", + "allOf": [ + { + "$ref": "#/definitions/Decimal" + } + ] + } + }, + "additionalProperties": false + }, + "Percent": { + "description": "Percent represents a value between 0 and 100% (i.e. between 0.0 and 1.0)", + "allOf": [ + { + "$ref": "#/definitions/Decimal" + } + ] + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } + }, + "get_number_of_pending_events": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "NumberOfPendingEventsResponse", + "description": "Response containing number of currently pending epoch and interval events.", + "type": "object", + "required": [ + "epoch_events", + "interval_events" + ], + "properties": { + "epoch_events": { + "description": "The number of the currently pending epoch events.", + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "interval_events": { + "description": "The number of the currently pending epoch events.", + "type": "integer", + "format": "uint32", + "minimum": 0.0 + } + }, + "additionalProperties": false + }, + "get_owned_gateway": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "GatewayOwnershipResponse", + "description": "Response containing details of a gateway belonging to the particular owner.", + "type": "object", + "required": [ + "address" + ], + "properties": { + "address": { + "description": "Validated address of the gateway owner.", + "allOf": [ + { + "$ref": "#/definitions/Addr" + } + ] + }, + "gateway": { + "description": "If the provided address owns a gateway, this field contains its details.", + "anyOf": [ + { + "$ref": "#/definitions/GatewayBond" + }, + { + "type": "null" + } + ] + } + }, + "additionalProperties": false, + "definitions": { + "Addr": { + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + }, + "Coin": { + "type": "object", + "required": [ + "amount", + "denom" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "denom": { + "type": "string" + } + } + }, + "Gateway": { + "description": "Information provided by the node operator during bonding that are used to allow other entities to use the services of this node.", + "type": "object", + "required": [ + "clients_port", + "host", + "identity_key", + "location", + "mix_port", + "sphinx_key", + "version" + ], + "properties": { + "clients_port": { + "description": "Port used by this gateway for listening for client requests.", + "type": "integer", + "format": "uint16", + "minimum": 0.0 + }, + "host": { + "description": "Network address of this gateway, for example 1.1.1.1 or foo.gateway.com", + "type": "string" + }, + "identity_key": { + "description": "Base58 encoded ed25519 EdDSA public key of the gateway used to derive shared keys with clients", + "type": "string" + }, + "location": { + "description": "The physical, self-reported, location of this gateway.", + "type": "string" + }, + "mix_port": { + "description": "Port used by this gateway for listening for mix packets.", + "type": "integer", + "format": "uint16", + "minimum": 0.0 + }, + "sphinx_key": { + "description": "Base58-encoded x25519 public key used for sphinx key derivation.", + "type": "string" + }, + "version": { + "description": "The self-reported semver version of this gateway.", + "type": "string" + } + }, + "additionalProperties": false + }, + "GatewayBond": { + "description": "Basic gateway information provided by the node operator.", + "type": "object", + "required": [ + "block_height", + "gateway", + "owner", + "pledge_amount" + ], + "properties": { + "block_height": { + "description": "Block height at which this gateway has been bonded.", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "gateway": { + "description": "Information provided by the operator for the purposes of bonding.", + "allOf": [ + { + "$ref": "#/definitions/Gateway" + } + ] + }, + "owner": { + "description": "Address of the owner of this gateway.", + "allOf": [ + { + "$ref": "#/definitions/Addr" + } + ] + }, + "pledge_amount": { + "description": "Original amount pledged by the operator of this node.", + "allOf": [ + { + "$ref": "#/definitions/Coin" + } + ] + }, + "proxy": { + "description": "Entity who bonded this gateway on behalf of the owner. If exists, it's most likely the address of the vesting contract.", + "anyOf": [ + { + "$ref": "#/definitions/Addr" + }, + { + "type": "null" + } + ] + } + }, + "additionalProperties": false + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } + }, + "get_owned_mixnode": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "MixOwnershipResponse", + "description": "Response containing details of a mixnode belonging to the particular owner.", + "type": "object", + "required": [ + "address" + ], + "properties": { + "address": { + "description": "Validated address of the mixnode owner.", + "allOf": [ + { + "$ref": "#/definitions/Addr" + } + ] + }, + "mixnode_details": { + "description": "If the provided address owns a mixnode, this field contains its detailed information.", + "anyOf": [ + { + "$ref": "#/definitions/MixNodeDetails" + }, + { + "type": "null" + } + ] + } + }, + "additionalProperties": false, + "definitions": { + "Addr": { + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + }, + "Coin": { + "type": "object", + "required": [ + "amount", + "denom" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "denom": { + "type": "string" + } + } + }, + "Decimal": { + "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", + "type": "string" + }, + "Layer": { + "type": "string", + "enum": [ + "One", + "Two", + "Three" + ] + }, + "MixNode": { + "description": "Information provided by the node operator during bonding that are used to allow other entities to use the services of this node.", + "type": "object", + "required": [ + "host", + "http_api_port", + "identity_key", + "mix_port", + "sphinx_key", + "verloc_port", + "version" + ], + "properties": { + "host": { + "description": "Network address of this mixnode, for example 1.1.1.1 or foo.mixnode.com", + "type": "string" + }, + "http_api_port": { + "description": "Port used by this mixnode for its http(s) API", + "type": "integer", + "format": "uint16", + "minimum": 0.0 + }, + "identity_key": { + "description": "Base58-encoded ed25519 EdDSA public key.", + "type": "string" + }, + "mix_port": { + "description": "Port used by this mixnode for listening for mix packets.", + "type": "integer", + "format": "uint16", + "minimum": 0.0 + }, + "sphinx_key": { + "description": "Base58-encoded x25519 public key used for sphinx key derivation.", + "type": "string" + }, + "verloc_port": { + "description": "Port used by this mixnode for listening for verloc requests.", + "type": "integer", + "format": "uint16", + "minimum": 0.0 + }, + "version": { + "description": "The self-reported semver version of this mixnode.", + "type": "string" + } + }, + "additionalProperties": false + }, + "MixNodeBond": { + "description": "Basic mixnode information provided by the node operator.", + "type": "object", + "required": [ + "bonding_height", + "is_unbonding", + "layer", + "mix_id", + "mix_node", + "original_pledge", + "owner" + ], + "properties": { + "bonding_height": { + "description": "Block height at which this mixnode has been bonded.", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "is_unbonding": { + "description": "Flag to indicate whether this node is in the process of unbonding, that will conclude upon the epoch finishing.", + "type": "boolean" + }, + "layer": { + "description": "Layer assigned to this mixnode.", + "allOf": [ + { + "$ref": "#/definitions/Layer" + } + ] + }, + "mix_id": { + "description": "Unique id assigned to the bonded mixnode.", + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "mix_node": { + "description": "Information provided by the operator for the purposes of bonding.", + "allOf": [ + { + "$ref": "#/definitions/MixNode" + } + ] + }, + "original_pledge": { + "description": "Original amount pledged by the operator of this node.", + "allOf": [ + { + "$ref": "#/definitions/Coin" + } + ] + }, + "owner": { + "description": "Address of the owner of this mixnode.", + "allOf": [ + { + "$ref": "#/definitions/Addr" + } + ] + }, + "proxy": { + "description": "Entity who bonded this mixnode on behalf of the owner. If exists, it's most likely the address of the vesting contract.", + "anyOf": [ + { + "$ref": "#/definitions/Addr" + }, + { + "type": "null" + } + ] + } + }, + "additionalProperties": false + }, + "MixNodeCostParams": { + "description": "The cost parameters, or the cost function, defined for the particular mixnode that influences how the rewards should be split between the node operator and its delegators.", + "type": "object", + "required": [ + "interval_operating_cost", + "profit_margin_percent" + ], + "properties": { + "interval_operating_cost": { + "description": "Operating cost of the associated mixnode per the entire interval.", + "allOf": [ + { + "$ref": "#/definitions/Coin" + } + ] + }, + "profit_margin_percent": { + "description": "The profit margin of the associated mixnode, i.e. the desired percent of the reward to be distributed to the operator.", + "allOf": [ + { + "$ref": "#/definitions/Percent" + } + ] + } + }, + "additionalProperties": false + }, + "MixNodeDetails": { + "description": "Full details associated with given mixnode.", + "type": "object", + "required": [ + "bond_information", + "rewarding_details" + ], + "properties": { + "bond_information": { + "description": "Basic bond information of this mixnode, such as owner address, original pledge, etc.", + "allOf": [ + { + "$ref": "#/definitions/MixNodeBond" + } + ] + }, + "pending_changes": { + "description": "Adjustments to the mixnode that are ought to happen during future epoch transitions.", + "default": { + "pledge_change": null + }, + "allOf": [ + { + "$ref": "#/definitions/PendingMixNodeChanges" + } + ] + }, + "rewarding_details": { + "description": "Details used for computation of rewarding related data.", + "allOf": [ + { + "$ref": "#/definitions/MixNodeRewarding" + } + ] + } + }, + "additionalProperties": false + }, + "MixNodeRewarding": { + "type": "object", + "required": [ + "cost_params", + "delegates", + "last_rewarded_epoch", + "operator", + "total_unit_reward", + "unique_delegations", + "unit_delegation" + ], + "properties": { + "cost_params": { + "description": "Information provided by the operator that influence the cost function.", + "allOf": [ + { + "$ref": "#/definitions/MixNodeCostParams" + } + ] + }, + "delegates": { + "description": "Total delegation and compounded reward earned by all node delegators.", + "allOf": [ + { + "$ref": "#/definitions/Decimal" + } + ] + }, + "last_rewarded_epoch": { + "description": "Marks the epoch when this node was last rewarded so that we wouldn't accidentally attempt to reward it multiple times in the same epoch.", + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "operator": { + "description": "Total pledge and compounded reward earned by the node operator.", + "allOf": [ + { + "$ref": "#/definitions/Decimal" + } + ] + }, + "total_unit_reward": { + "description": "Cumulative reward earned by the \"unit delegation\" since the block 0.", + "allOf": [ + { + "$ref": "#/definitions/Decimal" + } + ] + }, + "unique_delegations": { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "unit_delegation": { + "description": "Value of the theoretical \"unit delegation\" that has delegated to this mixnode at block 0.", + "allOf": [ + { + "$ref": "#/definitions/Decimal" + } + ] + } + }, + "additionalProperties": false + }, + "PendingMixNodeChanges": { + "type": "object", + "properties": { + "pledge_change": { + "type": [ + "integer", + "null" + ], + "format": "uint32", + "minimum": 0.0 + } + }, + "additionalProperties": false + }, + "Percent": { + "description": "Percent represents a value between 0 and 100% (i.e. between 0.0 and 1.0)", + "allOf": [ + { + "$ref": "#/definitions/Decimal" + } + ] + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } + }, + "get_pending_delegator_reward": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "PendingRewardResponse", + "description": "Response containing information about accrued rewards.", + "type": "object", + "required": [ + "mixnode_still_fully_bonded" + ], + "properties": { + "amount_earned": { + "description": "The amount of tokens that could be claimed.", + "anyOf": [ + { + "$ref": "#/definitions/Coin" + }, + { + "type": "null" + } + ] + }, + "amount_earned_detailed": { + "description": "The full pending rewards. Note that it's nearly identical to `amount_earned`, however, it contains few additional decimal points for more accurate reward calculation.", + "anyOf": [ + { + "$ref": "#/definitions/Decimal" + }, + { + "type": "null" + } + ] + }, + "amount_staked": { + "description": "The amount of tokens initially staked.", + "anyOf": [ + { + "$ref": "#/definitions/Coin" + }, + { + "type": "null" + } + ] + }, + "mixnode_still_fully_bonded": { + "description": "The associated mixnode is still fully bonded, meaning it is neither unbonded nor in the process of unbonding that would have finished at the epoch transition.", + "type": "boolean" + } + }, + "additionalProperties": false, + "definitions": { + "Coin": { + "type": "object", + "required": [ + "amount", + "denom" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "denom": { + "type": "string" + } + } + }, + "Decimal": { + "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", + "type": "string" + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } + }, + "get_pending_epoch_event": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "PendingEpochEventResponse", + "type": "object", + "required": [ + "event_id" + ], + "properties": { + "event": { + "anyOf": [ + { + "$ref": "#/definitions/PendingEpochEventData" + }, + { + "type": "null" + } + ] + }, + "event_id": { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + } + }, + "additionalProperties": false, + "definitions": { + "Addr": { + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + }, + "Coin": { + "type": "object", + "required": [ + "amount", + "denom" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "denom": { + "type": "string" + } + } + }, + "PendingEpochEventData": { + "description": "Details of a particular pending epoch event.", + "type": "object", + "required": [ + "created_at", + "kind" + ], + "properties": { + "created_at": { + "description": "The block height at which the request has been made.", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "kind": { + "description": "The underlying event data, containing its concrete type and information on how it should get resolved.", + "allOf": [ + { + "$ref": "#/definitions/PendingEpochEventKind" + } + ] + } + }, + "additionalProperties": false + }, + "PendingEpochEventKind": { + "description": "Enum encompassing all possible epoch events.", + "oneOf": [ + { + "description": "Request to create a delegation towards particular mixnode. Note that if such delegation already exists, it will get updated with the provided token amount.", + "type": "object", + "required": [ + "delegate" + ], + "properties": { + "delegate": { + "type": "object", + "required": [ + "amount", + "mix_id", + "owner" + ], + "properties": { + "amount": { + "description": "The amount of tokens to use for the delegation.", + "allOf": [ + { + "$ref": "#/definitions/Coin" + } + ] + }, + "mix_id": { + "description": "The id of the mixnode used for the delegation.", + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "owner": { + "description": "The address of the owner of the delegation.", + "allOf": [ + { + "$ref": "#/definitions/Addr" + } + ] + }, + "proxy": { + "description": "Entity who made the delegation on behalf of the owner. If present, it's most likely the address of the vesting contract.", + "anyOf": [ + { + "$ref": "#/definitions/Addr" + }, + { + "type": "null" + } + ] + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Request to remove delegation from particular mixnode.", + "type": "object", + "required": [ + "undelegate" + ], + "properties": { + "undelegate": { + "type": "object", + "required": [ + "mix_id", + "owner" + ], + "properties": { + "mix_id": { + "description": "The id of the mixnode used for the delegation.", + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "owner": { + "description": "The address of the owner of the delegation.", + "allOf": [ + { + "$ref": "#/definitions/Addr" + } + ] + }, + "proxy": { + "description": "Entity who made the delegation on behalf of the owner. If present, it's most likely the address of the vesting contract.", + "anyOf": [ + { + "$ref": "#/definitions/Addr" + }, + { + "type": "null" + } + ] + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Request to pledge more tokens (by the node operator) towards its node.", + "type": "object", + "required": [ + "pledge_more" + ], + "properties": { + "pledge_more": { + "type": "object", + "required": [ + "amount", + "mix_id" + ], + "properties": { + "amount": { + "description": "The amount of additional tokens to use by the pledge.", + "allOf": [ + { + "$ref": "#/definitions/Coin" + } + ] + }, + "mix_id": { + "description": "The id of the mixnode that will have its pledge updated.", + "type": "integer", + "format": "uint32", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Request to decrease amount of pledged tokens (by the node operator) from its node.", + "type": "object", + "required": [ + "decrease_pledge" + ], + "properties": { + "decrease_pledge": { + "type": "object", + "required": [ + "decrease_by", + "mix_id" + ], + "properties": { + "decrease_by": { + "description": "The amount of tokens that should be removed from the pledge.", + "allOf": [ + { + "$ref": "#/definitions/Coin" + } + ] + }, + "mix_id": { + "description": "The id of the mixnode that will have its pledge updated.", + "type": "integer", + "format": "uint32", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Request to unbond a mixnode and completely remove it from the network.", + "type": "object", + "required": [ + "unbond_mixnode" + ], + "properties": { + "unbond_mixnode": { + "type": "object", + "required": [ + "mix_id" + ], + "properties": { + "mix_id": { + "description": "The id of the mixnode that will get unbonded.", + "type": "integer", + "format": "uint32", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Request to update the current size of the active set.", + "type": "object", + "required": [ + "update_active_set_size" + ], + "properties": { + "update_active_set_size": { + "type": "object", + "required": [ + "new_size" + ], + "properties": { + "new_size": { + "description": "The new desired size of the active set.", + "type": "integer", + "format": "uint32", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + ] + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } + }, + "get_pending_epoch_events": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "PendingEpochEventsResponse", + "description": "Response containing all currently pending epoch events that will be resolved once the current epoch finishes.", + "type": "object", + "required": [ + "events", + "seconds_until_executable" + ], + "properties": { + "events": { + "description": "The currently pending events.", + "type": "array", + "items": { + "$ref": "#/definitions/PendingEpochEvent" + } + }, + "seconds_until_executable": { + "description": "Amount of seconds until the events would be eligible to be resolved. It's equivalent to the time until the current epoch finishes.", + "type": "integer", + "format": "int64" + }, + "start_next_after": { + "description": "Field indicating paging information for the following queries if the caller wishes to get further entries.", + "type": [ + "integer", + "null" + ], + "format": "uint32", + "minimum": 0.0 + } + }, + "additionalProperties": false, + "definitions": { + "Addr": { + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + }, + "Coin": { + "type": "object", + "required": [ + "amount", + "denom" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "denom": { + "type": "string" + } + } + }, + "PendingEpochEvent": { + "description": "A request made at some point in the current epoch that's going to get resolved once the epoch rolls over.", + "type": "object", + "required": [ + "event", + "id" + ], + "properties": { + "event": { + "description": "The underlying event details, containing its type and information on how it should get resolved.", + "allOf": [ + { + "$ref": "#/definitions/PendingEpochEventData" + } + ] + }, + "id": { + "description": "The unique id associated with the event.", + "type": "integer", + "format": "uint32", + "minimum": 0.0 + } + }, + "additionalProperties": false + }, + "PendingEpochEventData": { + "description": "Details of a particular pending epoch event.", + "type": "object", + "required": [ + "created_at", + "kind" + ], + "properties": { + "created_at": { + "description": "The block height at which the request has been made.", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "kind": { + "description": "The underlying event data, containing its concrete type and information on how it should get resolved.", + "allOf": [ + { + "$ref": "#/definitions/PendingEpochEventKind" + } + ] + } + }, + "additionalProperties": false + }, + "PendingEpochEventKind": { + "description": "Enum encompassing all possible epoch events.", + "oneOf": [ + { + "description": "Request to create a delegation towards particular mixnode. Note that if such delegation already exists, it will get updated with the provided token amount.", + "type": "object", + "required": [ + "delegate" + ], + "properties": { + "delegate": { + "type": "object", + "required": [ + "amount", + "mix_id", + "owner" + ], + "properties": { + "amount": { + "description": "The amount of tokens to use for the delegation.", + "allOf": [ + { + "$ref": "#/definitions/Coin" + } + ] + }, + "mix_id": { + "description": "The id of the mixnode used for the delegation.", + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "owner": { + "description": "The address of the owner of the delegation.", + "allOf": [ + { + "$ref": "#/definitions/Addr" + } + ] + }, + "proxy": { + "description": "Entity who made the delegation on behalf of the owner. If present, it's most likely the address of the vesting contract.", + "anyOf": [ + { + "$ref": "#/definitions/Addr" + }, + { + "type": "null" + } + ] + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Request to remove delegation from particular mixnode.", + "type": "object", + "required": [ + "undelegate" + ], + "properties": { + "undelegate": { + "type": "object", + "required": [ + "mix_id", + "owner" + ], + "properties": { + "mix_id": { + "description": "The id of the mixnode used for the delegation.", + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "owner": { + "description": "The address of the owner of the delegation.", + "allOf": [ + { + "$ref": "#/definitions/Addr" + } + ] + }, + "proxy": { + "description": "Entity who made the delegation on behalf of the owner. If present, it's most likely the address of the vesting contract.", + "anyOf": [ + { + "$ref": "#/definitions/Addr" + }, + { + "type": "null" + } + ] + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Request to pledge more tokens (by the node operator) towards its node.", + "type": "object", + "required": [ + "pledge_more" + ], + "properties": { + "pledge_more": { + "type": "object", + "required": [ + "amount", + "mix_id" + ], + "properties": { + "amount": { + "description": "The amount of additional tokens to use by the pledge.", + "allOf": [ + { + "$ref": "#/definitions/Coin" + } + ] + }, + "mix_id": { + "description": "The id of the mixnode that will have its pledge updated.", + "type": "integer", + "format": "uint32", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Request to decrease amount of pledged tokens (by the node operator) from its node.", + "type": "object", + "required": [ + "decrease_pledge" + ], + "properties": { + "decrease_pledge": { + "type": "object", + "required": [ + "decrease_by", + "mix_id" + ], + "properties": { + "decrease_by": { + "description": "The amount of tokens that should be removed from the pledge.", + "allOf": [ + { + "$ref": "#/definitions/Coin" + } + ] + }, + "mix_id": { + "description": "The id of the mixnode that will have its pledge updated.", + "type": "integer", + "format": "uint32", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Request to unbond a mixnode and completely remove it from the network.", + "type": "object", + "required": [ + "unbond_mixnode" + ], + "properties": { + "unbond_mixnode": { + "type": "object", + "required": [ + "mix_id" + ], + "properties": { + "mix_id": { + "description": "The id of the mixnode that will get unbonded.", + "type": "integer", + "format": "uint32", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Request to update the current size of the active set.", + "type": "object", + "required": [ + "update_active_set_size" + ], + "properties": { + "update_active_set_size": { + "type": "object", + "required": [ + "new_size" + ], + "properties": { + "new_size": { + "description": "The new desired size of the active set.", + "type": "integer", + "format": "uint32", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + ] + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } + }, + "get_pending_interval_event": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "PendingIntervalEventResponse", + "type": "object", + "required": [ + "event_id" + ], + "properties": { + "event": { + "anyOf": [ + { + "$ref": "#/definitions/PendingIntervalEventData" + }, + { + "type": "null" + } + ] + }, + "event_id": { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + } + }, + "additionalProperties": false, + "definitions": { + "Coin": { + "type": "object", + "required": [ + "amount", + "denom" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "denom": { + "type": "string" + } + } + }, + "Decimal": { + "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", + "type": "string" + }, + "IntervalRewardingParamsUpdate": { + "description": "Specification on how the rewarding params should be updated.", + "type": "object", + "properties": { + "active_set_work_factor": { + "description": "Defines the new value of the active set work factor.", + "anyOf": [ + { + "$ref": "#/definitions/Decimal" + }, + { + "type": "null" + } + ] + }, + "interval_pool_emission": { + "description": "Defines the new value of the interval pool emission rate.", + "anyOf": [ + { + "$ref": "#/definitions/Percent" + }, + { + "type": "null" + } + ] + }, + "reward_pool": { + "description": "Defines the new value of the reward pool.", + "anyOf": [ + { + "$ref": "#/definitions/Decimal" + }, + { + "type": "null" + } + ] + }, + "rewarded_set_size": { + "description": "Defines the new size of the rewarded set.", + "type": [ + "integer", + "null" + ], + "format": "uint32", + "minimum": 0.0 + }, + "staking_supply": { + "description": "Defines the new value of the staking supply.", + "anyOf": [ + { + "$ref": "#/definitions/Decimal" + }, + { + "type": "null" + } + ] + }, + "staking_supply_scale_factor": { + "description": "Defines the new value of the staking supply scale factor.", + "anyOf": [ + { + "$ref": "#/definitions/Percent" + }, + { + "type": "null" + } + ] + }, + "sybil_resistance_percent": { + "description": "Defines the new value of the sybil resistance percent.", + "anyOf": [ + { + "$ref": "#/definitions/Percent" + }, + { + "type": "null" + } + ] + } + }, + "additionalProperties": false + }, + "MixNodeCostParams": { + "description": "The cost parameters, or the cost function, defined for the particular mixnode that influences how the rewards should be split between the node operator and its delegators.", + "type": "object", + "required": [ + "interval_operating_cost", + "profit_margin_percent" + ], + "properties": { + "interval_operating_cost": { + "description": "Operating cost of the associated mixnode per the entire interval.", + "allOf": [ + { + "$ref": "#/definitions/Coin" + } + ] + }, + "profit_margin_percent": { + "description": "The profit margin of the associated mixnode, i.e. the desired percent of the reward to be distributed to the operator.", + "allOf": [ + { + "$ref": "#/definitions/Percent" + } + ] + } + }, + "additionalProperties": false + }, + "PendingIntervalEventData": { + "description": "Details of a particular pending interval event.", + "type": "object", + "required": [ + "created_at", + "kind" + ], + "properties": { + "created_at": { + "description": "The block height at which the request has been made.", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "kind": { + "description": "The underlying event data, containing its concrete type and information on how it should get resolved.", + "allOf": [ + { + "$ref": "#/definitions/PendingIntervalEventKind" + } + ] + } + }, + "additionalProperties": false + }, + "PendingIntervalEventKind": { + "description": "Enum encompassing all possible interval events.", + "oneOf": [ + { + "description": "Request to update cost parameters of given mixnode.", + "type": "object", + "required": [ + "change_mix_cost_params" + ], + "properties": { + "change_mix_cost_params": { + "type": "object", + "required": [ + "mix_id", + "new_costs" + ], + "properties": { + "mix_id": { + "description": "The id of the mixnode that will have its cost parameters updated.", + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "new_costs": { + "description": "The new updated cost function of this mixnode.", + "allOf": [ + { + "$ref": "#/definitions/MixNodeCostParams" + } + ] + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Request to update the underlying rewarding parameters used by the system", + "type": "object", + "required": [ + "update_rewarding_params" + ], + "properties": { + "update_rewarding_params": { + "type": "object", + "required": [ + "update" + ], + "properties": { + "update": { + "description": "The detailed specification of the update.", + "allOf": [ + { + "$ref": "#/definitions/IntervalRewardingParamsUpdate" + } + ] + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Request to change the next interval configuration.", + "type": "object", + "required": [ + "update_interval_config" + ], + "properties": { + "update_interval_config": { + "type": "object", + "required": [ + "epoch_duration_secs", + "epochs_in_interval" + ], + "properties": { + "epoch_duration_secs": { + "description": "The new epoch duration.", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "epochs_in_interval": { + "description": "The new number of epochs in intervals.", + "type": "integer", + "format": "uint32", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + ] + }, + "Percent": { + "description": "Percent represents a value between 0 and 100% (i.e. between 0.0 and 1.0)", + "allOf": [ + { + "$ref": "#/definitions/Decimal" + } + ] + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } + }, + "get_pending_interval_events": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "PendingIntervalEventsResponse", + "description": "Response containing all currently pending interval events that will be resolved once the current interval finishes.", + "type": "object", + "required": [ + "events", + "seconds_until_executable" + ], + "properties": { + "events": { + "description": "The currently pending events.", + "type": "array", + "items": { + "$ref": "#/definitions/PendingIntervalEvent" + } + }, + "seconds_until_executable": { + "description": "Amount of seconds until the events would be eligible to be resolved. It's equivalent to the time until the current interval finishes.", + "type": "integer", + "format": "int64" + }, + "start_next_after": { + "description": "Field indicating paging information for the following queries if the caller wishes to get further entries.", + "type": [ + "integer", + "null" + ], + "format": "uint32", + "minimum": 0.0 + } + }, + "additionalProperties": false, + "definitions": { + "Coin": { + "type": "object", + "required": [ + "amount", + "denom" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "denom": { + "type": "string" + } + } + }, + "Decimal": { + "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", + "type": "string" + }, + "IntervalRewardingParamsUpdate": { + "description": "Specification on how the rewarding params should be updated.", + "type": "object", + "properties": { + "active_set_work_factor": { + "description": "Defines the new value of the active set work factor.", + "anyOf": [ + { + "$ref": "#/definitions/Decimal" + }, + { + "type": "null" + } + ] + }, + "interval_pool_emission": { + "description": "Defines the new value of the interval pool emission rate.", + "anyOf": [ + { + "$ref": "#/definitions/Percent" + }, + { + "type": "null" + } + ] + }, + "reward_pool": { + "description": "Defines the new value of the reward pool.", + "anyOf": [ + { + "$ref": "#/definitions/Decimal" + }, + { + "type": "null" + } + ] + }, + "rewarded_set_size": { + "description": "Defines the new size of the rewarded set.", + "type": [ + "integer", + "null" + ], + "format": "uint32", + "minimum": 0.0 + }, + "staking_supply": { + "description": "Defines the new value of the staking supply.", + "anyOf": [ + { + "$ref": "#/definitions/Decimal" + }, + { + "type": "null" + } + ] + }, + "staking_supply_scale_factor": { + "description": "Defines the new value of the staking supply scale factor.", + "anyOf": [ + { + "$ref": "#/definitions/Percent" + }, + { + "type": "null" + } + ] + }, + "sybil_resistance_percent": { + "description": "Defines the new value of the sybil resistance percent.", + "anyOf": [ + { + "$ref": "#/definitions/Percent" + }, + { + "type": "null" + } + ] + } + }, + "additionalProperties": false + }, + "MixNodeCostParams": { + "description": "The cost parameters, or the cost function, defined for the particular mixnode that influences how the rewards should be split between the node operator and its delegators.", + "type": "object", + "required": [ + "interval_operating_cost", + "profit_margin_percent" + ], + "properties": { + "interval_operating_cost": { + "description": "Operating cost of the associated mixnode per the entire interval.", + "allOf": [ + { + "$ref": "#/definitions/Coin" + } + ] + }, + "profit_margin_percent": { + "description": "The profit margin of the associated mixnode, i.e. the desired percent of the reward to be distributed to the operator.", + "allOf": [ + { + "$ref": "#/definitions/Percent" + } + ] + } + }, + "additionalProperties": false + }, + "PendingIntervalEvent": { + "description": "A request made at some point in the current interval that's going to get resolved once the interval rolls over.", + "type": "object", + "required": [ + "event", + "id" + ], + "properties": { + "event": { + "description": "The underlying event details, containing its type and information on how it should get resolved.", + "allOf": [ + { + "$ref": "#/definitions/PendingIntervalEventData" + } + ] + }, + "id": { + "description": "The unique id associated with the event.", + "type": "integer", + "format": "uint32", + "minimum": 0.0 + } + }, + "additionalProperties": false + }, + "PendingIntervalEventData": { + "description": "Details of a particular pending interval event.", + "type": "object", + "required": [ + "created_at", + "kind" + ], + "properties": { + "created_at": { + "description": "The block height at which the request has been made.", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "kind": { + "description": "The underlying event data, containing its concrete type and information on how it should get resolved.", + "allOf": [ + { + "$ref": "#/definitions/PendingIntervalEventKind" + } + ] + } + }, + "additionalProperties": false + }, + "PendingIntervalEventKind": { + "description": "Enum encompassing all possible interval events.", + "oneOf": [ + { + "description": "Request to update cost parameters of given mixnode.", + "type": "object", + "required": [ + "change_mix_cost_params" + ], + "properties": { + "change_mix_cost_params": { + "type": "object", + "required": [ + "mix_id", + "new_costs" + ], + "properties": { + "mix_id": { + "description": "The id of the mixnode that will have its cost parameters updated.", + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "new_costs": { + "description": "The new updated cost function of this mixnode.", + "allOf": [ + { + "$ref": "#/definitions/MixNodeCostParams" + } + ] + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Request to update the underlying rewarding parameters used by the system", + "type": "object", + "required": [ + "update_rewarding_params" + ], + "properties": { + "update_rewarding_params": { + "type": "object", + "required": [ + "update" + ], + "properties": { + "update": { + "description": "The detailed specification of the update.", + "allOf": [ + { + "$ref": "#/definitions/IntervalRewardingParamsUpdate" + } + ] + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Request to change the next interval configuration.", + "type": "object", + "required": [ + "update_interval_config" + ], + "properties": { + "update_interval_config": { + "type": "object", + "required": [ + "epoch_duration_secs", + "epochs_in_interval" + ], + "properties": { + "epoch_duration_secs": { + "description": "The new epoch duration.", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "epochs_in_interval": { + "description": "The new number of epochs in intervals.", + "type": "integer", + "format": "uint32", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + ] + }, + "Percent": { + "description": "Percent represents a value between 0 and 100% (i.e. between 0.0 and 1.0)", + "allOf": [ + { + "$ref": "#/definitions/Decimal" + } + ] + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } + }, + "get_pending_mix_node_operator_reward": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "PendingRewardResponse", + "description": "Response containing information about accrued rewards.", + "type": "object", + "required": [ + "mixnode_still_fully_bonded" + ], + "properties": { + "amount_earned": { + "description": "The amount of tokens that could be claimed.", + "anyOf": [ + { + "$ref": "#/definitions/Coin" + }, + { + "type": "null" + } + ] + }, + "amount_earned_detailed": { + "description": "The full pending rewards. Note that it's nearly identical to `amount_earned`, however, it contains few additional decimal points for more accurate reward calculation.", + "anyOf": [ + { + "$ref": "#/definitions/Decimal" + }, + { + "type": "null" + } + ] + }, + "amount_staked": { + "description": "The amount of tokens initially staked.", + "anyOf": [ + { + "$ref": "#/definitions/Coin" + }, + { + "type": "null" + } + ] + }, + "mixnode_still_fully_bonded": { + "description": "The associated mixnode is still fully bonded, meaning it is neither unbonded nor in the process of unbonding that would have finished at the epoch transition.", + "type": "boolean" + } + }, + "additionalProperties": false, + "definitions": { + "Coin": { + "type": "object", + "required": [ + "amount", + "denom" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "denom": { + "type": "string" + } + } + }, + "Decimal": { + "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", + "type": "string" + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } + }, + "get_pending_operator_reward": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "PendingRewardResponse", + "description": "Response containing information about accrued rewards.", + "type": "object", + "required": [ + "mixnode_still_fully_bonded" + ], + "properties": { + "amount_earned": { + "description": "The amount of tokens that could be claimed.", + "anyOf": [ + { + "$ref": "#/definitions/Coin" + }, + { + "type": "null" + } + ] + }, + "amount_earned_detailed": { + "description": "The full pending rewards. Note that it's nearly identical to `amount_earned`, however, it contains few additional decimal points for more accurate reward calculation.", + "anyOf": [ + { + "$ref": "#/definitions/Decimal" + }, + { + "type": "null" + } + ] + }, + "amount_staked": { + "description": "The amount of tokens initially staked.", + "anyOf": [ + { + "$ref": "#/definitions/Coin" + }, + { + "type": "null" + } + ] + }, + "mixnode_still_fully_bonded": { + "description": "The associated mixnode is still fully bonded, meaning it is neither unbonded nor in the process of unbonding that would have finished at the epoch transition.", + "type": "boolean" + } + }, + "additionalProperties": false, + "definitions": { + "Coin": { + "type": "object", + "required": [ + "amount", + "denom" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "denom": { + "type": "string" + } + } + }, + "Decimal": { + "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", + "type": "string" + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } + }, + "get_rewarded_set": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "PagedRewardedSetResponse", + "description": "Response containing paged list of all mixnodes in the rewarded set.", + "type": "object", + "required": [ + "nodes" + ], + "properties": { + "nodes": { + "description": "Nodes in the current rewarded set.", + "type": "array", + "items": { + "type": "array", + "items": [ + { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + { + "$ref": "#/definitions/RewardedSetNodeStatus" + } + ], + "maxItems": 2, + "minItems": 2 + } + }, + "start_next_after": { + "description": "Field indicating paging information for the following queries if the caller wishes to get further entries.", + "type": [ + "integer", + "null" + ], + "format": "uint32", + "minimum": 0.0 + } + }, + "additionalProperties": false, + "definitions": { + "RewardedSetNodeStatus": { + "description": "Current state of given node in the rewarded set.", + "oneOf": [ + { + "description": "Node that is currently active, i.e. is expected to be used by clients for mixing packets.", + "type": "string", + "enum": [ + "active" + ] + }, + { + "description": "Node that is currently in standby, i.e. it's present in the rewarded set but is not active.", + "type": "string", + "enum": [ + "standby" + ] + } + ] + } + } + }, + "get_rewarding_params": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "RewardingParams", + "description": "Parameters used for reward calculation.", + "type": "object", + "required": [ + "active_set_size", + "interval", + "rewarded_set_size" + ], + "properties": { + "active_set_size": { + "description": "The expected number of mixnodes in the active set.", + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "interval": { + "description": "Parameters that should remain unchanged throughout an interval.", + "allOf": [ + { + "$ref": "#/definitions/IntervalRewardParams" + } + ] + }, + "rewarded_set_size": { + "description": "The expected number of mixnodes in the rewarded set (i.e. active + standby).", + "type": "integer", + "format": "uint32", + "minimum": 0.0 + } + }, + "additionalProperties": false, + "definitions": { + "Decimal": { + "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", + "type": "string" + }, + "IntervalRewardParams": { + "description": "Parameters required by the mix-mining reward distribution that do not change during an interval.", + "type": "object", + "required": [ + "active_set_work_factor", + "epoch_reward_budget", + "interval_pool_emission", + "reward_pool", + "stake_saturation_point", + "staking_supply", + "staking_supply_scale_factor", + "sybil_resistance" + ], + "properties": { + "active_set_work_factor": { + "description": "Current active set work factor. It is not really expected to be changing very often. As a matter of fact, unless there's a very specific reason, it should remain constant.", + "allOf": [ + { + "$ref": "#/definitions/Decimal" + } + ] + }, + "epoch_reward_budget": { + "description": "Current value of the computed reward budget per epoch, per node. It is expected to be constant throughout the interval.", + "allOf": [ + { + "$ref": "#/definitions/Decimal" + } + ] + }, + "interval_pool_emission": { + "description": "Current maximum interval pool emission. Assuming all nodes in the rewarded set are fully saturated and have 100% performance, this % of the reward pool would get distributed in rewards to all operators and its delegators. It is not really expected to be changing very often. As a matter of fact, unless there's a very specific reason, it should remain constant.", + "allOf": [ + { + "$ref": "#/definitions/Percent" + } + ] + }, + "reward_pool": { + "description": "Current value of the rewarding pool. It is expected to be constant throughout the interval.", + "allOf": [ + { + "$ref": "#/definitions/Decimal" + } + ] + }, + "stake_saturation_point": { + "description": "Current value of the stake saturation point. It is expected to be constant throughout the interval.", + "allOf": [ + { + "$ref": "#/definitions/Decimal" + } + ] + }, + "staking_supply": { + "description": "Current value of the staking supply. It is expected to be constant throughout the interval.", + "allOf": [ + { + "$ref": "#/definitions/Decimal" + } + ] + }, + "staking_supply_scale_factor": { + "description": "Defines the percentage of stake needed to reach saturation for all of the nodes in the rewarded set. Also known as `beta`.", + "allOf": [ + { + "$ref": "#/definitions/Percent" + } + ] + }, + "sybil_resistance": { + "description": "Current value of the sybil resistance percent (`alpha`). It is not really expected to be changing very often. As a matter of fact, unless there's a very specific reason, it should remain constant.", + "allOf": [ + { + "$ref": "#/definitions/Percent" + } + ] + } + }, + "additionalProperties": false + }, + "Percent": { + "description": "Percent represents a value between 0 and 100% (i.e. between 0.0 and 1.0)", + "allOf": [ + { + "$ref": "#/definitions/Decimal" + } + ] + } + } + }, + "get_rewarding_validator_address": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "String", + "type": "string" + }, + "get_signing_nonce": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "uint32", + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "get_stake_saturation": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "StakeSaturationResponse", + "description": "Response containing the current state of the stake saturation of a mixnode with the provided id.", + "type": "object", + "required": [ + "mix_id" + ], + "properties": { + "current_saturation": { + "description": "The current stake saturation of this node that is indirectly used in reward calculation formulas. Note that it can't be larger than 1.", + "anyOf": [ + { + "$ref": "#/definitions/Decimal" + }, + { + "type": "null" + } + ] + }, + "mix_id": { + "description": "Id of the requested mixnode.", + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "uncapped_saturation": { + "description": "The current, absolute, stake saturation of this node. Note that as the name suggests it can be larger than 1. However, anything beyond that value has no effect on the total node reward.", + "anyOf": [ + { + "$ref": "#/definitions/Decimal" + }, + { + "type": "null" + } + ] + } + }, + "additionalProperties": false, + "definitions": { + "Decimal": { + "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", + "type": "string" + } + } + }, + "get_state": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ContractState", + "description": "The current state of the mixnet contract.", + "type": "object", + "required": [ + "owner", + "params", + "rewarding_denom", + "rewarding_validator_address", + "vesting_contract_address" + ], + "properties": { + "owner": { + "description": "Address of the contract owner.", + "allOf": [ + { + "$ref": "#/definitions/Addr" + } + ] + }, + "params": { + "description": "Contract parameters that could be adjusted in a transaction the contract admin.", + "allOf": [ + { + "$ref": "#/definitions/ContractStateParams" + } + ] + }, + "rewarding_denom": { + "description": "The expected denom used for rewarding (and realistically any other operation). Default: `unym`", + "type": "string" + }, + "rewarding_validator_address": { + "description": "Address of \"rewarding validator\" (nym-api) that's allowed to send any rewarding-related transactions.", + "allOf": [ + { + "$ref": "#/definitions/Addr" + } + ] + }, + "vesting_contract_address": { + "description": "Address of the vesting contract to which the mixnet contract would be sending all track-related messages.", + "allOf": [ + { + "$ref": "#/definitions/Addr" + } + ] + } + }, + "additionalProperties": false, + "definitions": { + "Addr": { + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + }, + "Coin": { + "type": "object", + "required": [ + "amount", + "denom" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "denom": { + "type": "string" + } + } + }, + "ContractStateParams": { + "description": "Contract parameters that could be adjusted in a transaction by the contract admin.", + "type": "object", + "required": [ + "minimum_gateway_pledge", + "minimum_mixnode_pledge" + ], + "properties": { + "minimum_gateway_pledge": { + "description": "Minimum amount a gateway must pledge to get into the system.", + "allOf": [ + { + "$ref": "#/definitions/Coin" + } + ] + }, + "minimum_mixnode_delegation": { + "description": "Minimum amount a delegator must stake in orders for his delegation to get accepted.", + "anyOf": [ + { + "$ref": "#/definitions/Coin" + }, + { + "type": "null" + } + ] + }, + "minimum_mixnode_pledge": { + "description": "Minimum amount a mixnode must pledge to get into the system.", + "allOf": [ + { + "$ref": "#/definitions/Coin" + } + ] + } + }, + "additionalProperties": false + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } + }, + "get_state_params": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ContractStateParams", + "description": "Contract parameters that could be adjusted in a transaction by the contract admin.", + "type": "object", + "required": [ + "minimum_gateway_pledge", + "minimum_mixnode_pledge" + ], + "properties": { + "minimum_gateway_pledge": { + "description": "Minimum amount a gateway must pledge to get into the system.", + "allOf": [ + { + "$ref": "#/definitions/Coin" + } + ] + }, + "minimum_mixnode_delegation": { + "description": "Minimum amount a delegator must stake in orders for his delegation to get accepted.", + "anyOf": [ + { + "$ref": "#/definitions/Coin" + }, + { + "type": "null" + } + ] + }, + "minimum_mixnode_pledge": { + "description": "Minimum amount a mixnode must pledge to get into the system.", + "allOf": [ + { + "$ref": "#/definitions/Coin" + } + ] + } + }, + "additionalProperties": false, + "definitions": { + "Coin": { + "type": "object", + "required": [ + "amount", + "denom" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "denom": { + "type": "string" + } + } + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } + }, + "get_unbonded_mix_node_information": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "UnbondedMixnodeResponse", + "description": "Response containing basic information of an unbonded mixnode with the provided id.", + "type": "object", + "required": [ + "mix_id" + ], + "properties": { + "mix_id": { + "description": "Id of the requested mixnode.", + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "unbonded_info": { + "description": "If there existed a mixnode with the provided id, this field contains its basic information.", + "anyOf": [ + { + "$ref": "#/definitions/UnbondedMixnode" + }, + { + "type": "null" + } + ] + } + }, + "additionalProperties": false, + "definitions": { + "Addr": { + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + }, + "UnbondedMixnode": { + "description": "Basic information of a node that used to be part of the mix network but has already unbonded.", + "type": "object", + "required": [ + "identity_key", + "owner", + "unbonding_height" + ], + "properties": { + "identity_key": { + "description": "Base58-encoded ed25519 EdDSA public key.", + "type": "string" + }, + "owner": { + "description": "Address of the owner of this mixnode.", + "allOf": [ + { + "$ref": "#/definitions/Addr" + } + ] + }, + "proxy": { + "description": "Entity who bonded this mixnode on behalf of the owner. If exists, it's most likely the address of the vesting contract.", + "anyOf": [ + { + "$ref": "#/definitions/Addr" + }, + { + "type": "null" + } + ] + }, + "unbonding_height": { + "description": "Block height at which this mixnode has unbonded.", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + } + }, + "get_unbonded_mix_nodes": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "PagedUnbondedMixnodesResponse", + "description": "Response containing paged list of all mixnodes that have ever unbonded.", + "type": "object", + "required": [ + "nodes", + "per_page" + ], + "properties": { + "nodes": { + "description": "The past ids of unbonded mixnodes alongside their basic information such as the owner or the identity key.", + "type": "array", + "items": { + "type": "array", + "items": [ + { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + { + "$ref": "#/definitions/UnbondedMixnode" + } + ], + "maxItems": 2, + "minItems": 2 + } + }, + "per_page": { + "description": "Maximum number of entries that could be included in a response. `per_page <= nodes.len()`", + "type": "integer", + "format": "uint", + "minimum": 0.0 + }, + "start_next_after": { + "description": "Field indicating paging information for the following queries if the caller wishes to get further entries.", + "type": [ + "integer", + "null" + ], + "format": "uint32", + "minimum": 0.0 + } + }, + "additionalProperties": false, + "definitions": { + "Addr": { + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + }, + "UnbondedMixnode": { + "description": "Basic information of a node that used to be part of the mix network but has already unbonded.", + "type": "object", + "required": [ + "identity_key", + "owner", + "unbonding_height" + ], + "properties": { + "identity_key": { + "description": "Base58-encoded ed25519 EdDSA public key.", + "type": "string" + }, + "owner": { + "description": "Address of the owner of this mixnode.", + "allOf": [ + { + "$ref": "#/definitions/Addr" + } + ] + }, + "proxy": { + "description": "Entity who bonded this mixnode on behalf of the owner. If exists, it's most likely the address of the vesting contract.", + "anyOf": [ + { + "$ref": "#/definitions/Addr" + }, + { + "type": "null" + } + ] + }, + "unbonding_height": { + "description": "Block height at which this mixnode has unbonded.", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + } + }, + "get_unbonded_mix_nodes_by_identity_key": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "PagedUnbondedMixnodesResponse", + "description": "Response containing paged list of all mixnodes that have ever unbonded.", + "type": "object", + "required": [ + "nodes", + "per_page" + ], + "properties": { + "nodes": { + "description": "The past ids of unbonded mixnodes alongside their basic information such as the owner or the identity key.", + "type": "array", + "items": { + "type": "array", + "items": [ + { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + { + "$ref": "#/definitions/UnbondedMixnode" + } + ], + "maxItems": 2, + "minItems": 2 + } + }, + "per_page": { + "description": "Maximum number of entries that could be included in a response. `per_page <= nodes.len()`", + "type": "integer", + "format": "uint", + "minimum": 0.0 + }, + "start_next_after": { + "description": "Field indicating paging information for the following queries if the caller wishes to get further entries.", + "type": [ + "integer", + "null" + ], + "format": "uint32", + "minimum": 0.0 + } + }, + "additionalProperties": false, + "definitions": { + "Addr": { + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + }, + "UnbondedMixnode": { + "description": "Basic information of a node that used to be part of the mix network but has already unbonded.", + "type": "object", + "required": [ + "identity_key", + "owner", + "unbonding_height" + ], + "properties": { + "identity_key": { + "description": "Base58-encoded ed25519 EdDSA public key.", + "type": "string" + }, + "owner": { + "description": "Address of the owner of this mixnode.", + "allOf": [ + { + "$ref": "#/definitions/Addr" + } + ] + }, + "proxy": { + "description": "Entity who bonded this mixnode on behalf of the owner. If exists, it's most likely the address of the vesting contract.", + "anyOf": [ + { + "$ref": "#/definitions/Addr" + }, + { + "type": "null" + } + ] + }, + "unbonding_height": { + "description": "Block height at which this mixnode has unbonded.", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + } + }, + "get_unbonded_mix_nodes_by_owner": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "PagedUnbondedMixnodesResponse", + "description": "Response containing paged list of all mixnodes that have ever unbonded.", + "type": "object", + "required": [ + "nodes", + "per_page" + ], + "properties": { + "nodes": { + "description": "The past ids of unbonded mixnodes alongside their basic information such as the owner or the identity key.", + "type": "array", + "items": { + "type": "array", + "items": [ + { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + { + "$ref": "#/definitions/UnbondedMixnode" + } + ], + "maxItems": 2, + "minItems": 2 + } + }, + "per_page": { + "description": "Maximum number of entries that could be included in a response. `per_page <= nodes.len()`", + "type": "integer", + "format": "uint", + "minimum": 0.0 + }, + "start_next_after": { + "description": "Field indicating paging information for the following queries if the caller wishes to get further entries.", + "type": [ + "integer", + "null" + ], + "format": "uint32", + "minimum": 0.0 + } + }, + "additionalProperties": false, + "definitions": { + "Addr": { + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + }, + "UnbondedMixnode": { + "description": "Basic information of a node that used to be part of the mix network but has already unbonded.", + "type": "object", + "required": [ + "identity_key", + "owner", + "unbonding_height" + ], + "properties": { + "identity_key": { + "description": "Base58-encoded ed25519 EdDSA public key.", + "type": "string" + }, + "owner": { + "description": "Address of the owner of this mixnode.", + "allOf": [ + { + "$ref": "#/definitions/Addr" + } + ] + }, + "proxy": { + "description": "Entity who bonded this mixnode on behalf of the owner. If exists, it's most likely the address of the vesting contract.", + "anyOf": [ + { + "$ref": "#/definitions/Addr" + }, + { + "type": "null" + } + ] + }, + "unbonding_height": { + "description": "Block height at which this mixnode has unbonded.", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + } + } + } +} diff --git a/contracts/mixnet/schema/execute_msg.json b/contracts/mixnet/schema/raw/execute.json similarity index 73% rename from contracts/mixnet/schema/execute_msg.json rename to contracts/mixnet/schema/raw/execute.json index b50d74a447..c4f1817fc9 100644 --- a/contracts/mixnet/schema/execute_msg.json +++ b/contracts/mixnet/schema/raw/execute.json @@ -23,7 +23,8 @@ "format": "uint32", "minimum": 0.0 } - } + }, + "additionalProperties": false } }, "additionalProperties": false @@ -38,17 +39,14 @@ "create_family": { "type": "object", "required": [ - "label", - "owner_signature" + "label" ], "properties": { "label": { "type": "string" - }, - "owner_signature": { - "type": "string" } - } + }, + "additionalProperties": false } }, "additionalProperties": false @@ -64,16 +62,17 @@ "type": "object", "required": [ "family_head", - "signature" + "join_permit" ], "properties": { "family_head": { - "type": "string" + "$ref": "#/definitions/FamilyHead" }, - "signature": { - "type": "string" + "join_permit": { + "$ref": "#/definitions/MessageSignature" } - } + }, + "additionalProperties": false } }, "additionalProperties": false @@ -87,17 +86,14 @@ "leave_family": { "type": "object", "required": [ - "family_head", - "signature" + "family_head" ], "properties": { "family_head": { - "type": "string" - }, - "signature": { - "type": "string" + "$ref": "#/definitions/FamilyHead" } - } + }, + "additionalProperties": false } }, "additionalProperties": false @@ -111,17 +107,14 @@ "kick_family_member": { "type": "object", "required": [ - "member", - "signature" + "member" ], "properties": { "member": { "type": "string" - }, - "signature": { - "type": "string" } - } + }, + "additionalProperties": false } }, "additionalProperties": false @@ -136,8 +129,7 @@ "type": "object", "required": [ "label", - "owner_address", - "owner_signature" + "owner_address" ], "properties": { "label": { @@ -145,17 +137,15 @@ }, "owner_address": { "type": "string" - }, - "owner_signature": { - "type": "string" } - } + }, + "additionalProperties": false } }, "additionalProperties": false }, { - "description": "Family head needs to sign the joining node IdentityKey", + "description": "Family head needs to sign the joining node IdentityKey, MixNode needs to provide its signature proving that it wants to join the family", "type": "object", "required": [ "join_family_on_behalf" @@ -165,20 +155,21 @@ "type": "object", "required": [ "family_head", - "member_address", - "signature" + "join_permit", + "member_address" ], "properties": { "family_head": { - "type": "string" + "$ref": "#/definitions/FamilyHead" + }, + "join_permit": { + "$ref": "#/definitions/MessageSignature" }, "member_address": { "type": "string" - }, - "signature": { - "type": "string" } - } + }, + "additionalProperties": false } }, "additionalProperties": false @@ -193,20 +184,17 @@ "type": "object", "required": [ "family_head", - "member_address", - "signature" + "member_address" ], "properties": { "family_head": { - "type": "string" + "$ref": "#/definitions/FamilyHead" }, "member_address": { "type": "string" - }, - "signature": { - "type": "string" } - } + }, + "additionalProperties": false } }, "additionalProperties": false @@ -221,8 +209,7 @@ "type": "object", "required": [ "head_address", - "member", - "signature" + "member" ], "properties": { "head_address": { @@ -230,11 +217,9 @@ }, "member": { "type": "string" - }, - "signature": { - "type": "string" } - } + }, + "additionalProperties": false } }, "additionalProperties": false @@ -254,7 +239,8 @@ "address": { "type": "string" } - } + }, + "additionalProperties": false } }, "additionalProperties": false @@ -274,7 +260,8 @@ "updated_parameters": { "$ref": "#/definitions/ContractStateParams" } - } + }, + "additionalProperties": false } }, "additionalProperties": false @@ -300,7 +287,8 @@ "force_immediately": { "type": "boolean" } - } + }, + "additionalProperties": false } }, "additionalProperties": false @@ -324,7 +312,8 @@ "updated_params": { "$ref": "#/definitions/IntervalRewardingParamsUpdate" } - } + }, + "additionalProperties": false } }, "additionalProperties": false @@ -356,7 +345,21 @@ "force_immediately": { "type": "boolean" } - } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "begin_epoch_transition" + ], + "properties": { + "begin_epoch_transition": { + "type": "object", + "additionalProperties": false } }, "additionalProperties": false @@ -385,7 +388,8 @@ "$ref": "#/definitions/LayerAssignment" } } - } + }, + "additionalProperties": false } }, "additionalProperties": false @@ -407,7 +411,8 @@ "format": "uint32", "minimum": 0.0 } - } + }, + "additionalProperties": false } }, "additionalProperties": false @@ -433,9 +438,10 @@ "$ref": "#/definitions/MixNode" }, "owner_signature": { - "type": "string" + "$ref": "#/definitions/MessageSignature" } - } + }, + "additionalProperties": false } }, "additionalProperties": false @@ -465,9 +471,10 @@ "type": "string" }, "owner_signature": { - "type": "string" + "$ref": "#/definitions/MessageSignature" } - } + }, + "additionalProperties": false } }, "additionalProperties": false @@ -479,7 +486,8 @@ ], "properties": { "pledge_more": { - "type": "object" + "type": "object", + "additionalProperties": false } }, "additionalProperties": false @@ -499,7 +507,54 @@ "owner": { "type": "string" } - } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "decrease_pledge" + ], + "properties": { + "decrease_pledge": { + "type": "object", + "required": [ + "decrease_by" + ], + "properties": { + "decrease_by": { + "$ref": "#/definitions/Coin" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "decrease_pledge_on_behalf" + ], + "properties": { + "decrease_pledge_on_behalf": { + "type": "object", + "required": [ + "decrease_by", + "owner" + ], + "properties": { + "decrease_by": { + "$ref": "#/definitions/Coin" + }, + "owner": { + "type": "string" + } + }, + "additionalProperties": false } }, "additionalProperties": false @@ -511,7 +566,8 @@ ], "properties": { "unbond_mixnode": { - "type": "object" + "type": "object", + "additionalProperties": false } }, "additionalProperties": false @@ -531,7 +587,8 @@ "owner": { "type": "string" } - } + }, + "additionalProperties": false } }, "additionalProperties": false @@ -551,7 +608,8 @@ "new_costs": { "$ref": "#/definitions/MixNodeCostParams" } - } + }, + "additionalProperties": false } }, "additionalProperties": false @@ -575,7 +633,8 @@ "owner": { "type": "string" } - } + }, + "additionalProperties": false } }, "additionalProperties": false @@ -595,7 +654,8 @@ "new_config": { "$ref": "#/definitions/MixNodeConfigUpdate" } - } + }, + "additionalProperties": false } }, "additionalProperties": false @@ -619,7 +679,8 @@ "owner": { "type": "string" } - } + }, + "additionalProperties": false } }, "additionalProperties": false @@ -641,9 +702,10 @@ "$ref": "#/definitions/Gateway" }, "owner_signature": { - "type": "string" + "$ref": "#/definitions/MessageSignature" } - } + }, + "additionalProperties": false } }, "additionalProperties": false @@ -669,9 +731,10 @@ "type": "string" }, "owner_signature": { - "type": "string" + "$ref": "#/definitions/MessageSignature" } - } + }, + "additionalProperties": false } }, "additionalProperties": false @@ -683,7 +746,8 @@ ], "properties": { "unbond_gateway": { - "type": "object" + "type": "object", + "additionalProperties": false } }, "additionalProperties": false @@ -703,7 +767,54 @@ "owner": { "type": "string" } - } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "update_gateway_config" + ], + "properties": { + "update_gateway_config": { + "type": "object", + "required": [ + "new_config" + ], + "properties": { + "new_config": { + "$ref": "#/definitions/GatewayConfigUpdate" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "update_gateway_config_on_behalf" + ], + "properties": { + "update_gateway_config_on_behalf": { + "type": "object", + "required": [ + "new_config", + "owner" + ], + "properties": { + "new_config": { + "$ref": "#/definitions/GatewayConfigUpdate" + }, + "owner": { + "type": "string" + } + }, + "additionalProperties": false } }, "additionalProperties": false @@ -725,7 +836,8 @@ "format": "uint32", "minimum": 0.0 } - } + }, + "additionalProperties": false } }, "additionalProperties": false @@ -751,7 +863,8 @@ "format": "uint32", "minimum": 0.0 } - } + }, + "additionalProperties": false } }, "additionalProperties": false @@ -773,7 +886,8 @@ "format": "uint32", "minimum": 0.0 } - } + }, + "additionalProperties": false } }, "additionalProperties": false @@ -799,7 +913,8 @@ "format": "uint32", "minimum": 0.0 } - } + }, + "additionalProperties": false } }, "additionalProperties": false @@ -825,7 +940,8 @@ "performance": { "$ref": "#/definitions/Percent" } - } + }, + "additionalProperties": false } }, "additionalProperties": false @@ -837,7 +953,8 @@ ], "properties": { "withdraw_operator_reward": { - "type": "object" + "type": "object", + "additionalProperties": false } }, "additionalProperties": false @@ -857,7 +974,8 @@ "owner": { "type": "string" } - } + }, + "additionalProperties": false } }, "additionalProperties": false @@ -879,7 +997,8 @@ "format": "uint32", "minimum": 0.0 } - } + }, + "additionalProperties": false } }, "additionalProperties": false @@ -905,7 +1024,8 @@ "owner": { "type": "string" } - } + }, + "additionalProperties": false } }, "additionalProperties": false @@ -928,6 +1048,7 @@ } }, "ContractStateParams": { + "description": "Contract parameters that could be adjusted in a transaction by the contract admin.", "type": "object", "required": [ "minimum_gateway_pledge", @@ -961,13 +1082,19 @@ } ] } - } + }, + "additionalProperties": false }, "Decimal": { "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", "type": "string" }, + "FamilyHead": { + "description": "Head of particular family as identified by its identity key (i.e. public component of its ed25519 keypair stringified into base58).", + "type": "string" + }, "Gateway": { + "description": "Information provided by the node operator during bonding that are used to allow other entities to use the services of this node.", "type": "object", "required": [ "clients_port", @@ -980,17 +1107,58 @@ ], "properties": { "clients_port": { + "description": "Port used by this gateway for listening for client requests.", "type": "integer", "format": "uint16", "minimum": 0.0 }, "host": { + "description": "Network address of this gateway, for example 1.1.1.1 or foo.gateway.com", "type": "string" }, "identity_key": { "description": "Base58 encoded ed25519 EdDSA public key of the gateway used to derive shared keys with clients", "type": "string" }, + "location": { + "description": "The physical, self-reported, location of this gateway.", + "type": "string" + }, + "mix_port": { + "description": "Port used by this gateway for listening for mix packets.", + "type": "integer", + "format": "uint16", + "minimum": 0.0 + }, + "sphinx_key": { + "description": "Base58-encoded x25519 public key used for sphinx key derivation.", + "type": "string" + }, + "version": { + "description": "The self-reported semver version of this gateway.", + "type": "string" + } + }, + "additionalProperties": false + }, + "GatewayConfigUpdate": { + "type": "object", + "required": [ + "clients_port", + "host", + "location", + "mix_port", + "version" + ], + "properties": { + "clients_port": { + "type": "integer", + "format": "uint16", + "minimum": 0.0 + }, + "host": { + "type": "string" + }, "location": { "type": "string" }, @@ -999,18 +1167,18 @@ "format": "uint16", "minimum": 0.0 }, - "sphinx_key": { - "type": "string" - }, "version": { "type": "string" } - } + }, + "additionalProperties": false }, "IntervalRewardingParamsUpdate": { + "description": "Specification on how the rewarding params should be updated.", "type": "object", "properties": { "active_set_work_factor": { + "description": "Defines the new value of the active set work factor.", "anyOf": [ { "$ref": "#/definitions/Decimal" @@ -1021,6 +1189,7 @@ ] }, "interval_pool_emission": { + "description": "Defines the new value of the interval pool emission rate.", "anyOf": [ { "$ref": "#/definitions/Percent" @@ -1031,6 +1200,7 @@ ] }, "reward_pool": { + "description": "Defines the new value of the reward pool.", "anyOf": [ { "$ref": "#/definitions/Decimal" @@ -1041,6 +1211,7 @@ ] }, "rewarded_set_size": { + "description": "Defines the new size of the rewarded set.", "type": [ "integer", "null" @@ -1049,6 +1220,7 @@ "minimum": 0.0 }, "staking_supply": { + "description": "Defines the new value of the staking supply.", "anyOf": [ { "$ref": "#/definitions/Decimal" @@ -1059,6 +1231,7 @@ ] }, "staking_supply_scale_factor": { + "description": "Defines the new value of the staking supply scale factor.", "anyOf": [ { "$ref": "#/definitions/Percent" @@ -1069,6 +1242,7 @@ ] }, "sybil_resistance_percent": { + "description": "Defines the new value of the sybil resistance percent.", "anyOf": [ { "$ref": "#/definitions/Percent" @@ -1078,7 +1252,8 @@ } ] } - } + }, + "additionalProperties": false }, "Layer": { "type": "string", @@ -1089,6 +1264,7 @@ ] }, "LayerAssignment": { + "description": "Specifies layer assignment for the given mixnode.", "type": "object", "required": [ "layer", @@ -1096,16 +1272,32 @@ ], "properties": { "layer": { - "$ref": "#/definitions/Layer" + "description": "The layer to which it's going to be assigned", + "allOf": [ + { + "$ref": "#/definitions/Layer" + } + ] }, "mix_id": { + "description": "The id of the mixnode.", "type": "integer", "format": "uint32", "minimum": 0.0 } + }, + "additionalProperties": false + }, + "MessageSignature": { + "type": "array", + "items": { + "type": "integer", + "format": "uint8", + "minimum": 0.0 } }, "MixNode": { + "description": "Information provided by the node operator during bonding that are used to allow other entities to use the services of this node.", "type": "object", "required": [ "host", @@ -1118,10 +1310,11 @@ ], "properties": { "host": { - "description": "Network address of this mixnode, for example 1.1.1.1:1234 or foo.mixnode.com", + "description": "Network address of this mixnode, for example 1.1.1.1 or foo.mixnode.com", "type": "string" }, "http_api_port": { + "description": "Port used by this mixnode for its http(s) API", "type": "integer", "format": "uint16", "minimum": 0.0 @@ -1131,6 +1324,7 @@ "type": "string" }, "mix_port": { + "description": "Port used by this mixnode for listening for mix packets.", "type": "integer", "format": "uint16", "minimum": 0.0 @@ -1140,14 +1334,17 @@ "type": "string" }, "verloc_port": { + "description": "Port used by this mixnode for listening for verloc requests.", "type": "integer", "format": "uint16", "minimum": 0.0 }, "version": { + "description": "The self-reported semver version of this mixnode.", "type": "string" } - } + }, + "additionalProperties": false }, "MixNodeConfigUpdate": { "type": "object", @@ -1180,9 +1377,11 @@ "version": { "type": "string" } - } + }, + "additionalProperties": false }, "MixNodeCostParams": { + "description": "The cost parameters, or the cost function, defined for the particular mixnode that influences how the rewards should be split between the node operator and its delegators.", "type": "object", "required": [ "interval_operating_cost", @@ -1198,9 +1397,15 @@ ] }, "profit_margin_percent": { - "$ref": "#/definitions/Percent" + "description": "The profit margin of the associated mixnode, i.e. the desired percent of the reward to be distributed to the operator.", + "allOf": [ + { + "$ref": "#/definitions/Percent" + } + ] } - } + }, + "additionalProperties": false }, "Percent": { "description": "Percent represents a value between 0 and 100% (i.e. between 0.0 and 1.0)", diff --git a/contracts/mixnet/schema/instantiate_msg.json b/contracts/mixnet/schema/raw/instantiate.json similarity index 97% rename from contracts/mixnet/schema/instantiate_msg.json rename to contracts/mixnet/schema/raw/instantiate.json index 6d7c0ea33a..60cf202f08 100644 --- a/contracts/mixnet/schema/instantiate_msg.json +++ b/contracts/mixnet/schema/raw/instantiate.json @@ -32,6 +32,7 @@ "type": "string" } }, + "additionalProperties": false, "definitions": { "Decimal": { "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", @@ -97,7 +98,8 @@ "sybil_resistance": { "$ref": "#/definitions/Percent" } - } + }, + "additionalProperties": false }, "Percent": { "description": "Percent represents a value between 0 and 100% (i.e. between 0.0 and 1.0)", diff --git a/contracts/mixnet/schema/migrate_msg.json b/contracts/mixnet/schema/raw/migrate.json similarity index 85% rename from contracts/mixnet/schema/migrate_msg.json rename to contracts/mixnet/schema/raw/migrate.json index d1975efa64..e9962aaf92 100644 --- a/contracts/mixnet/schema/migrate_msg.json +++ b/contracts/mixnet/schema/raw/migrate.json @@ -9,5 +9,6 @@ "null" ] } - } + }, + "additionalProperties": false } diff --git a/contracts/mixnet/schema/query_msg.json b/contracts/mixnet/schema/raw/query.json similarity index 56% rename from contracts/mixnet/schema/query_msg.json rename to contracts/mixnet/schema/raw/query.json index a8dde27e0a..f4738a91fe 100644 --- a/contracts/mixnet/schema/query_msg.json +++ b/contracts/mixnet/schema/raw/query.json @@ -3,6 +3,7 @@ "title": "QueryMsg", "oneOf": [ { + "description": "Gets the list of families registered in this contract.", "type": "object", "required": [ "get_all_families_paged" @@ -12,6 +13,7 @@ "type": "object", "properties": { "limit": { + "description": "Controls the maximum number of entries returned by the query. Note that too large values will be overwritten by a saner default.", "type": [ "integer", "null" @@ -20,17 +22,20 @@ "minimum": 0.0 }, "start_after": { + "description": "Pagination control for the values returned by the query. Note that the provided value itself will **not** be used for the response.", "type": [ "string", "null" ] } - } + }, + "additionalProperties": false } }, "additionalProperties": false }, { + "description": "Gets the list of all family members registered in this contract.", "type": "object", "required": [ "get_all_members_paged" @@ -40,6 +45,7 @@ "type": "object", "properties": { "limit": { + "description": "Controls the maximum number of entries returned by the query. Note that too large values will be overwritten by a saner default.", "type": [ "integer", "null" @@ -48,17 +54,20 @@ "minimum": 0.0 }, "start_after": { + "description": "Pagination control for the values returned by the query. Note that the provided value itself will **not** be used for the response.", "type": [ "string", "null" ] } - } + }, + "additionalProperties": false } }, "additionalProperties": false }, { + "description": "Attempts to lookup family information given the family head.", "type": "object", "required": [ "get_family_by_head" @@ -73,12 +82,14 @@ "head": { "type": "string" } - } + }, + "additionalProperties": false } }, "additionalProperties": false }, { + "description": "Attempts to lookup family information given the family label.", "type": "object", "required": [ "get_family_by_label" @@ -93,12 +104,14 @@ "label": { "type": "string" } - } + }, + "additionalProperties": false } }, "additionalProperties": false }, { + "description": "Attempts to retrieve family members given the family head.", "type": "object", "required": [ "get_family_members_by_head" @@ -113,12 +126,14 @@ "head": { "type": "string" } - } + }, + "additionalProperties": false } }, "additionalProperties": false }, { + "description": "Attempts to retrieve family members given the family label.", "type": "object", "required": [ "get_family_members_by_label" @@ -133,84 +148,126 @@ "label": { "type": "string" } - } + }, + "additionalProperties": false } }, "additionalProperties": false }, { + "description": "Gets build information of this contract, such as the commit hash used for the build or rustc version.", "type": "object", "required": [ "get_contract_version" ], "properties": { "get_contract_version": { - "type": "object" + "type": "object", + "additionalProperties": false } }, "additionalProperties": false }, { + "description": "Gets the stored contract version information that's required by the CW2 spec interface for migrations.", + "type": "object", + "required": [ + "get_cw2_contract_version" + ], + "properties": { + "get_cw2_contract_version": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Gets the address of the validator that's allowed to send rewarding transactions and transition the epoch.", "type": "object", "required": [ "get_rewarding_validator_address" ], "properties": { "get_rewarding_validator_address": { - "type": "object" + "type": "object", + "additionalProperties": false } }, "additionalProperties": false }, { + "description": "Gets the contract parameters that could be adjusted in a transaction by the contract admin.", "type": "object", "required": [ "get_state_params" ], "properties": { "get_state_params": { - "type": "object" + "type": "object", + "additionalProperties": false } }, "additionalProperties": false }, { + "description": "Gets the current state of the contract.", "type": "object", "required": [ "get_state" ], "properties": { "get_state": { - "type": "object" + "type": "object", + "additionalProperties": false } }, "additionalProperties": false }, { + "description": "Gets the current parameters used for reward calculation.", "type": "object", "required": [ "get_rewarding_params" ], "properties": { "get_rewarding_params": { - "type": "object" + "type": "object", + "additionalProperties": false } }, "additionalProperties": false }, { + "description": "Gets the status of the current rewarding epoch.", + "type": "object", + "required": [ + "get_epoch_status" + ], + "properties": { + "get_epoch_status": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Get the details of the current rewarding interval.", "type": "object", "required": [ "get_current_interval_details" ], "properties": { "get_current_interval_details": { - "type": "object" + "type": "object", + "additionalProperties": false } }, "additionalProperties": false }, { + "description": "Gets the current list of mixnodes in the rewarded set.", "type": "object", "required": [ "get_rewarded_set" @@ -220,6 +277,7 @@ "type": "object", "properties": { "limit": { + "description": "Controls the maximum number of entries returned by the query. Note that too large values will be overwritten by a saner default.", "type": [ "integer", "null" @@ -228,6 +286,7 @@ "minimum": 0.0 }, "start_after": { + "description": "Pagination control for the values returned by the query. Note that the provided value itself will **not** be used for the response.", "type": [ "integer", "null" @@ -235,12 +294,14 @@ "format": "uint32", "minimum": 0.0 } - } + }, + "additionalProperties": false } }, "additionalProperties": false }, { + "description": "Gets the basic list of all currently bonded mixnodes.", "type": "object", "required": [ "get_mix_node_bonds" @@ -250,6 +311,7 @@ "type": "object", "properties": { "limit": { + "description": "Controls the maximum number of entries returned by the query. Note that too large values will be overwritten by a saner default.", "type": [ "integer", "null" @@ -258,6 +320,7 @@ "minimum": 0.0 }, "start_after": { + "description": "Pagination control for the values returned by the query. Note that the provided value itself will **not** be used for the response.", "type": [ "integer", "null" @@ -265,12 +328,14 @@ "format": "uint32", "minimum": 0.0 } - } + }, + "additionalProperties": false } }, "additionalProperties": false }, { + "description": "Gets the detailed list of all currently bonded mixnodes.", "type": "object", "required": [ "get_mix_nodes_detailed" @@ -280,6 +345,7 @@ "type": "object", "properties": { "limit": { + "description": "Controls the maximum number of entries returned by the query. Note that too large values will be overwritten by a saner default.", "type": [ "integer", "null" @@ -288,6 +354,7 @@ "minimum": 0.0 }, "start_after": { + "description": "Pagination control for the values returned by the query. Note that the provided value itself will **not** be used for the response.", "type": [ "integer", "null" @@ -295,12 +362,14 @@ "format": "uint32", "minimum": 0.0 } - } + }, + "additionalProperties": false } }, "additionalProperties": false }, { + "description": "Gets the basic list of all unbonded mixnodes.", "type": "object", "required": [ "get_unbonded_mix_nodes" @@ -310,6 +379,7 @@ "type": "object", "properties": { "limit": { + "description": "Controls the maximum number of entries returned by the query. Note that too large values will be overwritten by a saner default.", "type": [ "integer", "null" @@ -318,6 +388,7 @@ "minimum": 0.0 }, "start_after": { + "description": "Pagination control for the values returned by the query. Note that the provided value itself will **not** be used for the response.", "type": [ "integer", "null" @@ -325,12 +396,14 @@ "format": "uint32", "minimum": 0.0 } - } + }, + "additionalProperties": false } }, "additionalProperties": false }, { + "description": "Gets the basic list of all unbonded mixnodes that belonged to a particular owner.", "type": "object", "required": [ "get_unbonded_mix_nodes_by_owner" @@ -343,6 +416,7 @@ ], "properties": { "limit": { + "description": "Controls the maximum number of entries returned by the query. Note that too large values will be overwritten by a saner default.", "type": [ "integer", "null" @@ -351,9 +425,11 @@ "minimum": 0.0 }, "owner": { + "description": "The address of the owner of the the mixnodes used for the query.", "type": "string" }, "start_after": { + "description": "Pagination control for the values returned by the query. Note that the provided value itself will **not** be used for the response.", "type": [ "integer", "null" @@ -361,12 +437,14 @@ "format": "uint32", "minimum": 0.0 } - } + }, + "additionalProperties": false } }, "additionalProperties": false }, { + "description": "Gets the basic list of all unbonded mixnodes that used the particular identity key.", "type": "object", "required": [ "get_unbonded_mix_nodes_by_identity_key" @@ -379,9 +457,11 @@ ], "properties": { "identity_key": { + "description": "The identity key (base58-encoded ed25519 public key) of the mixnode used for the query.", "type": "string" }, "limit": { + "description": "Controls the maximum number of entries returned by the query. Note that too large values will be overwritten by a saner default.", "type": [ "integer", "null" @@ -390,6 +470,7 @@ "minimum": 0.0 }, "start_after": { + "description": "Pagination control for the values returned by the query. Note that the provided value itself will **not** be used for the response.", "type": [ "integer", "null" @@ -397,12 +478,14 @@ "format": "uint32", "minimum": 0.0 } - } + }, + "additionalProperties": false } }, "additionalProperties": false }, { + "description": "Gets the detailed mixnode information belonging to the particular owner.", "type": "object", "required": [ "get_owned_mixnode" @@ -415,14 +498,17 @@ ], "properties": { "address": { + "description": "Address of the mixnode owner to use for the query.", "type": "string" } - } + }, + "additionalProperties": false } }, "additionalProperties": false }, { + "description": "Gets the detailed mixnode information of a node with the provided id.", "type": "object", "required": [ "get_mixnode_details" @@ -435,16 +521,19 @@ ], "properties": { "mix_id": { + "description": "Id of the node to query.", "type": "integer", "format": "uint32", "minimum": 0.0 } - } + }, + "additionalProperties": false } }, "additionalProperties": false }, { + "description": "Gets the rewarding information of a mixnode with the provided id.", "type": "object", "required": [ "get_mixnode_rewarding_details" @@ -457,16 +546,19 @@ ], "properties": { "mix_id": { + "description": "Id of the node to query.", "type": "integer", "format": "uint32", "minimum": 0.0 } - } + }, + "additionalProperties": false } }, "additionalProperties": false }, { + "description": "Gets the stake saturation of a mixnode with the provided id.", "type": "object", "required": [ "get_stake_saturation" @@ -479,16 +571,19 @@ ], "properties": { "mix_id": { + "description": "Id of the node to query.", "type": "integer", "format": "uint32", "minimum": 0.0 } - } + }, + "additionalProperties": false } }, "additionalProperties": false }, { + "description": "Gets the basic information of an unbonded mixnode with the provided id.", "type": "object", "required": [ "get_unbonded_mix_node_information" @@ -501,16 +596,19 @@ ], "properties": { "mix_id": { + "description": "Id of the node to query.", "type": "integer", "format": "uint32", "minimum": 0.0 } - } + }, + "additionalProperties": false } }, "additionalProperties": false }, { + "description": "Gets the detailed mixnode information of a node given its current identity key.", "type": "object", "required": [ "get_bonded_mixnode_details_by_identity" @@ -523,26 +621,31 @@ ], "properties": { "mix_identity": { + "description": "The identity key (base58-encoded ed25519 public key) of the mixnode used for the query.", "type": "string" } - } + }, + "additionalProperties": false } }, "additionalProperties": false }, { + "description": "Gets the current layer configuration of the mix network.", "type": "object", "required": [ "get_layer_distribution" ], "properties": { "get_layer_distribution": { - "type": "object" + "type": "object", + "additionalProperties": false } }, "additionalProperties": false }, { + "description": "Gets the basic list of all currently bonded gateways.", "type": "object", "required": [ "get_gateways" @@ -552,6 +655,7 @@ "type": "object", "properties": { "limit": { + "description": "Controls the maximum number of entries returned by the query. Note that too large values will be overwritten by a saner default.", "type": [ "integer", "null" @@ -560,17 +664,20 @@ "minimum": 0.0 }, "start_after": { + "description": "Pagination control for the values returned by the query. Note that the provided value itself will **not** be used for the response.", "type": [ "string", "null" ] } - } + }, + "additionalProperties": false } }, "additionalProperties": false }, { + "description": "Gets the gateway details of a node given its identity key.", "type": "object", "required": [ "get_gateway_bond" @@ -583,14 +690,17 @@ ], "properties": { "identity": { + "description": "The identity key (base58-encoded ed25519 public key) of the gateway used for the query.", "type": "string" } - } + }, + "additionalProperties": false } }, "additionalProperties": false }, { + "description": "Gets the detailed gateway information belonging to the particular owner.", "type": "object", "required": [ "get_owned_gateway" @@ -603,14 +713,17 @@ ], "properties": { "address": { + "description": "Address of the gateway owner to use for the query.", "type": "string" } - } + }, + "additionalProperties": false } }, "additionalProperties": false }, { + "description": "Gets all delegations associated with particular mixnode", "type": "object", "required": [ "get_mixnode_delegations" @@ -623,6 +736,7 @@ ], "properties": { "limit": { + "description": "Controls the maximum number of entries returned by the query. Note that too large values will be overwritten by a saner default.", "type": [ "integer", "null" @@ -631,22 +745,26 @@ "minimum": 0.0 }, "mix_id": { + "description": "Id of the node to query.", "type": "integer", "format": "uint32", "minimum": 0.0 }, "start_after": { + "description": "Pagination control for the values returned by the query. Note that the provided value itself will **not** be used for the response.", "type": [ "string", "null" ] } - } + }, + "additionalProperties": false } }, "additionalProperties": false }, { + "description": "Gets all delegations associated with particular delegator", "type": "object", "required": [ "get_delegator_delegations" @@ -659,9 +777,11 @@ ], "properties": { "delegator": { + "description": "The address of the owner of the delegations.", "type": "string" }, "limit": { + "description": "Controls the maximum number of entries returned by the query. Note that too large values will be overwritten by a saner default.", "type": [ "integer", "null" @@ -670,6 +790,7 @@ "minimum": 0.0 }, "start_after": { + "description": "Pagination control for the values returned by the query. Note that the provided value itself will **not** be used for the response.", "type": [ "array", "null" @@ -687,12 +808,14 @@ "maxItems": 2, "minItems": 2 } - } + }, + "additionalProperties": false } }, "additionalProperties": false }, { + "description": "Gets delegation information associated with particular mixnode - delegator pair", "type": "object", "required": [ "get_delegation_details" @@ -706,25 +829,30 @@ ], "properties": { "delegator": { + "description": "The address of the owner of the delegation.", "type": "string" }, "mix_id": { + "description": "Id of the node to query.", "type": "integer", "format": "uint32", "minimum": 0.0 }, "proxy": { + "description": "Entity who made the delegation on behalf of the owner. If present, it's most likely the address of the vesting contract.", "type": [ "string", "null" ] } - } + }, + "additionalProperties": false } }, "additionalProperties": false }, { + "description": "Gets all delegations in the system", "type": "object", "required": [ "get_all_delegations" @@ -734,6 +862,7 @@ "type": "object", "properties": { "limit": { + "description": "Controls the maximum number of entries returned by the query. Note that too large values will be overwritten by a saner default.", "type": [ "integer", "null" @@ -742,6 +871,7 @@ "minimum": 0.0 }, "start_after": { + "description": "Pagination control for the values returned by the query. Note that the provided value itself will **not** be used for the response.", "type": [ "array", "null" @@ -759,12 +889,14 @@ "maxItems": 2, "minItems": 2 } - } + }, + "additionalProperties": false } }, "additionalProperties": false }, { + "description": "Gets the reward amount accrued by the node operator that has not yet been claimed.", "type": "object", "required": [ "get_pending_operator_reward" @@ -777,14 +909,17 @@ ], "properties": { "address": { + "description": "Address of the operator to use for the query.", "type": "string" } - } + }, + "additionalProperties": false } }, "additionalProperties": false }, { + "description": "Gets the reward amount accrued by the particular mixnode that has not yet been claimed.", "type": "object", "required": [ "get_pending_mix_node_operator_reward" @@ -797,16 +932,19 @@ ], "properties": { "mix_id": { + "description": "Id of the node to query.", "type": "integer", "format": "uint32", "minimum": 0.0 } - } + }, + "additionalProperties": false } }, "additionalProperties": false }, { + "description": "Gets the reward amount accrued by the particular delegator that has not yet been claimed.", "type": "object", "required": [ "get_pending_delegator_reward" @@ -820,25 +958,30 @@ ], "properties": { "address": { + "description": "Address of the delegator to use for the query.", "type": "string" }, "mix_id": { + "description": "Id of the node to query.", "type": "integer", "format": "uint32", "minimum": 0.0 }, "proxy": { + "description": "Entity who made the delegation on behalf of the owner. If present, it's most likely the address of the vesting contract.", "type": [ "string", "null" ] } - } + }, + "additionalProperties": false } }, "additionalProperties": false }, { + "description": "Given the provided node performance, attempt to estimate the operator reward for the current epoch.", "type": "object", "required": [ "get_estimated_current_epoch_operator_reward" @@ -852,19 +995,27 @@ ], "properties": { "estimated_performance": { - "$ref": "#/definitions/Percent" + "description": "The estimated performance for the current epoch of the given node.", + "allOf": [ + { + "$ref": "#/definitions/Percent" + } + ] }, "mix_id": { + "description": "Id of the node to query.", "type": "integer", "format": "uint32", "minimum": 0.0 } - } + }, + "additionalProperties": false } }, "additionalProperties": false }, { + "description": "Given the provided node performance, attempt to estimate the delegator reward for the current epoch.", "type": "object", "required": [ "get_estimated_current_epoch_delegator_reward" @@ -879,28 +1030,38 @@ ], "properties": { "address": { + "description": "Address of the delegator to use for the query.", "type": "string" }, "estimated_performance": { - "$ref": "#/definitions/Percent" + "description": "The estimated performance for the current epoch of the given node.", + "allOf": [ + { + "$ref": "#/definitions/Percent" + } + ] }, "mix_id": { + "description": "Id of the node to query.", "type": "integer", "format": "uint32", "minimum": 0.0 }, "proxy": { + "description": "Entity who made the delegation on behalf of the owner. If present, it's most likely the address of the vesting contract.", "type": [ "string", "null" ] } - } + }, + "additionalProperties": false } }, "additionalProperties": false }, { + "description": "Gets the list of all currently pending epoch events that will be resolved once the current epoch finishes.", "type": "object", "required": [ "get_pending_epoch_events" @@ -910,6 +1071,7 @@ "type": "object", "properties": { "limit": { + "description": "Controls the maximum number of entries returned by the query. Note that too large values will be overwritten by a saner default.", "type": [ "integer", "null" @@ -918,6 +1080,7 @@ "minimum": 0.0 }, "start_after": { + "description": "Pagination control for the values returned by the query. Note that the provided value itself will **not** be used for the response.", "type": [ "integer", "null" @@ -925,12 +1088,14 @@ "format": "uint32", "minimum": 0.0 } - } + }, + "additionalProperties": false } }, "additionalProperties": false }, { + "description": "Gets the list of all currently pending interval events that will be resolved once the current interval finishes.", "type": "object", "required": [ "get_pending_interval_events" @@ -940,6 +1105,7 @@ "type": "object", "properties": { "limit": { + "description": "Controls the maximum number of entries returned by the query. Note that too large values will be overwritten by a saner default.", "type": [ "integer", "null" @@ -948,6 +1114,7 @@ "minimum": 0.0 }, "start_after": { + "description": "Pagination control for the values returned by the query. Note that the provided value itself will **not** be used for the response.", "type": [ "integer", "null" @@ -955,7 +1122,95 @@ "format": "uint32", "minimum": 0.0 } - } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Gets detailed information about a pending epoch event given its id.", + "type": "object", + "required": [ + "get_pending_epoch_event" + ], + "properties": { + "get_pending_epoch_event": { + "type": "object", + "required": [ + "event_id" + ], + "properties": { + "event_id": { + "description": "The unique id associated with the event.", + "type": "integer", + "format": "uint32", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Gets detailed information about a pending interval event given its id.", + "type": "object", + "required": [ + "get_pending_interval_event" + ], + "properties": { + "get_pending_interval_event": { + "type": "object", + "required": [ + "event_id" + ], + "properties": { + "event_id": { + "description": "The unique id associated with the event.", + "type": "integer", + "format": "uint32", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Gets the information about the number of currently pending epoch and interval events.", + "type": "object", + "required": [ + "get_number_of_pending_events" + ], + "properties": { + "get_number_of_pending_events": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Gets the signing nonce associated with the particular cosmos address.", + "type": "object", + "required": [ + "get_signing_nonce" + ], + "properties": { + "get_signing_nonce": { + "type": "object", + "required": [ + "address" + ], + "properties": { + "address": { + "description": "Cosmos address used for the query of the signing nonce.", + "type": "string" + } + }, + "additionalProperties": false } }, "additionalProperties": false diff --git a/contracts/mixnet/schema/raw/response_to_get_all_delegations.json b/contracts/mixnet/schema/raw/response_to_get_all_delegations.json new file mode 100644 index 0000000000..a919220c46 --- /dev/null +++ b/contracts/mixnet/schema/raw/response_to_get_all_delegations.json @@ -0,0 +1,128 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "PagedAllDelegationsResponse", + "description": "Response containing paged list of all delegations currently active.", + "type": "object", + "required": [ + "delegations" + ], + "properties": { + "delegations": { + "description": "Each individual delegation made.", + "type": "array", + "items": { + "$ref": "#/definitions/Delegation" + } + }, + "start_next_after": { + "description": "Field indicating paging information for the following queries if the caller wishes to get further entries.", + "type": [ + "array", + "null" + ], + "items": [ + { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + { + "type": "string" + } + ], + "maxItems": 2, + "minItems": 2 + } + }, + "additionalProperties": false, + "definitions": { + "Addr": { + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + }, + "Coin": { + "type": "object", + "required": [ + "amount", + "denom" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "denom": { + "type": "string" + } + } + }, + "Decimal": { + "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", + "type": "string" + }, + "Delegation": { + "description": "Information about tokens being delegated towards given mixnode in order to accrue rewards with their work.", + "type": "object", + "required": [ + "amount", + "cumulative_reward_ratio", + "height", + "mix_id", + "owner" + ], + "properties": { + "amount": { + "description": "Original delegation amount. Note that it is never mutated as delegation accumulates rewards.", + "allOf": [ + { + "$ref": "#/definitions/Coin" + } + ] + }, + "cumulative_reward_ratio": { + "description": "Value of the \"unit delegation\" associated with the mixnode at the time of delegation.", + "allOf": [ + { + "$ref": "#/definitions/Decimal" + } + ] + }, + "height": { + "description": "Block height where this delegation occurred.", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "mix_id": { + "description": "Id of the MixNode that this delegation was performed against.", + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "owner": { + "description": "Address of the owner of this delegation.", + "allOf": [ + { + "$ref": "#/definitions/Addr" + } + ] + }, + "proxy": { + "description": "Proxy address used to delegate the funds on behalf of another address", + "anyOf": [ + { + "$ref": "#/definitions/Addr" + }, + { + "type": "null" + } + ] + } + }, + "additionalProperties": false + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } +} diff --git a/contracts/mixnet/schema/raw/response_to_get_all_families_paged.json b/contracts/mixnet/schema/raw/response_to_get_all_families_paged.json new file mode 100644 index 0000000000..6744e15be6 --- /dev/null +++ b/contracts/mixnet/schema/raw/response_to_get_all_families_paged.json @@ -0,0 +1,62 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "PagedFamiliesResponse", + "description": "Response containing paged list of all families registered in the contract.", + "type": "object", + "required": [ + "families" + ], + "properties": { + "families": { + "description": "The families registered in the contract.", + "type": "array", + "items": { + "$ref": "#/definitions/Family" + } + }, + "start_next_after": { + "description": "Field indicating paging information for the following queries if the caller wishes to get further entries.", + "type": [ + "string", + "null" + ] + } + }, + "additionalProperties": false, + "definitions": { + "Family": { + "description": "A group of mixnodes associated with particular staking entity. When defined all nodes belonging to the same family will be prioritised to be put onto the same layer.", + "type": "object", + "required": [ + "head", + "label" + ], + "properties": { + "head": { + "description": "Owner of this family.", + "allOf": [ + { + "$ref": "#/definitions/FamilyHead" + } + ] + }, + "label": { + "description": "Human readable label for this family.", + "type": "string" + }, + "proxy": { + "description": "Optional proxy (i.e. vesting contract address) used when creating the family.", + "type": [ + "string", + "null" + ] + } + }, + "additionalProperties": false + }, + "FamilyHead": { + "description": "Head of particular family as identified by its identity key (i.e. public component of its ed25519 keypair stringified into base58).", + "type": "string" + } + } +} diff --git a/contracts/mixnet/schema/raw/response_to_get_all_members_paged.json b/contracts/mixnet/schema/raw/response_to_get_all_members_paged.json new file mode 100644 index 0000000000..69da663f92 --- /dev/null +++ b/contracts/mixnet/schema/raw/response_to_get_all_members_paged.json @@ -0,0 +1,42 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "PagedMembersResponse", + "description": "Response containing paged list of all family members (of ALL families) registered in the contract.", + "type": "object", + "required": [ + "members" + ], + "properties": { + "members": { + "description": "The members alongside their family heads.", + "type": "array", + "items": { + "type": "array", + "items": [ + { + "type": "string" + }, + { + "$ref": "#/definitions/FamilyHead" + } + ], + "maxItems": 2, + "minItems": 2 + } + }, + "start_next_after": { + "description": "Field indicating paging information for the following queries if the caller wishes to get further entries.", + "type": [ + "string", + "null" + ] + } + }, + "additionalProperties": false, + "definitions": { + "FamilyHead": { + "description": "Head of particular family as identified by its identity key (i.e. public component of its ed25519 keypair stringified into base58).", + "type": "string" + } + } +} diff --git a/contracts/mixnet/schema/raw/response_to_get_bonded_mixnode_details_by_identity.json b/contracts/mixnet/schema/raw/response_to_get_bonded_mixnode_details_by_identity.json new file mode 100644 index 0000000000..59fc029331 --- /dev/null +++ b/contracts/mixnet/schema/raw/response_to_get_bonded_mixnode_details_by_identity.json @@ -0,0 +1,342 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "MixnodeDetailsByIdentityResponse", + "description": "Response containing details of a bonded mixnode with the provided identity key.", + "type": "object", + "required": [ + "identity_key" + ], + "properties": { + "identity_key": { + "description": "The identity key (base58-encoded ed25519 public key) of the mixnode.", + "type": "string" + }, + "mixnode_details": { + "description": "If there exists a bonded mixnode with the provided identity key, this field contains its detailed information.", + "anyOf": [ + { + "$ref": "#/definitions/MixNodeDetails" + }, + { + "type": "null" + } + ] + } + }, + "additionalProperties": false, + "definitions": { + "Addr": { + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + }, + "Coin": { + "type": "object", + "required": [ + "amount", + "denom" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "denom": { + "type": "string" + } + } + }, + "Decimal": { + "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", + "type": "string" + }, + "Layer": { + "type": "string", + "enum": [ + "One", + "Two", + "Three" + ] + }, + "MixNode": { + "description": "Information provided by the node operator during bonding that are used to allow other entities to use the services of this node.", + "type": "object", + "required": [ + "host", + "http_api_port", + "identity_key", + "mix_port", + "sphinx_key", + "verloc_port", + "version" + ], + "properties": { + "host": { + "description": "Network address of this mixnode, for example 1.1.1.1 or foo.mixnode.com", + "type": "string" + }, + "http_api_port": { + "description": "Port used by this mixnode for its http(s) API", + "type": "integer", + "format": "uint16", + "minimum": 0.0 + }, + "identity_key": { + "description": "Base58-encoded ed25519 EdDSA public key.", + "type": "string" + }, + "mix_port": { + "description": "Port used by this mixnode for listening for mix packets.", + "type": "integer", + "format": "uint16", + "minimum": 0.0 + }, + "sphinx_key": { + "description": "Base58-encoded x25519 public key used for sphinx key derivation.", + "type": "string" + }, + "verloc_port": { + "description": "Port used by this mixnode for listening for verloc requests.", + "type": "integer", + "format": "uint16", + "minimum": 0.0 + }, + "version": { + "description": "The self-reported semver version of this mixnode.", + "type": "string" + } + }, + "additionalProperties": false + }, + "MixNodeBond": { + "description": "Basic mixnode information provided by the node operator.", + "type": "object", + "required": [ + "bonding_height", + "is_unbonding", + "layer", + "mix_id", + "mix_node", + "original_pledge", + "owner" + ], + "properties": { + "bonding_height": { + "description": "Block height at which this mixnode has been bonded.", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "is_unbonding": { + "description": "Flag to indicate whether this node is in the process of unbonding, that will conclude upon the epoch finishing.", + "type": "boolean" + }, + "layer": { + "description": "Layer assigned to this mixnode.", + "allOf": [ + { + "$ref": "#/definitions/Layer" + } + ] + }, + "mix_id": { + "description": "Unique id assigned to the bonded mixnode.", + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "mix_node": { + "description": "Information provided by the operator for the purposes of bonding.", + "allOf": [ + { + "$ref": "#/definitions/MixNode" + } + ] + }, + "original_pledge": { + "description": "Original amount pledged by the operator of this node.", + "allOf": [ + { + "$ref": "#/definitions/Coin" + } + ] + }, + "owner": { + "description": "Address of the owner of this mixnode.", + "allOf": [ + { + "$ref": "#/definitions/Addr" + } + ] + }, + "proxy": { + "description": "Entity who bonded this mixnode on behalf of the owner. If exists, it's most likely the address of the vesting contract.", + "anyOf": [ + { + "$ref": "#/definitions/Addr" + }, + { + "type": "null" + } + ] + } + }, + "additionalProperties": false + }, + "MixNodeCostParams": { + "description": "The cost parameters, or the cost function, defined for the particular mixnode that influences how the rewards should be split between the node operator and its delegators.", + "type": "object", + "required": [ + "interval_operating_cost", + "profit_margin_percent" + ], + "properties": { + "interval_operating_cost": { + "description": "Operating cost of the associated mixnode per the entire interval.", + "allOf": [ + { + "$ref": "#/definitions/Coin" + } + ] + }, + "profit_margin_percent": { + "description": "The profit margin of the associated mixnode, i.e. the desired percent of the reward to be distributed to the operator.", + "allOf": [ + { + "$ref": "#/definitions/Percent" + } + ] + } + }, + "additionalProperties": false + }, + "MixNodeDetails": { + "description": "Full details associated with given mixnode.", + "type": "object", + "required": [ + "bond_information", + "rewarding_details" + ], + "properties": { + "bond_information": { + "description": "Basic bond information of this mixnode, such as owner address, original pledge, etc.", + "allOf": [ + { + "$ref": "#/definitions/MixNodeBond" + } + ] + }, + "pending_changes": { + "description": "Adjustments to the mixnode that are ought to happen during future epoch transitions.", + "default": { + "pledge_change": null + }, + "allOf": [ + { + "$ref": "#/definitions/PendingMixNodeChanges" + } + ] + }, + "rewarding_details": { + "description": "Details used for computation of rewarding related data.", + "allOf": [ + { + "$ref": "#/definitions/MixNodeRewarding" + } + ] + } + }, + "additionalProperties": false + }, + "MixNodeRewarding": { + "type": "object", + "required": [ + "cost_params", + "delegates", + "last_rewarded_epoch", + "operator", + "total_unit_reward", + "unique_delegations", + "unit_delegation" + ], + "properties": { + "cost_params": { + "description": "Information provided by the operator that influence the cost function.", + "allOf": [ + { + "$ref": "#/definitions/MixNodeCostParams" + } + ] + }, + "delegates": { + "description": "Total delegation and compounded reward earned by all node delegators.", + "allOf": [ + { + "$ref": "#/definitions/Decimal" + } + ] + }, + "last_rewarded_epoch": { + "description": "Marks the epoch when this node was last rewarded so that we wouldn't accidentally attempt to reward it multiple times in the same epoch.", + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "operator": { + "description": "Total pledge and compounded reward earned by the node operator.", + "allOf": [ + { + "$ref": "#/definitions/Decimal" + } + ] + }, + "total_unit_reward": { + "description": "Cumulative reward earned by the \"unit delegation\" since the block 0.", + "allOf": [ + { + "$ref": "#/definitions/Decimal" + } + ] + }, + "unique_delegations": { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "unit_delegation": { + "description": "Value of the theoretical \"unit delegation\" that has delegated to this mixnode at block 0.", + "allOf": [ + { + "$ref": "#/definitions/Decimal" + } + ] + } + }, + "additionalProperties": false + }, + "PendingMixNodeChanges": { + "type": "object", + "properties": { + "pledge_change": { + "type": [ + "integer", + "null" + ], + "format": "uint32", + "minimum": 0.0 + } + }, + "additionalProperties": false + }, + "Percent": { + "description": "Percent represents a value between 0 and 100% (i.e. between 0.0 and 1.0)", + "allOf": [ + { + "$ref": "#/definitions/Decimal" + } + ] + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } +} diff --git a/contracts/mixnet/schema/raw/response_to_get_c_w2_contract_version.json b/contracts/mixnet/schema/raw/response_to_get_c_w2_contract_version.json new file mode 100644 index 0000000000..3ded8c84d9 --- /dev/null +++ b/contracts/mixnet/schema/raw/response_to_get_c_w2_contract_version.json @@ -0,0 +1,20 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ContractVersion", + "type": "object", + "required": [ + "contract", + "version" + ], + "properties": { + "contract": { + "description": "contract is the crate name of the implementing contract, eg. `crate:cw20-base` we will use other prefixes for other languages, and their standard global namespacing", + "type": "string" + }, + "version": { + "description": "version is any string that this implementation knows. It may be simple counter \"1\", \"2\". or semantic version on release tags \"v0.7.0\", or some custom feature flag list. the only code that needs to understand the version parsing is code that knows how to migrate from the given contract (and is tied to it's implementation somehow)", + "type": "string" + } + }, + "additionalProperties": false +} diff --git a/contracts/mixnet/schema/raw/response_to_get_contract_version.json b/contracts/mixnet/schema/raw/response_to_get_contract_version.json new file mode 100644 index 0000000000..60ae65f3d1 --- /dev/null +++ b/contracts/mixnet/schema/raw/response_to_get_contract_version.json @@ -0,0 +1,40 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ContractBuildInformation", + "type": "object", + "required": [ + "build_timestamp", + "build_version", + "commit_branch", + "commit_sha", + "commit_timestamp", + "rustc_version" + ], + "properties": { + "build_timestamp": { + "description": "Provides the build timestamp, for example `2021-02-23T20:14:46.558472672+00:00`.", + "type": "string" + }, + "build_version": { + "description": "Provides the build version, for example `0.1.0-9-g46f83e1`.", + "type": "string" + }, + "commit_branch": { + "description": "Provides the name of the git branch that was used for the build, for example `master`.", + "type": "string" + }, + "commit_sha": { + "description": "Provides the hash of the commit that was used for the build, for example `46f83e112520533338245862d366f6a02cef07d4`.", + "type": "string" + }, + "commit_timestamp": { + "description": "Provides the timestamp of the commit that was used for the build, for example `2021-02-23T08:08:02-05:00`.", + "type": "string" + }, + "rustc_version": { + "description": "Provides the rustc version that was used for the build, for example `1.52.0-nightly`.", + "type": "string" + } + }, + "additionalProperties": false +} diff --git a/contracts/mixnet/schema/raw/response_to_get_current_interval_details.json b/contracts/mixnet/schema/raw/response_to_get_current_interval_details.json new file mode 100644 index 0000000000..1a90ceb1eb --- /dev/null +++ b/contracts/mixnet/schema/raw/response_to_get_current_interval_details.json @@ -0,0 +1,97 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "CurrentIntervalResponse", + "description": "Information about the current rewarding interval.", + "type": "object", + "required": [ + "current_blocktime", + "interval", + "is_current_epoch_over", + "is_current_interval_over" + ], + "properties": { + "current_blocktime": { + "description": "The current blocktime", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "interval": { + "description": "Detailed information about the underlying interval.", + "allOf": [ + { + "$ref": "#/definitions/Interval" + } + ] + }, + "is_current_epoch_over": { + "description": "Flag indicating whether the current epoch is over and it should be advanced.", + "type": "boolean" + }, + "is_current_interval_over": { + "description": "Flag indicating whether the current interval is over and it should be advanced.", + "type": "boolean" + } + }, + "additionalProperties": false, + "definitions": { + "Duration": { + "type": "object", + "required": [ + "nanos", + "secs" + ], + "properties": { + "nanos": { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "secs": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + } + }, + "Interval": { + "type": "object", + "required": [ + "current_epoch_id", + "current_epoch_start", + "epoch_length", + "epochs_in_interval", + "id", + "total_elapsed_epochs" + ], + "properties": { + "current_epoch_id": { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "current_epoch_start": { + "type": "string" + }, + "epoch_length": { + "$ref": "#/definitions/Duration" + }, + "epochs_in_interval": { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "id": { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "total_elapsed_epochs": { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + } + } + } + } +} diff --git a/contracts/mixnet/schema/raw/response_to_get_delegation_details.json b/contracts/mixnet/schema/raw/response_to_get_delegation_details.json new file mode 100644 index 0000000000..8371493b30 --- /dev/null +++ b/contracts/mixnet/schema/raw/response_to_get_delegation_details.json @@ -0,0 +1,117 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "MixNodeDelegationResponse", + "description": "Response containing delegation details.", + "type": "object", + "required": [ + "mixnode_still_bonded" + ], + "properties": { + "delegation": { + "description": "If the delegation exists, this field contains its detailed information.", + "anyOf": [ + { + "$ref": "#/definitions/Delegation" + }, + { + "type": "null" + } + ] + }, + "mixnode_still_bonded": { + "description": "Flag indicating whether the node towards which the delegation was made is still bonded in the network.", + "type": "boolean" + } + }, + "additionalProperties": false, + "definitions": { + "Addr": { + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + }, + "Coin": { + "type": "object", + "required": [ + "amount", + "denom" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "denom": { + "type": "string" + } + } + }, + "Decimal": { + "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", + "type": "string" + }, + "Delegation": { + "description": "Information about tokens being delegated towards given mixnode in order to accrue rewards with their work.", + "type": "object", + "required": [ + "amount", + "cumulative_reward_ratio", + "height", + "mix_id", + "owner" + ], + "properties": { + "amount": { + "description": "Original delegation amount. Note that it is never mutated as delegation accumulates rewards.", + "allOf": [ + { + "$ref": "#/definitions/Coin" + } + ] + }, + "cumulative_reward_ratio": { + "description": "Value of the \"unit delegation\" associated with the mixnode at the time of delegation.", + "allOf": [ + { + "$ref": "#/definitions/Decimal" + } + ] + }, + "height": { + "description": "Block height where this delegation occurred.", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "mix_id": { + "description": "Id of the MixNode that this delegation was performed against.", + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "owner": { + "description": "Address of the owner of this delegation.", + "allOf": [ + { + "$ref": "#/definitions/Addr" + } + ] + }, + "proxy": { + "description": "Proxy address used to delegate the funds on behalf of another address", + "anyOf": [ + { + "$ref": "#/definitions/Addr" + }, + { + "type": "null" + } + ] + } + }, + "additionalProperties": false + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } +} diff --git a/contracts/mixnet/schema/raw/response_to_get_delegator_delegations.json b/contracts/mixnet/schema/raw/response_to_get_delegator_delegations.json new file mode 100644 index 0000000000..b6e7e7a6ee --- /dev/null +++ b/contracts/mixnet/schema/raw/response_to_get_delegator_delegations.json @@ -0,0 +1,128 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "PagedDelegatorDelegationsResponse", + "description": "Response containing paged list of all delegations made by the particular address.", + "type": "object", + "required": [ + "delegations" + ], + "properties": { + "delegations": { + "description": "Each individual delegation made.", + "type": "array", + "items": { + "$ref": "#/definitions/Delegation" + } + }, + "start_next_after": { + "description": "Field indicating paging information for the following queries if the caller wishes to get further entries.", + "type": [ + "array", + "null" + ], + "items": [ + { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + { + "type": "string" + } + ], + "maxItems": 2, + "minItems": 2 + } + }, + "additionalProperties": false, + "definitions": { + "Addr": { + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + }, + "Coin": { + "type": "object", + "required": [ + "amount", + "denom" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "denom": { + "type": "string" + } + } + }, + "Decimal": { + "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", + "type": "string" + }, + "Delegation": { + "description": "Information about tokens being delegated towards given mixnode in order to accrue rewards with their work.", + "type": "object", + "required": [ + "amount", + "cumulative_reward_ratio", + "height", + "mix_id", + "owner" + ], + "properties": { + "amount": { + "description": "Original delegation amount. Note that it is never mutated as delegation accumulates rewards.", + "allOf": [ + { + "$ref": "#/definitions/Coin" + } + ] + }, + "cumulative_reward_ratio": { + "description": "Value of the \"unit delegation\" associated with the mixnode at the time of delegation.", + "allOf": [ + { + "$ref": "#/definitions/Decimal" + } + ] + }, + "height": { + "description": "Block height where this delegation occurred.", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "mix_id": { + "description": "Id of the MixNode that this delegation was performed against.", + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "owner": { + "description": "Address of the owner of this delegation.", + "allOf": [ + { + "$ref": "#/definitions/Addr" + } + ] + }, + "proxy": { + "description": "Proxy address used to delegate the funds on behalf of another address", + "anyOf": [ + { + "$ref": "#/definitions/Addr" + }, + { + "type": "null" + } + ] + } + }, + "additionalProperties": false + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } +} diff --git a/contracts/mixnet/schema/raw/response_to_get_epoch_status.json b/contracts/mixnet/schema/raw/response_to_get_epoch_status.json new file mode 100644 index 0000000000..1a85e633bf --- /dev/null +++ b/contracts/mixnet/schema/raw/response_to_get_epoch_status.json @@ -0,0 +1,93 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "EpochStatus", + "description": "The status of the current rewarding epoch.", + "type": "object", + "required": [ + "being_advanced_by", + "state" + ], + "properties": { + "being_advanced_by": { + "description": "Specifies either, which validator is currently performing progression into the following epoch (if the epoch is currently being progressed), or which validator was responsible for progressing into the current epoch (if the epoch is currently in progress)", + "allOf": [ + { + "$ref": "#/definitions/Addr" + } + ] + }, + "state": { + "description": "The concrete state of the epoch.", + "allOf": [ + { + "$ref": "#/definitions/EpochState" + } + ] + } + }, + "additionalProperties": false, + "definitions": { + "Addr": { + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + }, + "EpochState": { + "description": "The state of the current rewarding epoch.", + "oneOf": [ + { + "description": "Represents the state of an epoch that's in progress (well, duh.) All actions are allowed to be issued.", + "type": "string", + "enum": [ + "in_progress" + ] + }, + { + "description": "Represents the state of an epoch when the rewarding entity has been decided on, and the mixnodes are in the process of being rewarded for their work in this epoch.", + "type": "object", + "required": [ + "rewarding" + ], + "properties": { + "rewarding": { + "type": "object", + "required": [ + "final_node_id", + "last_rewarded" + ], + "properties": { + "final_node_id": { + "description": "The id of the last node that's going to be rewarded before progressing into the next state.", + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "last_rewarded": { + "description": "The id of the last node that has already received its rewards.", + "type": "integer", + "format": "uint32", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Represents the state of an epoch when all mixnodes have already been rewarded for their work in this epoch and all issued actions should now get resolved before being allowed to advance into the next epoch.", + "type": "string", + "enum": [ + "reconciling_events" + ] + }, + { + "description": "Represents the state of an epoch when all mixnodes have already been rewarded for their work in this epoch, all issued actions got resolved and the epoch should now be advanced whilst assigning new rewarded set.", + "type": "string", + "enum": [ + "advancing_epoch" + ] + } + ] + } + } +} diff --git a/contracts/mixnet/schema/raw/response_to_get_estimated_current_epoch_delegator_reward.json b/contracts/mixnet/schema/raw/response_to_get_estimated_current_epoch_delegator_reward.json new file mode 100644 index 0000000000..94ca8beb78 --- /dev/null +++ b/contracts/mixnet/schema/raw/response_to_get_estimated_current_epoch_delegator_reward.json @@ -0,0 +1,89 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "EstimatedCurrentEpochRewardResponse", + "description": "Response containing estimation of node rewards for the current epoch.", + "type": "object", + "properties": { + "current_stake_value": { + "description": "The current stake value given all past rewarding and compounding since the original staking was performed.", + "anyOf": [ + { + "$ref": "#/definitions/Coin" + }, + { + "type": "null" + } + ] + }, + "current_stake_value_detailed_amount": { + "description": "The current stake value. Note that it's nearly identical to `current_stake_value`, however, it contains few additional decimal points for more accurate reward calculation.", + "anyOf": [ + { + "$ref": "#/definitions/Decimal" + }, + { + "type": "null" + } + ] + }, + "detailed_estimation_amount": { + "description": "The full reward estimation. Note that it's nearly identical to `estimation`, however, it contains few additional decimal points for more accurate reward calculation.", + "anyOf": [ + { + "$ref": "#/definitions/Decimal" + }, + { + "type": "null" + } + ] + }, + "estimation": { + "description": "The reward estimation for the current epoch, i.e. the amount of tokens that could be claimable after the epoch finishes and the state of the network does not change.", + "anyOf": [ + { + "$ref": "#/definitions/Coin" + }, + { + "type": "null" + } + ] + }, + "original_stake": { + "description": "The amount of tokens initially staked.", + "anyOf": [ + { + "$ref": "#/definitions/Coin" + }, + { + "type": "null" + } + ] + } + }, + "additionalProperties": false, + "definitions": { + "Coin": { + "type": "object", + "required": [ + "amount", + "denom" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "denom": { + "type": "string" + } + } + }, + "Decimal": { + "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", + "type": "string" + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } +} diff --git a/contracts/mixnet/schema/raw/response_to_get_estimated_current_epoch_operator_reward.json b/contracts/mixnet/schema/raw/response_to_get_estimated_current_epoch_operator_reward.json new file mode 100644 index 0000000000..94ca8beb78 --- /dev/null +++ b/contracts/mixnet/schema/raw/response_to_get_estimated_current_epoch_operator_reward.json @@ -0,0 +1,89 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "EstimatedCurrentEpochRewardResponse", + "description": "Response containing estimation of node rewards for the current epoch.", + "type": "object", + "properties": { + "current_stake_value": { + "description": "The current stake value given all past rewarding and compounding since the original staking was performed.", + "anyOf": [ + { + "$ref": "#/definitions/Coin" + }, + { + "type": "null" + } + ] + }, + "current_stake_value_detailed_amount": { + "description": "The current stake value. Note that it's nearly identical to `current_stake_value`, however, it contains few additional decimal points for more accurate reward calculation.", + "anyOf": [ + { + "$ref": "#/definitions/Decimal" + }, + { + "type": "null" + } + ] + }, + "detailed_estimation_amount": { + "description": "The full reward estimation. Note that it's nearly identical to `estimation`, however, it contains few additional decimal points for more accurate reward calculation.", + "anyOf": [ + { + "$ref": "#/definitions/Decimal" + }, + { + "type": "null" + } + ] + }, + "estimation": { + "description": "The reward estimation for the current epoch, i.e. the amount of tokens that could be claimable after the epoch finishes and the state of the network does not change.", + "anyOf": [ + { + "$ref": "#/definitions/Coin" + }, + { + "type": "null" + } + ] + }, + "original_stake": { + "description": "The amount of tokens initially staked.", + "anyOf": [ + { + "$ref": "#/definitions/Coin" + }, + { + "type": "null" + } + ] + } + }, + "additionalProperties": false, + "definitions": { + "Coin": { + "type": "object", + "required": [ + "amount", + "denom" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "denom": { + "type": "string" + } + } + }, + "Decimal": { + "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", + "type": "string" + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } +} diff --git a/contracts/mixnet/schema/raw/response_to_get_family_by_head.json b/contracts/mixnet/schema/raw/response_to_get_family_by_head.json new file mode 100644 index 0000000000..d7bc69b639 --- /dev/null +++ b/contracts/mixnet/schema/raw/response_to_get_family_by_head.json @@ -0,0 +1,67 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "FamilyByHeadResponse", + "description": "Response containing family information.", + "type": "object", + "required": [ + "head" + ], + "properties": { + "family": { + "description": "If applicable, the family associated with the provided head.", + "anyOf": [ + { + "$ref": "#/definitions/Family" + }, + { + "type": "null" + } + ] + }, + "head": { + "description": "The family head used for the query.", + "allOf": [ + { + "$ref": "#/definitions/FamilyHead" + } + ] + } + }, + "additionalProperties": false, + "definitions": { + "Family": { + "description": "A group of mixnodes associated with particular staking entity. When defined all nodes belonging to the same family will be prioritised to be put onto the same layer.", + "type": "object", + "required": [ + "head", + "label" + ], + "properties": { + "head": { + "description": "Owner of this family.", + "allOf": [ + { + "$ref": "#/definitions/FamilyHead" + } + ] + }, + "label": { + "description": "Human readable label for this family.", + "type": "string" + }, + "proxy": { + "description": "Optional proxy (i.e. vesting contract address) used when creating the family.", + "type": [ + "string", + "null" + ] + } + }, + "additionalProperties": false + }, + "FamilyHead": { + "description": "Head of particular family as identified by its identity key (i.e. public component of its ed25519 keypair stringified into base58).", + "type": "string" + } + } +} diff --git a/contracts/mixnet/schema/raw/response_to_get_family_by_label.json b/contracts/mixnet/schema/raw/response_to_get_family_by_label.json new file mode 100644 index 0000000000..c007bd05dc --- /dev/null +++ b/contracts/mixnet/schema/raw/response_to_get_family_by_label.json @@ -0,0 +1,63 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "FamilyByLabelResponse", + "description": "Response containing family information.", + "type": "object", + "required": [ + "label" + ], + "properties": { + "family": { + "description": "If applicable, the family associated with the provided label.", + "anyOf": [ + { + "$ref": "#/definitions/Family" + }, + { + "type": "null" + } + ] + }, + "label": { + "description": "The family label used for the query.", + "type": "string" + } + }, + "additionalProperties": false, + "definitions": { + "Family": { + "description": "A group of mixnodes associated with particular staking entity. When defined all nodes belonging to the same family will be prioritised to be put onto the same layer.", + "type": "object", + "required": [ + "head", + "label" + ], + "properties": { + "head": { + "description": "Owner of this family.", + "allOf": [ + { + "$ref": "#/definitions/FamilyHead" + } + ] + }, + "label": { + "description": "Human readable label for this family.", + "type": "string" + }, + "proxy": { + "description": "Optional proxy (i.e. vesting contract address) used when creating the family.", + "type": [ + "string", + "null" + ] + } + }, + "additionalProperties": false + }, + "FamilyHead": { + "description": "Head of particular family as identified by its identity key (i.e. public component of its ed25519 keypair stringified into base58).", + "type": "string" + } + } +} diff --git a/contracts/mixnet/schema/raw/response_to_get_family_members_by_head.json b/contracts/mixnet/schema/raw/response_to_get_family_members_by_head.json new file mode 100644 index 0000000000..7975599604 --- /dev/null +++ b/contracts/mixnet/schema/raw/response_to_get_family_members_by_head.json @@ -0,0 +1,34 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "FamilyMembersByHeadResponse", + "description": "Response containing family members information.", + "type": "object", + "required": [ + "head", + "members" + ], + "properties": { + "head": { + "description": "The family head used for the query.", + "allOf": [ + { + "$ref": "#/definitions/FamilyHead" + } + ] + }, + "members": { + "description": "All members belonging to the specified family.", + "type": "array", + "items": { + "type": "string" + } + } + }, + "additionalProperties": false, + "definitions": { + "FamilyHead": { + "description": "Head of particular family as identified by its identity key (i.e. public component of its ed25519 keypair stringified into base58).", + "type": "string" + } + } +} diff --git a/contracts/mixnet/schema/raw/response_to_get_family_members_by_label.json b/contracts/mixnet/schema/raw/response_to_get_family_members_by_label.json new file mode 100644 index 0000000000..a76bb07f7d --- /dev/null +++ b/contracts/mixnet/schema/raw/response_to_get_family_members_by_label.json @@ -0,0 +1,24 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "FamilyMembersByLabelResponse", + "description": "Response containing family members information.", + "type": "object", + "required": [ + "label", + "members" + ], + "properties": { + "label": { + "description": "The family label used for the query.", + "type": "string" + }, + "members": { + "description": "All members belonging to the specified family.", + "type": "array", + "items": { + "type": "string" + } + } + }, + "additionalProperties": false +} diff --git a/contracts/mixnet/schema/raw/response_to_get_gateway_bond.json b/contracts/mixnet/schema/raw/response_to_get_gateway_bond.json new file mode 100644 index 0000000000..dd7f99b4a4 --- /dev/null +++ b/contracts/mixnet/schema/raw/response_to_get_gateway_bond.json @@ -0,0 +1,154 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "GatewayBondResponse", + "description": "Response containing details of a gateway with the provided identity key.", + "type": "object", + "required": [ + "identity" + ], + "properties": { + "gateway": { + "description": "If there exists a gateway with the provided identity key, this field contains its details.", + "anyOf": [ + { + "$ref": "#/definitions/GatewayBond" + }, + { + "type": "null" + } + ] + }, + "identity": { + "description": "The identity key (base58-encoded ed25519 public key) of the gateway.", + "type": "string" + } + }, + "additionalProperties": false, + "definitions": { + "Addr": { + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + }, + "Coin": { + "type": "object", + "required": [ + "amount", + "denom" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "denom": { + "type": "string" + } + } + }, + "Gateway": { + "description": "Information provided by the node operator during bonding that are used to allow other entities to use the services of this node.", + "type": "object", + "required": [ + "clients_port", + "host", + "identity_key", + "location", + "mix_port", + "sphinx_key", + "version" + ], + "properties": { + "clients_port": { + "description": "Port used by this gateway for listening for client requests.", + "type": "integer", + "format": "uint16", + "minimum": 0.0 + }, + "host": { + "description": "Network address of this gateway, for example 1.1.1.1 or foo.gateway.com", + "type": "string" + }, + "identity_key": { + "description": "Base58 encoded ed25519 EdDSA public key of the gateway used to derive shared keys with clients", + "type": "string" + }, + "location": { + "description": "The physical, self-reported, location of this gateway.", + "type": "string" + }, + "mix_port": { + "description": "Port used by this gateway for listening for mix packets.", + "type": "integer", + "format": "uint16", + "minimum": 0.0 + }, + "sphinx_key": { + "description": "Base58-encoded x25519 public key used for sphinx key derivation.", + "type": "string" + }, + "version": { + "description": "The self-reported semver version of this gateway.", + "type": "string" + } + }, + "additionalProperties": false + }, + "GatewayBond": { + "description": "Basic gateway information provided by the node operator.", + "type": "object", + "required": [ + "block_height", + "gateway", + "owner", + "pledge_amount" + ], + "properties": { + "block_height": { + "description": "Block height at which this gateway has been bonded.", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "gateway": { + "description": "Information provided by the operator for the purposes of bonding.", + "allOf": [ + { + "$ref": "#/definitions/Gateway" + } + ] + }, + "owner": { + "description": "Address of the owner of this gateway.", + "allOf": [ + { + "$ref": "#/definitions/Addr" + } + ] + }, + "pledge_amount": { + "description": "Original amount pledged by the operator of this node.", + "allOf": [ + { + "$ref": "#/definitions/Coin" + } + ] + }, + "proxy": { + "description": "Entity who bonded this gateway on behalf of the owner. If exists, it's most likely the address of the vesting contract.", + "anyOf": [ + { + "$ref": "#/definitions/Addr" + }, + { + "type": "null" + } + ] + } + }, + "additionalProperties": false + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } +} diff --git a/contracts/mixnet/schema/raw/response_to_get_gateways.json b/contracts/mixnet/schema/raw/response_to_get_gateways.json new file mode 100644 index 0000000000..0ec50135b6 --- /dev/null +++ b/contracts/mixnet/schema/raw/response_to_get_gateways.json @@ -0,0 +1,160 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "PagedGatewayResponse", + "description": "Response containing paged list of all gateway bonds in the contract.", + "type": "object", + "required": [ + "nodes", + "per_page" + ], + "properties": { + "nodes": { + "description": "The gateway bond information present in the contract.", + "type": "array", + "items": { + "$ref": "#/definitions/GatewayBond" + } + }, + "per_page": { + "description": "Maximum number of entries that could be included in a response. `per_page <= nodes.len()`", + "type": "integer", + "format": "uint", + "minimum": 0.0 + }, + "start_next_after": { + "description": "Field indicating paging information for the following queries if the caller wishes to get further entries.", + "type": [ + "string", + "null" + ] + } + }, + "additionalProperties": false, + "definitions": { + "Addr": { + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + }, + "Coin": { + "type": "object", + "required": [ + "amount", + "denom" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "denom": { + "type": "string" + } + } + }, + "Gateway": { + "description": "Information provided by the node operator during bonding that are used to allow other entities to use the services of this node.", + "type": "object", + "required": [ + "clients_port", + "host", + "identity_key", + "location", + "mix_port", + "sphinx_key", + "version" + ], + "properties": { + "clients_port": { + "description": "Port used by this gateway for listening for client requests.", + "type": "integer", + "format": "uint16", + "minimum": 0.0 + }, + "host": { + "description": "Network address of this gateway, for example 1.1.1.1 or foo.gateway.com", + "type": "string" + }, + "identity_key": { + "description": "Base58 encoded ed25519 EdDSA public key of the gateway used to derive shared keys with clients", + "type": "string" + }, + "location": { + "description": "The physical, self-reported, location of this gateway.", + "type": "string" + }, + "mix_port": { + "description": "Port used by this gateway for listening for mix packets.", + "type": "integer", + "format": "uint16", + "minimum": 0.0 + }, + "sphinx_key": { + "description": "Base58-encoded x25519 public key used for sphinx key derivation.", + "type": "string" + }, + "version": { + "description": "The self-reported semver version of this gateway.", + "type": "string" + } + }, + "additionalProperties": false + }, + "GatewayBond": { + "description": "Basic gateway information provided by the node operator.", + "type": "object", + "required": [ + "block_height", + "gateway", + "owner", + "pledge_amount" + ], + "properties": { + "block_height": { + "description": "Block height at which this gateway has been bonded.", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "gateway": { + "description": "Information provided by the operator for the purposes of bonding.", + "allOf": [ + { + "$ref": "#/definitions/Gateway" + } + ] + }, + "owner": { + "description": "Address of the owner of this gateway.", + "allOf": [ + { + "$ref": "#/definitions/Addr" + } + ] + }, + "pledge_amount": { + "description": "Original amount pledged by the operator of this node.", + "allOf": [ + { + "$ref": "#/definitions/Coin" + } + ] + }, + "proxy": { + "description": "Entity who bonded this gateway on behalf of the owner. If exists, it's most likely the address of the vesting contract.", + "anyOf": [ + { + "$ref": "#/definitions/Addr" + }, + { + "type": "null" + } + ] + } + }, + "additionalProperties": false + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } +} diff --git a/contracts/mixnet/schema/raw/response_to_get_layer_distribution.json b/contracts/mixnet/schema/raw/response_to_get_layer_distribution.json new file mode 100644 index 0000000000..0ed27f33cb --- /dev/null +++ b/contracts/mixnet/schema/raw/response_to_get_layer_distribution.json @@ -0,0 +1,32 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "LayerDistribution", + "description": "The current layer distribution of the mix network.", + "type": "object", + "required": [ + "layer1", + "layer2", + "layer3" + ], + "properties": { + "layer1": { + "description": "Number of nodes on the first layer.", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "layer2": { + "description": "Number of nodes on the second layer.", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "layer3": { + "description": "Number of nodes on the third layer.", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false +} diff --git a/contracts/mixnet/schema/raw/response_to_get_mix_node_bonds.json b/contracts/mixnet/schema/raw/response_to_get_mix_node_bonds.json new file mode 100644 index 0000000000..61bdf6b3f1 --- /dev/null +++ b/contracts/mixnet/schema/raw/response_to_get_mix_node_bonds.json @@ -0,0 +1,193 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "PagedMixnodeBondsResponse", + "description": "Response containing paged list of all mixnode bonds in the contract.", + "type": "object", + "required": [ + "nodes", + "per_page" + ], + "properties": { + "nodes": { + "description": "The mixnode bond information present in the contract.", + "type": "array", + "items": { + "$ref": "#/definitions/MixNodeBond" + } + }, + "per_page": { + "description": "Maximum number of entries that could be included in a response. `per_page <= nodes.len()`", + "type": "integer", + "format": "uint", + "minimum": 0.0 + }, + "start_next_after": { + "description": "Field indicating paging information for the following queries if the caller wishes to get further entries.", + "type": [ + "integer", + "null" + ], + "format": "uint32", + "minimum": 0.0 + } + }, + "additionalProperties": false, + "definitions": { + "Addr": { + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + }, + "Coin": { + "type": "object", + "required": [ + "amount", + "denom" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "denom": { + "type": "string" + } + } + }, + "Layer": { + "type": "string", + "enum": [ + "One", + "Two", + "Three" + ] + }, + "MixNode": { + "description": "Information provided by the node operator during bonding that are used to allow other entities to use the services of this node.", + "type": "object", + "required": [ + "host", + "http_api_port", + "identity_key", + "mix_port", + "sphinx_key", + "verloc_port", + "version" + ], + "properties": { + "host": { + "description": "Network address of this mixnode, for example 1.1.1.1 or foo.mixnode.com", + "type": "string" + }, + "http_api_port": { + "description": "Port used by this mixnode for its http(s) API", + "type": "integer", + "format": "uint16", + "minimum": 0.0 + }, + "identity_key": { + "description": "Base58-encoded ed25519 EdDSA public key.", + "type": "string" + }, + "mix_port": { + "description": "Port used by this mixnode for listening for mix packets.", + "type": "integer", + "format": "uint16", + "minimum": 0.0 + }, + "sphinx_key": { + "description": "Base58-encoded x25519 public key used for sphinx key derivation.", + "type": "string" + }, + "verloc_port": { + "description": "Port used by this mixnode for listening for verloc requests.", + "type": "integer", + "format": "uint16", + "minimum": 0.0 + }, + "version": { + "description": "The self-reported semver version of this mixnode.", + "type": "string" + } + }, + "additionalProperties": false + }, + "MixNodeBond": { + "description": "Basic mixnode information provided by the node operator.", + "type": "object", + "required": [ + "bonding_height", + "is_unbonding", + "layer", + "mix_id", + "mix_node", + "original_pledge", + "owner" + ], + "properties": { + "bonding_height": { + "description": "Block height at which this mixnode has been bonded.", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "is_unbonding": { + "description": "Flag to indicate whether this node is in the process of unbonding, that will conclude upon the epoch finishing.", + "type": "boolean" + }, + "layer": { + "description": "Layer assigned to this mixnode.", + "allOf": [ + { + "$ref": "#/definitions/Layer" + } + ] + }, + "mix_id": { + "description": "Unique id assigned to the bonded mixnode.", + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "mix_node": { + "description": "Information provided by the operator for the purposes of bonding.", + "allOf": [ + { + "$ref": "#/definitions/MixNode" + } + ] + }, + "original_pledge": { + "description": "Original amount pledged by the operator of this node.", + "allOf": [ + { + "$ref": "#/definitions/Coin" + } + ] + }, + "owner": { + "description": "Address of the owner of this mixnode.", + "allOf": [ + { + "$ref": "#/definitions/Addr" + } + ] + }, + "proxy": { + "description": "Entity who bonded this mixnode on behalf of the owner. If exists, it's most likely the address of the vesting contract.", + "anyOf": [ + { + "$ref": "#/definitions/Addr" + }, + { + "type": "null" + } + ] + } + }, + "additionalProperties": false + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } +} diff --git a/contracts/mixnet/schema/raw/response_to_get_mix_nodes_detailed.json b/contracts/mixnet/schema/raw/response_to_get_mix_nodes_detailed.json new file mode 100644 index 0000000000..bc95bb0839 --- /dev/null +++ b/contracts/mixnet/schema/raw/response_to_get_mix_nodes_detailed.json @@ -0,0 +1,350 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "PagedMixnodesDetailsResponse", + "description": "Response containing paged list of all mixnode details in the contract.", + "type": "object", + "required": [ + "nodes", + "per_page" + ], + "properties": { + "nodes": { + "description": "All mixnode details stored in the contract. Apart from the basic bond information it also contains details required for all future reward calculation as well as any pending changes requested by the operator.", + "type": "array", + "items": { + "$ref": "#/definitions/MixNodeDetails" + } + }, + "per_page": { + "description": "Maximum number of entries that could be included in a response. `per_page <= nodes.len()`", + "type": "integer", + "format": "uint", + "minimum": 0.0 + }, + "start_next_after": { + "description": "Field indicating paging information for the following queries if the caller wishes to get further entries.", + "type": [ + "integer", + "null" + ], + "format": "uint32", + "minimum": 0.0 + } + }, + "additionalProperties": false, + "definitions": { + "Addr": { + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + }, + "Coin": { + "type": "object", + "required": [ + "amount", + "denom" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "denom": { + "type": "string" + } + } + }, + "Decimal": { + "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", + "type": "string" + }, + "Layer": { + "type": "string", + "enum": [ + "One", + "Two", + "Three" + ] + }, + "MixNode": { + "description": "Information provided by the node operator during bonding that are used to allow other entities to use the services of this node.", + "type": "object", + "required": [ + "host", + "http_api_port", + "identity_key", + "mix_port", + "sphinx_key", + "verloc_port", + "version" + ], + "properties": { + "host": { + "description": "Network address of this mixnode, for example 1.1.1.1 or foo.mixnode.com", + "type": "string" + }, + "http_api_port": { + "description": "Port used by this mixnode for its http(s) API", + "type": "integer", + "format": "uint16", + "minimum": 0.0 + }, + "identity_key": { + "description": "Base58-encoded ed25519 EdDSA public key.", + "type": "string" + }, + "mix_port": { + "description": "Port used by this mixnode for listening for mix packets.", + "type": "integer", + "format": "uint16", + "minimum": 0.0 + }, + "sphinx_key": { + "description": "Base58-encoded x25519 public key used for sphinx key derivation.", + "type": "string" + }, + "verloc_port": { + "description": "Port used by this mixnode for listening for verloc requests.", + "type": "integer", + "format": "uint16", + "minimum": 0.0 + }, + "version": { + "description": "The self-reported semver version of this mixnode.", + "type": "string" + } + }, + "additionalProperties": false + }, + "MixNodeBond": { + "description": "Basic mixnode information provided by the node operator.", + "type": "object", + "required": [ + "bonding_height", + "is_unbonding", + "layer", + "mix_id", + "mix_node", + "original_pledge", + "owner" + ], + "properties": { + "bonding_height": { + "description": "Block height at which this mixnode has been bonded.", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "is_unbonding": { + "description": "Flag to indicate whether this node is in the process of unbonding, that will conclude upon the epoch finishing.", + "type": "boolean" + }, + "layer": { + "description": "Layer assigned to this mixnode.", + "allOf": [ + { + "$ref": "#/definitions/Layer" + } + ] + }, + "mix_id": { + "description": "Unique id assigned to the bonded mixnode.", + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "mix_node": { + "description": "Information provided by the operator for the purposes of bonding.", + "allOf": [ + { + "$ref": "#/definitions/MixNode" + } + ] + }, + "original_pledge": { + "description": "Original amount pledged by the operator of this node.", + "allOf": [ + { + "$ref": "#/definitions/Coin" + } + ] + }, + "owner": { + "description": "Address of the owner of this mixnode.", + "allOf": [ + { + "$ref": "#/definitions/Addr" + } + ] + }, + "proxy": { + "description": "Entity who bonded this mixnode on behalf of the owner. If exists, it's most likely the address of the vesting contract.", + "anyOf": [ + { + "$ref": "#/definitions/Addr" + }, + { + "type": "null" + } + ] + } + }, + "additionalProperties": false + }, + "MixNodeCostParams": { + "description": "The cost parameters, or the cost function, defined for the particular mixnode that influences how the rewards should be split between the node operator and its delegators.", + "type": "object", + "required": [ + "interval_operating_cost", + "profit_margin_percent" + ], + "properties": { + "interval_operating_cost": { + "description": "Operating cost of the associated mixnode per the entire interval.", + "allOf": [ + { + "$ref": "#/definitions/Coin" + } + ] + }, + "profit_margin_percent": { + "description": "The profit margin of the associated mixnode, i.e. the desired percent of the reward to be distributed to the operator.", + "allOf": [ + { + "$ref": "#/definitions/Percent" + } + ] + } + }, + "additionalProperties": false + }, + "MixNodeDetails": { + "description": "Full details associated with given mixnode.", + "type": "object", + "required": [ + "bond_information", + "rewarding_details" + ], + "properties": { + "bond_information": { + "description": "Basic bond information of this mixnode, such as owner address, original pledge, etc.", + "allOf": [ + { + "$ref": "#/definitions/MixNodeBond" + } + ] + }, + "pending_changes": { + "description": "Adjustments to the mixnode that are ought to happen during future epoch transitions.", + "default": { + "pledge_change": null + }, + "allOf": [ + { + "$ref": "#/definitions/PendingMixNodeChanges" + } + ] + }, + "rewarding_details": { + "description": "Details used for computation of rewarding related data.", + "allOf": [ + { + "$ref": "#/definitions/MixNodeRewarding" + } + ] + } + }, + "additionalProperties": false + }, + "MixNodeRewarding": { + "type": "object", + "required": [ + "cost_params", + "delegates", + "last_rewarded_epoch", + "operator", + "total_unit_reward", + "unique_delegations", + "unit_delegation" + ], + "properties": { + "cost_params": { + "description": "Information provided by the operator that influence the cost function.", + "allOf": [ + { + "$ref": "#/definitions/MixNodeCostParams" + } + ] + }, + "delegates": { + "description": "Total delegation and compounded reward earned by all node delegators.", + "allOf": [ + { + "$ref": "#/definitions/Decimal" + } + ] + }, + "last_rewarded_epoch": { + "description": "Marks the epoch when this node was last rewarded so that we wouldn't accidentally attempt to reward it multiple times in the same epoch.", + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "operator": { + "description": "Total pledge and compounded reward earned by the node operator.", + "allOf": [ + { + "$ref": "#/definitions/Decimal" + } + ] + }, + "total_unit_reward": { + "description": "Cumulative reward earned by the \"unit delegation\" since the block 0.", + "allOf": [ + { + "$ref": "#/definitions/Decimal" + } + ] + }, + "unique_delegations": { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "unit_delegation": { + "description": "Value of the theoretical \"unit delegation\" that has delegated to this mixnode at block 0.", + "allOf": [ + { + "$ref": "#/definitions/Decimal" + } + ] + } + }, + "additionalProperties": false + }, + "PendingMixNodeChanges": { + "type": "object", + "properties": { + "pledge_change": { + "type": [ + "integer", + "null" + ], + "format": "uint32", + "minimum": 0.0 + } + }, + "additionalProperties": false + }, + "Percent": { + "description": "Percent represents a value between 0 and 100% (i.e. between 0.0 and 1.0)", + "allOf": [ + { + "$ref": "#/definitions/Decimal" + } + ] + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } +} diff --git a/contracts/mixnet/schema/raw/response_to_get_mixnode_delegations.json b/contracts/mixnet/schema/raw/response_to_get_mixnode_delegations.json new file mode 100644 index 0000000000..82d8c56b90 --- /dev/null +++ b/contracts/mixnet/schema/raw/response_to_get_mixnode_delegations.json @@ -0,0 +1,116 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "PagedMixNodeDelegationsResponse", + "description": "Response containing paged list of all delegations made towards particular mixnode.", + "type": "object", + "required": [ + "delegations" + ], + "properties": { + "delegations": { + "description": "Each individual delegation made.", + "type": "array", + "items": { + "$ref": "#/definitions/Delegation" + } + }, + "start_next_after": { + "description": "Field indicating paging information for the following queries if the caller wishes to get further entries.", + "type": [ + "string", + "null" + ] + } + }, + "additionalProperties": false, + "definitions": { + "Addr": { + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + }, + "Coin": { + "type": "object", + "required": [ + "amount", + "denom" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "denom": { + "type": "string" + } + } + }, + "Decimal": { + "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", + "type": "string" + }, + "Delegation": { + "description": "Information about tokens being delegated towards given mixnode in order to accrue rewards with their work.", + "type": "object", + "required": [ + "amount", + "cumulative_reward_ratio", + "height", + "mix_id", + "owner" + ], + "properties": { + "amount": { + "description": "Original delegation amount. Note that it is never mutated as delegation accumulates rewards.", + "allOf": [ + { + "$ref": "#/definitions/Coin" + } + ] + }, + "cumulative_reward_ratio": { + "description": "Value of the \"unit delegation\" associated with the mixnode at the time of delegation.", + "allOf": [ + { + "$ref": "#/definitions/Decimal" + } + ] + }, + "height": { + "description": "Block height where this delegation occurred.", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "mix_id": { + "description": "Id of the MixNode that this delegation was performed against.", + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "owner": { + "description": "Address of the owner of this delegation.", + "allOf": [ + { + "$ref": "#/definitions/Addr" + } + ] + }, + "proxy": { + "description": "Proxy address used to delegate the funds on behalf of another address", + "anyOf": [ + { + "$ref": "#/definitions/Addr" + }, + { + "type": "null" + } + ] + } + }, + "additionalProperties": false + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } +} diff --git a/contracts/mixnet/schema/raw/response_to_get_mixnode_details.json b/contracts/mixnet/schema/raw/response_to_get_mixnode_details.json new file mode 100644 index 0000000000..4d7bd42da4 --- /dev/null +++ b/contracts/mixnet/schema/raw/response_to_get_mixnode_details.json @@ -0,0 +1,344 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "MixnodeDetailsResponse", + "description": "Response containing details of a mixnode with the provided id.", + "type": "object", + "required": [ + "mix_id" + ], + "properties": { + "mix_id": { + "description": "Id of the requested mixnode.", + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "mixnode_details": { + "description": "If there exists a mixnode with the provided id, this field contains its detailed information.", + "anyOf": [ + { + "$ref": "#/definitions/MixNodeDetails" + }, + { + "type": "null" + } + ] + } + }, + "additionalProperties": false, + "definitions": { + "Addr": { + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + }, + "Coin": { + "type": "object", + "required": [ + "amount", + "denom" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "denom": { + "type": "string" + } + } + }, + "Decimal": { + "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", + "type": "string" + }, + "Layer": { + "type": "string", + "enum": [ + "One", + "Two", + "Three" + ] + }, + "MixNode": { + "description": "Information provided by the node operator during bonding that are used to allow other entities to use the services of this node.", + "type": "object", + "required": [ + "host", + "http_api_port", + "identity_key", + "mix_port", + "sphinx_key", + "verloc_port", + "version" + ], + "properties": { + "host": { + "description": "Network address of this mixnode, for example 1.1.1.1 or foo.mixnode.com", + "type": "string" + }, + "http_api_port": { + "description": "Port used by this mixnode for its http(s) API", + "type": "integer", + "format": "uint16", + "minimum": 0.0 + }, + "identity_key": { + "description": "Base58-encoded ed25519 EdDSA public key.", + "type": "string" + }, + "mix_port": { + "description": "Port used by this mixnode for listening for mix packets.", + "type": "integer", + "format": "uint16", + "minimum": 0.0 + }, + "sphinx_key": { + "description": "Base58-encoded x25519 public key used for sphinx key derivation.", + "type": "string" + }, + "verloc_port": { + "description": "Port used by this mixnode for listening for verloc requests.", + "type": "integer", + "format": "uint16", + "minimum": 0.0 + }, + "version": { + "description": "The self-reported semver version of this mixnode.", + "type": "string" + } + }, + "additionalProperties": false + }, + "MixNodeBond": { + "description": "Basic mixnode information provided by the node operator.", + "type": "object", + "required": [ + "bonding_height", + "is_unbonding", + "layer", + "mix_id", + "mix_node", + "original_pledge", + "owner" + ], + "properties": { + "bonding_height": { + "description": "Block height at which this mixnode has been bonded.", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "is_unbonding": { + "description": "Flag to indicate whether this node is in the process of unbonding, that will conclude upon the epoch finishing.", + "type": "boolean" + }, + "layer": { + "description": "Layer assigned to this mixnode.", + "allOf": [ + { + "$ref": "#/definitions/Layer" + } + ] + }, + "mix_id": { + "description": "Unique id assigned to the bonded mixnode.", + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "mix_node": { + "description": "Information provided by the operator for the purposes of bonding.", + "allOf": [ + { + "$ref": "#/definitions/MixNode" + } + ] + }, + "original_pledge": { + "description": "Original amount pledged by the operator of this node.", + "allOf": [ + { + "$ref": "#/definitions/Coin" + } + ] + }, + "owner": { + "description": "Address of the owner of this mixnode.", + "allOf": [ + { + "$ref": "#/definitions/Addr" + } + ] + }, + "proxy": { + "description": "Entity who bonded this mixnode on behalf of the owner. If exists, it's most likely the address of the vesting contract.", + "anyOf": [ + { + "$ref": "#/definitions/Addr" + }, + { + "type": "null" + } + ] + } + }, + "additionalProperties": false + }, + "MixNodeCostParams": { + "description": "The cost parameters, or the cost function, defined for the particular mixnode that influences how the rewards should be split between the node operator and its delegators.", + "type": "object", + "required": [ + "interval_operating_cost", + "profit_margin_percent" + ], + "properties": { + "interval_operating_cost": { + "description": "Operating cost of the associated mixnode per the entire interval.", + "allOf": [ + { + "$ref": "#/definitions/Coin" + } + ] + }, + "profit_margin_percent": { + "description": "The profit margin of the associated mixnode, i.e. the desired percent of the reward to be distributed to the operator.", + "allOf": [ + { + "$ref": "#/definitions/Percent" + } + ] + } + }, + "additionalProperties": false + }, + "MixNodeDetails": { + "description": "Full details associated with given mixnode.", + "type": "object", + "required": [ + "bond_information", + "rewarding_details" + ], + "properties": { + "bond_information": { + "description": "Basic bond information of this mixnode, such as owner address, original pledge, etc.", + "allOf": [ + { + "$ref": "#/definitions/MixNodeBond" + } + ] + }, + "pending_changes": { + "description": "Adjustments to the mixnode that are ought to happen during future epoch transitions.", + "default": { + "pledge_change": null + }, + "allOf": [ + { + "$ref": "#/definitions/PendingMixNodeChanges" + } + ] + }, + "rewarding_details": { + "description": "Details used for computation of rewarding related data.", + "allOf": [ + { + "$ref": "#/definitions/MixNodeRewarding" + } + ] + } + }, + "additionalProperties": false + }, + "MixNodeRewarding": { + "type": "object", + "required": [ + "cost_params", + "delegates", + "last_rewarded_epoch", + "operator", + "total_unit_reward", + "unique_delegations", + "unit_delegation" + ], + "properties": { + "cost_params": { + "description": "Information provided by the operator that influence the cost function.", + "allOf": [ + { + "$ref": "#/definitions/MixNodeCostParams" + } + ] + }, + "delegates": { + "description": "Total delegation and compounded reward earned by all node delegators.", + "allOf": [ + { + "$ref": "#/definitions/Decimal" + } + ] + }, + "last_rewarded_epoch": { + "description": "Marks the epoch when this node was last rewarded so that we wouldn't accidentally attempt to reward it multiple times in the same epoch.", + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "operator": { + "description": "Total pledge and compounded reward earned by the node operator.", + "allOf": [ + { + "$ref": "#/definitions/Decimal" + } + ] + }, + "total_unit_reward": { + "description": "Cumulative reward earned by the \"unit delegation\" since the block 0.", + "allOf": [ + { + "$ref": "#/definitions/Decimal" + } + ] + }, + "unique_delegations": { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "unit_delegation": { + "description": "Value of the theoretical \"unit delegation\" that has delegated to this mixnode at block 0.", + "allOf": [ + { + "$ref": "#/definitions/Decimal" + } + ] + } + }, + "additionalProperties": false + }, + "PendingMixNodeChanges": { + "type": "object", + "properties": { + "pledge_change": { + "type": [ + "integer", + "null" + ], + "format": "uint32", + "minimum": 0.0 + } + }, + "additionalProperties": false + }, + "Percent": { + "description": "Percent represents a value between 0 and 100% (i.e. between 0.0 and 1.0)", + "allOf": [ + { + "$ref": "#/definitions/Decimal" + } + ] + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } +} diff --git a/contracts/mixnet/schema/raw/response_to_get_mixnode_rewarding_details.json b/contracts/mixnet/schema/raw/response_to_get_mixnode_rewarding_details.json new file mode 100644 index 0000000000..9b30dc4a75 --- /dev/null +++ b/contracts/mixnet/schema/raw/response_to_get_mixnode_rewarding_details.json @@ -0,0 +1,155 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "MixnodeRewardingDetailsResponse", + "description": "Response containing rewarding information of a mixnode with the provided id.", + "type": "object", + "required": [ + "mix_id" + ], + "properties": { + "mix_id": { + "description": "Id of the requested mixnode.", + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "rewarding_details": { + "description": "If there exists a mixnode with the provided id, this field contains its rewarding information.", + "anyOf": [ + { + "$ref": "#/definitions/MixNodeRewarding" + }, + { + "type": "null" + } + ] + } + }, + "additionalProperties": false, + "definitions": { + "Coin": { + "type": "object", + "required": [ + "amount", + "denom" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "denom": { + "type": "string" + } + } + }, + "Decimal": { + "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", + "type": "string" + }, + "MixNodeCostParams": { + "description": "The cost parameters, or the cost function, defined for the particular mixnode that influences how the rewards should be split between the node operator and its delegators.", + "type": "object", + "required": [ + "interval_operating_cost", + "profit_margin_percent" + ], + "properties": { + "interval_operating_cost": { + "description": "Operating cost of the associated mixnode per the entire interval.", + "allOf": [ + { + "$ref": "#/definitions/Coin" + } + ] + }, + "profit_margin_percent": { + "description": "The profit margin of the associated mixnode, i.e. the desired percent of the reward to be distributed to the operator.", + "allOf": [ + { + "$ref": "#/definitions/Percent" + } + ] + } + }, + "additionalProperties": false + }, + "MixNodeRewarding": { + "type": "object", + "required": [ + "cost_params", + "delegates", + "last_rewarded_epoch", + "operator", + "total_unit_reward", + "unique_delegations", + "unit_delegation" + ], + "properties": { + "cost_params": { + "description": "Information provided by the operator that influence the cost function.", + "allOf": [ + { + "$ref": "#/definitions/MixNodeCostParams" + } + ] + }, + "delegates": { + "description": "Total delegation and compounded reward earned by all node delegators.", + "allOf": [ + { + "$ref": "#/definitions/Decimal" + } + ] + }, + "last_rewarded_epoch": { + "description": "Marks the epoch when this node was last rewarded so that we wouldn't accidentally attempt to reward it multiple times in the same epoch.", + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "operator": { + "description": "Total pledge and compounded reward earned by the node operator.", + "allOf": [ + { + "$ref": "#/definitions/Decimal" + } + ] + }, + "total_unit_reward": { + "description": "Cumulative reward earned by the \"unit delegation\" since the block 0.", + "allOf": [ + { + "$ref": "#/definitions/Decimal" + } + ] + }, + "unique_delegations": { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "unit_delegation": { + "description": "Value of the theoretical \"unit delegation\" that has delegated to this mixnode at block 0.", + "allOf": [ + { + "$ref": "#/definitions/Decimal" + } + ] + } + }, + "additionalProperties": false + }, + "Percent": { + "description": "Percent represents a value between 0 and 100% (i.e. between 0.0 and 1.0)", + "allOf": [ + { + "$ref": "#/definitions/Decimal" + } + ] + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } +} diff --git a/contracts/mixnet/schema/raw/response_to_get_number_of_pending_events.json b/contracts/mixnet/schema/raw/response_to_get_number_of_pending_events.json new file mode 100644 index 0000000000..520dbce29c --- /dev/null +++ b/contracts/mixnet/schema/raw/response_to_get_number_of_pending_events.json @@ -0,0 +1,25 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "NumberOfPendingEventsResponse", + "description": "Response containing number of currently pending epoch and interval events.", + "type": "object", + "required": [ + "epoch_events", + "interval_events" + ], + "properties": { + "epoch_events": { + "description": "The number of the currently pending epoch events.", + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "interval_events": { + "description": "The number of the currently pending epoch events.", + "type": "integer", + "format": "uint32", + "minimum": 0.0 + } + }, + "additionalProperties": false +} diff --git a/contracts/mixnet/schema/raw/response_to_get_owned_gateway.json b/contracts/mixnet/schema/raw/response_to_get_owned_gateway.json new file mode 100644 index 0000000000..50f2bbd4ad --- /dev/null +++ b/contracts/mixnet/schema/raw/response_to_get_owned_gateway.json @@ -0,0 +1,158 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "GatewayOwnershipResponse", + "description": "Response containing details of a gateway belonging to the particular owner.", + "type": "object", + "required": [ + "address" + ], + "properties": { + "address": { + "description": "Validated address of the gateway owner.", + "allOf": [ + { + "$ref": "#/definitions/Addr" + } + ] + }, + "gateway": { + "description": "If the provided address owns a gateway, this field contains its details.", + "anyOf": [ + { + "$ref": "#/definitions/GatewayBond" + }, + { + "type": "null" + } + ] + } + }, + "additionalProperties": false, + "definitions": { + "Addr": { + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + }, + "Coin": { + "type": "object", + "required": [ + "amount", + "denom" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "denom": { + "type": "string" + } + } + }, + "Gateway": { + "description": "Information provided by the node operator during bonding that are used to allow other entities to use the services of this node.", + "type": "object", + "required": [ + "clients_port", + "host", + "identity_key", + "location", + "mix_port", + "sphinx_key", + "version" + ], + "properties": { + "clients_port": { + "description": "Port used by this gateway for listening for client requests.", + "type": "integer", + "format": "uint16", + "minimum": 0.0 + }, + "host": { + "description": "Network address of this gateway, for example 1.1.1.1 or foo.gateway.com", + "type": "string" + }, + "identity_key": { + "description": "Base58 encoded ed25519 EdDSA public key of the gateway used to derive shared keys with clients", + "type": "string" + }, + "location": { + "description": "The physical, self-reported, location of this gateway.", + "type": "string" + }, + "mix_port": { + "description": "Port used by this gateway for listening for mix packets.", + "type": "integer", + "format": "uint16", + "minimum": 0.0 + }, + "sphinx_key": { + "description": "Base58-encoded x25519 public key used for sphinx key derivation.", + "type": "string" + }, + "version": { + "description": "The self-reported semver version of this gateway.", + "type": "string" + } + }, + "additionalProperties": false + }, + "GatewayBond": { + "description": "Basic gateway information provided by the node operator.", + "type": "object", + "required": [ + "block_height", + "gateway", + "owner", + "pledge_amount" + ], + "properties": { + "block_height": { + "description": "Block height at which this gateway has been bonded.", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "gateway": { + "description": "Information provided by the operator for the purposes of bonding.", + "allOf": [ + { + "$ref": "#/definitions/Gateway" + } + ] + }, + "owner": { + "description": "Address of the owner of this gateway.", + "allOf": [ + { + "$ref": "#/definitions/Addr" + } + ] + }, + "pledge_amount": { + "description": "Original amount pledged by the operator of this node.", + "allOf": [ + { + "$ref": "#/definitions/Coin" + } + ] + }, + "proxy": { + "description": "Entity who bonded this gateway on behalf of the owner. If exists, it's most likely the address of the vesting contract.", + "anyOf": [ + { + "$ref": "#/definitions/Addr" + }, + { + "type": "null" + } + ] + } + }, + "additionalProperties": false + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } +} diff --git a/contracts/mixnet/schema/raw/response_to_get_owned_mixnode.json b/contracts/mixnet/schema/raw/response_to_get_owned_mixnode.json new file mode 100644 index 0000000000..da895c4bf4 --- /dev/null +++ b/contracts/mixnet/schema/raw/response_to_get_owned_mixnode.json @@ -0,0 +1,346 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "MixOwnershipResponse", + "description": "Response containing details of a mixnode belonging to the particular owner.", + "type": "object", + "required": [ + "address" + ], + "properties": { + "address": { + "description": "Validated address of the mixnode owner.", + "allOf": [ + { + "$ref": "#/definitions/Addr" + } + ] + }, + "mixnode_details": { + "description": "If the provided address owns a mixnode, this field contains its detailed information.", + "anyOf": [ + { + "$ref": "#/definitions/MixNodeDetails" + }, + { + "type": "null" + } + ] + } + }, + "additionalProperties": false, + "definitions": { + "Addr": { + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + }, + "Coin": { + "type": "object", + "required": [ + "amount", + "denom" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "denom": { + "type": "string" + } + } + }, + "Decimal": { + "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", + "type": "string" + }, + "Layer": { + "type": "string", + "enum": [ + "One", + "Two", + "Three" + ] + }, + "MixNode": { + "description": "Information provided by the node operator during bonding that are used to allow other entities to use the services of this node.", + "type": "object", + "required": [ + "host", + "http_api_port", + "identity_key", + "mix_port", + "sphinx_key", + "verloc_port", + "version" + ], + "properties": { + "host": { + "description": "Network address of this mixnode, for example 1.1.1.1 or foo.mixnode.com", + "type": "string" + }, + "http_api_port": { + "description": "Port used by this mixnode for its http(s) API", + "type": "integer", + "format": "uint16", + "minimum": 0.0 + }, + "identity_key": { + "description": "Base58-encoded ed25519 EdDSA public key.", + "type": "string" + }, + "mix_port": { + "description": "Port used by this mixnode for listening for mix packets.", + "type": "integer", + "format": "uint16", + "minimum": 0.0 + }, + "sphinx_key": { + "description": "Base58-encoded x25519 public key used for sphinx key derivation.", + "type": "string" + }, + "verloc_port": { + "description": "Port used by this mixnode for listening for verloc requests.", + "type": "integer", + "format": "uint16", + "minimum": 0.0 + }, + "version": { + "description": "The self-reported semver version of this mixnode.", + "type": "string" + } + }, + "additionalProperties": false + }, + "MixNodeBond": { + "description": "Basic mixnode information provided by the node operator.", + "type": "object", + "required": [ + "bonding_height", + "is_unbonding", + "layer", + "mix_id", + "mix_node", + "original_pledge", + "owner" + ], + "properties": { + "bonding_height": { + "description": "Block height at which this mixnode has been bonded.", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "is_unbonding": { + "description": "Flag to indicate whether this node is in the process of unbonding, that will conclude upon the epoch finishing.", + "type": "boolean" + }, + "layer": { + "description": "Layer assigned to this mixnode.", + "allOf": [ + { + "$ref": "#/definitions/Layer" + } + ] + }, + "mix_id": { + "description": "Unique id assigned to the bonded mixnode.", + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "mix_node": { + "description": "Information provided by the operator for the purposes of bonding.", + "allOf": [ + { + "$ref": "#/definitions/MixNode" + } + ] + }, + "original_pledge": { + "description": "Original amount pledged by the operator of this node.", + "allOf": [ + { + "$ref": "#/definitions/Coin" + } + ] + }, + "owner": { + "description": "Address of the owner of this mixnode.", + "allOf": [ + { + "$ref": "#/definitions/Addr" + } + ] + }, + "proxy": { + "description": "Entity who bonded this mixnode on behalf of the owner. If exists, it's most likely the address of the vesting contract.", + "anyOf": [ + { + "$ref": "#/definitions/Addr" + }, + { + "type": "null" + } + ] + } + }, + "additionalProperties": false + }, + "MixNodeCostParams": { + "description": "The cost parameters, or the cost function, defined for the particular mixnode that influences how the rewards should be split between the node operator and its delegators.", + "type": "object", + "required": [ + "interval_operating_cost", + "profit_margin_percent" + ], + "properties": { + "interval_operating_cost": { + "description": "Operating cost of the associated mixnode per the entire interval.", + "allOf": [ + { + "$ref": "#/definitions/Coin" + } + ] + }, + "profit_margin_percent": { + "description": "The profit margin of the associated mixnode, i.e. the desired percent of the reward to be distributed to the operator.", + "allOf": [ + { + "$ref": "#/definitions/Percent" + } + ] + } + }, + "additionalProperties": false + }, + "MixNodeDetails": { + "description": "Full details associated with given mixnode.", + "type": "object", + "required": [ + "bond_information", + "rewarding_details" + ], + "properties": { + "bond_information": { + "description": "Basic bond information of this mixnode, such as owner address, original pledge, etc.", + "allOf": [ + { + "$ref": "#/definitions/MixNodeBond" + } + ] + }, + "pending_changes": { + "description": "Adjustments to the mixnode that are ought to happen during future epoch transitions.", + "default": { + "pledge_change": null + }, + "allOf": [ + { + "$ref": "#/definitions/PendingMixNodeChanges" + } + ] + }, + "rewarding_details": { + "description": "Details used for computation of rewarding related data.", + "allOf": [ + { + "$ref": "#/definitions/MixNodeRewarding" + } + ] + } + }, + "additionalProperties": false + }, + "MixNodeRewarding": { + "type": "object", + "required": [ + "cost_params", + "delegates", + "last_rewarded_epoch", + "operator", + "total_unit_reward", + "unique_delegations", + "unit_delegation" + ], + "properties": { + "cost_params": { + "description": "Information provided by the operator that influence the cost function.", + "allOf": [ + { + "$ref": "#/definitions/MixNodeCostParams" + } + ] + }, + "delegates": { + "description": "Total delegation and compounded reward earned by all node delegators.", + "allOf": [ + { + "$ref": "#/definitions/Decimal" + } + ] + }, + "last_rewarded_epoch": { + "description": "Marks the epoch when this node was last rewarded so that we wouldn't accidentally attempt to reward it multiple times in the same epoch.", + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "operator": { + "description": "Total pledge and compounded reward earned by the node operator.", + "allOf": [ + { + "$ref": "#/definitions/Decimal" + } + ] + }, + "total_unit_reward": { + "description": "Cumulative reward earned by the \"unit delegation\" since the block 0.", + "allOf": [ + { + "$ref": "#/definitions/Decimal" + } + ] + }, + "unique_delegations": { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "unit_delegation": { + "description": "Value of the theoretical \"unit delegation\" that has delegated to this mixnode at block 0.", + "allOf": [ + { + "$ref": "#/definitions/Decimal" + } + ] + } + }, + "additionalProperties": false + }, + "PendingMixNodeChanges": { + "type": "object", + "properties": { + "pledge_change": { + "type": [ + "integer", + "null" + ], + "format": "uint32", + "minimum": 0.0 + } + }, + "additionalProperties": false + }, + "Percent": { + "description": "Percent represents a value between 0 and 100% (i.e. between 0.0 and 1.0)", + "allOf": [ + { + "$ref": "#/definitions/Decimal" + } + ] + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } +} diff --git a/contracts/mixnet/schema/raw/response_to_get_pending_delegator_reward.json b/contracts/mixnet/schema/raw/response_to_get_pending_delegator_reward.json new file mode 100644 index 0000000000..e885d5976c --- /dev/null +++ b/contracts/mixnet/schema/raw/response_to_get_pending_delegator_reward.json @@ -0,0 +1,74 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "PendingRewardResponse", + "description": "Response containing information about accrued rewards.", + "type": "object", + "required": [ + "mixnode_still_fully_bonded" + ], + "properties": { + "amount_earned": { + "description": "The amount of tokens that could be claimed.", + "anyOf": [ + { + "$ref": "#/definitions/Coin" + }, + { + "type": "null" + } + ] + }, + "amount_earned_detailed": { + "description": "The full pending rewards. Note that it's nearly identical to `amount_earned`, however, it contains few additional decimal points for more accurate reward calculation.", + "anyOf": [ + { + "$ref": "#/definitions/Decimal" + }, + { + "type": "null" + } + ] + }, + "amount_staked": { + "description": "The amount of tokens initially staked.", + "anyOf": [ + { + "$ref": "#/definitions/Coin" + }, + { + "type": "null" + } + ] + }, + "mixnode_still_fully_bonded": { + "description": "The associated mixnode is still fully bonded, meaning it is neither unbonded nor in the process of unbonding that would have finished at the epoch transition.", + "type": "boolean" + } + }, + "additionalProperties": false, + "definitions": { + "Coin": { + "type": "object", + "required": [ + "amount", + "denom" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "denom": { + "type": "string" + } + } + }, + "Decimal": { + "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", + "type": "string" + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } +} diff --git a/contracts/mixnet/schema/raw/response_to_get_pending_epoch_event.json b/contracts/mixnet/schema/raw/response_to_get_pending_epoch_event.json new file mode 100644 index 0000000000..7588cad3e9 --- /dev/null +++ b/contracts/mixnet/schema/raw/response_to_get_pending_epoch_event.json @@ -0,0 +1,298 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "PendingEpochEventResponse", + "type": "object", + "required": [ + "event_id" + ], + "properties": { + "event": { + "anyOf": [ + { + "$ref": "#/definitions/PendingEpochEventData" + }, + { + "type": "null" + } + ] + }, + "event_id": { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + } + }, + "additionalProperties": false, + "definitions": { + "Addr": { + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + }, + "Coin": { + "type": "object", + "required": [ + "amount", + "denom" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "denom": { + "type": "string" + } + } + }, + "PendingEpochEventData": { + "description": "Details of a particular pending epoch event.", + "type": "object", + "required": [ + "created_at", + "kind" + ], + "properties": { + "created_at": { + "description": "The block height at which the request has been made.", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "kind": { + "description": "The underlying event data, containing its concrete type and information on how it should get resolved.", + "allOf": [ + { + "$ref": "#/definitions/PendingEpochEventKind" + } + ] + } + }, + "additionalProperties": false + }, + "PendingEpochEventKind": { + "description": "Enum encompassing all possible epoch events.", + "oneOf": [ + { + "description": "Request to create a delegation towards particular mixnode. Note that if such delegation already exists, it will get updated with the provided token amount.", + "type": "object", + "required": [ + "delegate" + ], + "properties": { + "delegate": { + "type": "object", + "required": [ + "amount", + "mix_id", + "owner" + ], + "properties": { + "amount": { + "description": "The amount of tokens to use for the delegation.", + "allOf": [ + { + "$ref": "#/definitions/Coin" + } + ] + }, + "mix_id": { + "description": "The id of the mixnode used for the delegation.", + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "owner": { + "description": "The address of the owner of the delegation.", + "allOf": [ + { + "$ref": "#/definitions/Addr" + } + ] + }, + "proxy": { + "description": "Entity who made the delegation on behalf of the owner. If present, it's most likely the address of the vesting contract.", + "anyOf": [ + { + "$ref": "#/definitions/Addr" + }, + { + "type": "null" + } + ] + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Request to remove delegation from particular mixnode.", + "type": "object", + "required": [ + "undelegate" + ], + "properties": { + "undelegate": { + "type": "object", + "required": [ + "mix_id", + "owner" + ], + "properties": { + "mix_id": { + "description": "The id of the mixnode used for the delegation.", + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "owner": { + "description": "The address of the owner of the delegation.", + "allOf": [ + { + "$ref": "#/definitions/Addr" + } + ] + }, + "proxy": { + "description": "Entity who made the delegation on behalf of the owner. If present, it's most likely the address of the vesting contract.", + "anyOf": [ + { + "$ref": "#/definitions/Addr" + }, + { + "type": "null" + } + ] + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Request to pledge more tokens (by the node operator) towards its node.", + "type": "object", + "required": [ + "pledge_more" + ], + "properties": { + "pledge_more": { + "type": "object", + "required": [ + "amount", + "mix_id" + ], + "properties": { + "amount": { + "description": "The amount of additional tokens to use by the pledge.", + "allOf": [ + { + "$ref": "#/definitions/Coin" + } + ] + }, + "mix_id": { + "description": "The id of the mixnode that will have its pledge updated.", + "type": "integer", + "format": "uint32", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Request to decrease amount of pledged tokens (by the node operator) from its node.", + "type": "object", + "required": [ + "decrease_pledge" + ], + "properties": { + "decrease_pledge": { + "type": "object", + "required": [ + "decrease_by", + "mix_id" + ], + "properties": { + "decrease_by": { + "description": "The amount of tokens that should be removed from the pledge.", + "allOf": [ + { + "$ref": "#/definitions/Coin" + } + ] + }, + "mix_id": { + "description": "The id of the mixnode that will have its pledge updated.", + "type": "integer", + "format": "uint32", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Request to unbond a mixnode and completely remove it from the network.", + "type": "object", + "required": [ + "unbond_mixnode" + ], + "properties": { + "unbond_mixnode": { + "type": "object", + "required": [ + "mix_id" + ], + "properties": { + "mix_id": { + "description": "The id of the mixnode that will get unbonded.", + "type": "integer", + "format": "uint32", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Request to update the current size of the active set.", + "type": "object", + "required": [ + "update_active_set_size" + ], + "properties": { + "update_active_set_size": { + "type": "object", + "required": [ + "new_size" + ], + "properties": { + "new_size": { + "description": "The new desired size of the active set.", + "type": "integer", + "format": "uint32", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + ] + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } +} diff --git a/contracts/mixnet/schema/raw/response_to_get_pending_epoch_events.json b/contracts/mixnet/schema/raw/response_to_get_pending_epoch_events.json new file mode 100644 index 0000000000..09a4d03787 --- /dev/null +++ b/contracts/mixnet/schema/raw/response_to_get_pending_epoch_events.json @@ -0,0 +1,331 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "PendingEpochEventsResponse", + "description": "Response containing all currently pending epoch events that will be resolved once the current epoch finishes.", + "type": "object", + "required": [ + "events", + "seconds_until_executable" + ], + "properties": { + "events": { + "description": "The currently pending events.", + "type": "array", + "items": { + "$ref": "#/definitions/PendingEpochEvent" + } + }, + "seconds_until_executable": { + "description": "Amount of seconds until the events would be eligible to be resolved. It's equivalent to the time until the current epoch finishes.", + "type": "integer", + "format": "int64" + }, + "start_next_after": { + "description": "Field indicating paging information for the following queries if the caller wishes to get further entries.", + "type": [ + "integer", + "null" + ], + "format": "uint32", + "minimum": 0.0 + } + }, + "additionalProperties": false, + "definitions": { + "Addr": { + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + }, + "Coin": { + "type": "object", + "required": [ + "amount", + "denom" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "denom": { + "type": "string" + } + } + }, + "PendingEpochEvent": { + "description": "A request made at some point in the current epoch that's going to get resolved once the epoch rolls over.", + "type": "object", + "required": [ + "event", + "id" + ], + "properties": { + "event": { + "description": "The underlying event details, containing its type and information on how it should get resolved.", + "allOf": [ + { + "$ref": "#/definitions/PendingEpochEventData" + } + ] + }, + "id": { + "description": "The unique id associated with the event.", + "type": "integer", + "format": "uint32", + "minimum": 0.0 + } + }, + "additionalProperties": false + }, + "PendingEpochEventData": { + "description": "Details of a particular pending epoch event.", + "type": "object", + "required": [ + "created_at", + "kind" + ], + "properties": { + "created_at": { + "description": "The block height at which the request has been made.", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "kind": { + "description": "The underlying event data, containing its concrete type and information on how it should get resolved.", + "allOf": [ + { + "$ref": "#/definitions/PendingEpochEventKind" + } + ] + } + }, + "additionalProperties": false + }, + "PendingEpochEventKind": { + "description": "Enum encompassing all possible epoch events.", + "oneOf": [ + { + "description": "Request to create a delegation towards particular mixnode. Note that if such delegation already exists, it will get updated with the provided token amount.", + "type": "object", + "required": [ + "delegate" + ], + "properties": { + "delegate": { + "type": "object", + "required": [ + "amount", + "mix_id", + "owner" + ], + "properties": { + "amount": { + "description": "The amount of tokens to use for the delegation.", + "allOf": [ + { + "$ref": "#/definitions/Coin" + } + ] + }, + "mix_id": { + "description": "The id of the mixnode used for the delegation.", + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "owner": { + "description": "The address of the owner of the delegation.", + "allOf": [ + { + "$ref": "#/definitions/Addr" + } + ] + }, + "proxy": { + "description": "Entity who made the delegation on behalf of the owner. If present, it's most likely the address of the vesting contract.", + "anyOf": [ + { + "$ref": "#/definitions/Addr" + }, + { + "type": "null" + } + ] + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Request to remove delegation from particular mixnode.", + "type": "object", + "required": [ + "undelegate" + ], + "properties": { + "undelegate": { + "type": "object", + "required": [ + "mix_id", + "owner" + ], + "properties": { + "mix_id": { + "description": "The id of the mixnode used for the delegation.", + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "owner": { + "description": "The address of the owner of the delegation.", + "allOf": [ + { + "$ref": "#/definitions/Addr" + } + ] + }, + "proxy": { + "description": "Entity who made the delegation on behalf of the owner. If present, it's most likely the address of the vesting contract.", + "anyOf": [ + { + "$ref": "#/definitions/Addr" + }, + { + "type": "null" + } + ] + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Request to pledge more tokens (by the node operator) towards its node.", + "type": "object", + "required": [ + "pledge_more" + ], + "properties": { + "pledge_more": { + "type": "object", + "required": [ + "amount", + "mix_id" + ], + "properties": { + "amount": { + "description": "The amount of additional tokens to use by the pledge.", + "allOf": [ + { + "$ref": "#/definitions/Coin" + } + ] + }, + "mix_id": { + "description": "The id of the mixnode that will have its pledge updated.", + "type": "integer", + "format": "uint32", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Request to decrease amount of pledged tokens (by the node operator) from its node.", + "type": "object", + "required": [ + "decrease_pledge" + ], + "properties": { + "decrease_pledge": { + "type": "object", + "required": [ + "decrease_by", + "mix_id" + ], + "properties": { + "decrease_by": { + "description": "The amount of tokens that should be removed from the pledge.", + "allOf": [ + { + "$ref": "#/definitions/Coin" + } + ] + }, + "mix_id": { + "description": "The id of the mixnode that will have its pledge updated.", + "type": "integer", + "format": "uint32", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Request to unbond a mixnode and completely remove it from the network.", + "type": "object", + "required": [ + "unbond_mixnode" + ], + "properties": { + "unbond_mixnode": { + "type": "object", + "required": [ + "mix_id" + ], + "properties": { + "mix_id": { + "description": "The id of the mixnode that will get unbonded.", + "type": "integer", + "format": "uint32", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Request to update the current size of the active set.", + "type": "object", + "required": [ + "update_active_set_size" + ], + "properties": { + "update_active_set_size": { + "type": "object", + "required": [ + "new_size" + ], + "properties": { + "new_size": { + "description": "The new desired size of the active set.", + "type": "integer", + "format": "uint32", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + ] + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } +} diff --git a/contracts/mixnet/schema/raw/response_to_get_pending_interval_event.json b/contracts/mixnet/schema/raw/response_to_get_pending_interval_event.json new file mode 100644 index 0000000000..a0877027a9 --- /dev/null +++ b/contracts/mixnet/schema/raw/response_to_get_pending_interval_event.json @@ -0,0 +1,291 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "PendingIntervalEventResponse", + "type": "object", + "required": [ + "event_id" + ], + "properties": { + "event": { + "anyOf": [ + { + "$ref": "#/definitions/PendingIntervalEventData" + }, + { + "type": "null" + } + ] + }, + "event_id": { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + } + }, + "additionalProperties": false, + "definitions": { + "Coin": { + "type": "object", + "required": [ + "amount", + "denom" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "denom": { + "type": "string" + } + } + }, + "Decimal": { + "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", + "type": "string" + }, + "IntervalRewardingParamsUpdate": { + "description": "Specification on how the rewarding params should be updated.", + "type": "object", + "properties": { + "active_set_work_factor": { + "description": "Defines the new value of the active set work factor.", + "anyOf": [ + { + "$ref": "#/definitions/Decimal" + }, + { + "type": "null" + } + ] + }, + "interval_pool_emission": { + "description": "Defines the new value of the interval pool emission rate.", + "anyOf": [ + { + "$ref": "#/definitions/Percent" + }, + { + "type": "null" + } + ] + }, + "reward_pool": { + "description": "Defines the new value of the reward pool.", + "anyOf": [ + { + "$ref": "#/definitions/Decimal" + }, + { + "type": "null" + } + ] + }, + "rewarded_set_size": { + "description": "Defines the new size of the rewarded set.", + "type": [ + "integer", + "null" + ], + "format": "uint32", + "minimum": 0.0 + }, + "staking_supply": { + "description": "Defines the new value of the staking supply.", + "anyOf": [ + { + "$ref": "#/definitions/Decimal" + }, + { + "type": "null" + } + ] + }, + "staking_supply_scale_factor": { + "description": "Defines the new value of the staking supply scale factor.", + "anyOf": [ + { + "$ref": "#/definitions/Percent" + }, + { + "type": "null" + } + ] + }, + "sybil_resistance_percent": { + "description": "Defines the new value of the sybil resistance percent.", + "anyOf": [ + { + "$ref": "#/definitions/Percent" + }, + { + "type": "null" + } + ] + } + }, + "additionalProperties": false + }, + "MixNodeCostParams": { + "description": "The cost parameters, or the cost function, defined for the particular mixnode that influences how the rewards should be split between the node operator and its delegators.", + "type": "object", + "required": [ + "interval_operating_cost", + "profit_margin_percent" + ], + "properties": { + "interval_operating_cost": { + "description": "Operating cost of the associated mixnode per the entire interval.", + "allOf": [ + { + "$ref": "#/definitions/Coin" + } + ] + }, + "profit_margin_percent": { + "description": "The profit margin of the associated mixnode, i.e. the desired percent of the reward to be distributed to the operator.", + "allOf": [ + { + "$ref": "#/definitions/Percent" + } + ] + } + }, + "additionalProperties": false + }, + "PendingIntervalEventData": { + "description": "Details of a particular pending interval event.", + "type": "object", + "required": [ + "created_at", + "kind" + ], + "properties": { + "created_at": { + "description": "The block height at which the request has been made.", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "kind": { + "description": "The underlying event data, containing its concrete type and information on how it should get resolved.", + "allOf": [ + { + "$ref": "#/definitions/PendingIntervalEventKind" + } + ] + } + }, + "additionalProperties": false + }, + "PendingIntervalEventKind": { + "description": "Enum encompassing all possible interval events.", + "oneOf": [ + { + "description": "Request to update cost parameters of given mixnode.", + "type": "object", + "required": [ + "change_mix_cost_params" + ], + "properties": { + "change_mix_cost_params": { + "type": "object", + "required": [ + "mix_id", + "new_costs" + ], + "properties": { + "mix_id": { + "description": "The id of the mixnode that will have its cost parameters updated.", + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "new_costs": { + "description": "The new updated cost function of this mixnode.", + "allOf": [ + { + "$ref": "#/definitions/MixNodeCostParams" + } + ] + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Request to update the underlying rewarding parameters used by the system", + "type": "object", + "required": [ + "update_rewarding_params" + ], + "properties": { + "update_rewarding_params": { + "type": "object", + "required": [ + "update" + ], + "properties": { + "update": { + "description": "The detailed specification of the update.", + "allOf": [ + { + "$ref": "#/definitions/IntervalRewardingParamsUpdate" + } + ] + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Request to change the next interval configuration.", + "type": "object", + "required": [ + "update_interval_config" + ], + "properties": { + "update_interval_config": { + "type": "object", + "required": [ + "epoch_duration_secs", + "epochs_in_interval" + ], + "properties": { + "epoch_duration_secs": { + "description": "The new epoch duration.", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "epochs_in_interval": { + "description": "The new number of epochs in intervals.", + "type": "integer", + "format": "uint32", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + ] + }, + "Percent": { + "description": "Percent represents a value between 0 and 100% (i.e. between 0.0 and 1.0)", + "allOf": [ + { + "$ref": "#/definitions/Decimal" + } + ] + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } +} diff --git a/contracts/mixnet/schema/raw/response_to_get_pending_interval_events.json b/contracts/mixnet/schema/raw/response_to_get_pending_interval_events.json new file mode 100644 index 0000000000..ccbdc22178 --- /dev/null +++ b/contracts/mixnet/schema/raw/response_to_get_pending_interval_events.json @@ -0,0 +1,324 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "PendingIntervalEventsResponse", + "description": "Response containing all currently pending interval events that will be resolved once the current interval finishes.", + "type": "object", + "required": [ + "events", + "seconds_until_executable" + ], + "properties": { + "events": { + "description": "The currently pending events.", + "type": "array", + "items": { + "$ref": "#/definitions/PendingIntervalEvent" + } + }, + "seconds_until_executable": { + "description": "Amount of seconds until the events would be eligible to be resolved. It's equivalent to the time until the current interval finishes.", + "type": "integer", + "format": "int64" + }, + "start_next_after": { + "description": "Field indicating paging information for the following queries if the caller wishes to get further entries.", + "type": [ + "integer", + "null" + ], + "format": "uint32", + "minimum": 0.0 + } + }, + "additionalProperties": false, + "definitions": { + "Coin": { + "type": "object", + "required": [ + "amount", + "denom" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "denom": { + "type": "string" + } + } + }, + "Decimal": { + "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", + "type": "string" + }, + "IntervalRewardingParamsUpdate": { + "description": "Specification on how the rewarding params should be updated.", + "type": "object", + "properties": { + "active_set_work_factor": { + "description": "Defines the new value of the active set work factor.", + "anyOf": [ + { + "$ref": "#/definitions/Decimal" + }, + { + "type": "null" + } + ] + }, + "interval_pool_emission": { + "description": "Defines the new value of the interval pool emission rate.", + "anyOf": [ + { + "$ref": "#/definitions/Percent" + }, + { + "type": "null" + } + ] + }, + "reward_pool": { + "description": "Defines the new value of the reward pool.", + "anyOf": [ + { + "$ref": "#/definitions/Decimal" + }, + { + "type": "null" + } + ] + }, + "rewarded_set_size": { + "description": "Defines the new size of the rewarded set.", + "type": [ + "integer", + "null" + ], + "format": "uint32", + "minimum": 0.0 + }, + "staking_supply": { + "description": "Defines the new value of the staking supply.", + "anyOf": [ + { + "$ref": "#/definitions/Decimal" + }, + { + "type": "null" + } + ] + }, + "staking_supply_scale_factor": { + "description": "Defines the new value of the staking supply scale factor.", + "anyOf": [ + { + "$ref": "#/definitions/Percent" + }, + { + "type": "null" + } + ] + }, + "sybil_resistance_percent": { + "description": "Defines the new value of the sybil resistance percent.", + "anyOf": [ + { + "$ref": "#/definitions/Percent" + }, + { + "type": "null" + } + ] + } + }, + "additionalProperties": false + }, + "MixNodeCostParams": { + "description": "The cost parameters, or the cost function, defined for the particular mixnode that influences how the rewards should be split between the node operator and its delegators.", + "type": "object", + "required": [ + "interval_operating_cost", + "profit_margin_percent" + ], + "properties": { + "interval_operating_cost": { + "description": "Operating cost of the associated mixnode per the entire interval.", + "allOf": [ + { + "$ref": "#/definitions/Coin" + } + ] + }, + "profit_margin_percent": { + "description": "The profit margin of the associated mixnode, i.e. the desired percent of the reward to be distributed to the operator.", + "allOf": [ + { + "$ref": "#/definitions/Percent" + } + ] + } + }, + "additionalProperties": false + }, + "PendingIntervalEvent": { + "description": "A request made at some point in the current interval that's going to get resolved once the interval rolls over.", + "type": "object", + "required": [ + "event", + "id" + ], + "properties": { + "event": { + "description": "The underlying event details, containing its type and information on how it should get resolved.", + "allOf": [ + { + "$ref": "#/definitions/PendingIntervalEventData" + } + ] + }, + "id": { + "description": "The unique id associated with the event.", + "type": "integer", + "format": "uint32", + "minimum": 0.0 + } + }, + "additionalProperties": false + }, + "PendingIntervalEventData": { + "description": "Details of a particular pending interval event.", + "type": "object", + "required": [ + "created_at", + "kind" + ], + "properties": { + "created_at": { + "description": "The block height at which the request has been made.", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "kind": { + "description": "The underlying event data, containing its concrete type and information on how it should get resolved.", + "allOf": [ + { + "$ref": "#/definitions/PendingIntervalEventKind" + } + ] + } + }, + "additionalProperties": false + }, + "PendingIntervalEventKind": { + "description": "Enum encompassing all possible interval events.", + "oneOf": [ + { + "description": "Request to update cost parameters of given mixnode.", + "type": "object", + "required": [ + "change_mix_cost_params" + ], + "properties": { + "change_mix_cost_params": { + "type": "object", + "required": [ + "mix_id", + "new_costs" + ], + "properties": { + "mix_id": { + "description": "The id of the mixnode that will have its cost parameters updated.", + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "new_costs": { + "description": "The new updated cost function of this mixnode.", + "allOf": [ + { + "$ref": "#/definitions/MixNodeCostParams" + } + ] + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Request to update the underlying rewarding parameters used by the system", + "type": "object", + "required": [ + "update_rewarding_params" + ], + "properties": { + "update_rewarding_params": { + "type": "object", + "required": [ + "update" + ], + "properties": { + "update": { + "description": "The detailed specification of the update.", + "allOf": [ + { + "$ref": "#/definitions/IntervalRewardingParamsUpdate" + } + ] + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Request to change the next interval configuration.", + "type": "object", + "required": [ + "update_interval_config" + ], + "properties": { + "update_interval_config": { + "type": "object", + "required": [ + "epoch_duration_secs", + "epochs_in_interval" + ], + "properties": { + "epoch_duration_secs": { + "description": "The new epoch duration.", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "epochs_in_interval": { + "description": "The new number of epochs in intervals.", + "type": "integer", + "format": "uint32", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + ] + }, + "Percent": { + "description": "Percent represents a value between 0 and 100% (i.e. between 0.0 and 1.0)", + "allOf": [ + { + "$ref": "#/definitions/Decimal" + } + ] + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } +} diff --git a/contracts/mixnet/schema/raw/response_to_get_pending_mix_node_operator_reward.json b/contracts/mixnet/schema/raw/response_to_get_pending_mix_node_operator_reward.json new file mode 100644 index 0000000000..e885d5976c --- /dev/null +++ b/contracts/mixnet/schema/raw/response_to_get_pending_mix_node_operator_reward.json @@ -0,0 +1,74 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "PendingRewardResponse", + "description": "Response containing information about accrued rewards.", + "type": "object", + "required": [ + "mixnode_still_fully_bonded" + ], + "properties": { + "amount_earned": { + "description": "The amount of tokens that could be claimed.", + "anyOf": [ + { + "$ref": "#/definitions/Coin" + }, + { + "type": "null" + } + ] + }, + "amount_earned_detailed": { + "description": "The full pending rewards. Note that it's nearly identical to `amount_earned`, however, it contains few additional decimal points for more accurate reward calculation.", + "anyOf": [ + { + "$ref": "#/definitions/Decimal" + }, + { + "type": "null" + } + ] + }, + "amount_staked": { + "description": "The amount of tokens initially staked.", + "anyOf": [ + { + "$ref": "#/definitions/Coin" + }, + { + "type": "null" + } + ] + }, + "mixnode_still_fully_bonded": { + "description": "The associated mixnode is still fully bonded, meaning it is neither unbonded nor in the process of unbonding that would have finished at the epoch transition.", + "type": "boolean" + } + }, + "additionalProperties": false, + "definitions": { + "Coin": { + "type": "object", + "required": [ + "amount", + "denom" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "denom": { + "type": "string" + } + } + }, + "Decimal": { + "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", + "type": "string" + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } +} diff --git a/contracts/mixnet/schema/raw/response_to_get_pending_operator_reward.json b/contracts/mixnet/schema/raw/response_to_get_pending_operator_reward.json new file mode 100644 index 0000000000..e885d5976c --- /dev/null +++ b/contracts/mixnet/schema/raw/response_to_get_pending_operator_reward.json @@ -0,0 +1,74 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "PendingRewardResponse", + "description": "Response containing information about accrued rewards.", + "type": "object", + "required": [ + "mixnode_still_fully_bonded" + ], + "properties": { + "amount_earned": { + "description": "The amount of tokens that could be claimed.", + "anyOf": [ + { + "$ref": "#/definitions/Coin" + }, + { + "type": "null" + } + ] + }, + "amount_earned_detailed": { + "description": "The full pending rewards. Note that it's nearly identical to `amount_earned`, however, it contains few additional decimal points for more accurate reward calculation.", + "anyOf": [ + { + "$ref": "#/definitions/Decimal" + }, + { + "type": "null" + } + ] + }, + "amount_staked": { + "description": "The amount of tokens initially staked.", + "anyOf": [ + { + "$ref": "#/definitions/Coin" + }, + { + "type": "null" + } + ] + }, + "mixnode_still_fully_bonded": { + "description": "The associated mixnode is still fully bonded, meaning it is neither unbonded nor in the process of unbonding that would have finished at the epoch transition.", + "type": "boolean" + } + }, + "additionalProperties": false, + "definitions": { + "Coin": { + "type": "object", + "required": [ + "amount", + "denom" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "denom": { + "type": "string" + } + } + }, + "Decimal": { + "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", + "type": "string" + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } +} diff --git a/contracts/mixnet/schema/raw/response_to_get_rewarded_set.json b/contracts/mixnet/schema/raw/response_to_get_rewarded_set.json new file mode 100644 index 0000000000..e623d3c1a8 --- /dev/null +++ b/contracts/mixnet/schema/raw/response_to_get_rewarded_set.json @@ -0,0 +1,61 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "PagedRewardedSetResponse", + "description": "Response containing paged list of all mixnodes in the rewarded set.", + "type": "object", + "required": [ + "nodes" + ], + "properties": { + "nodes": { + "description": "Nodes in the current rewarded set.", + "type": "array", + "items": { + "type": "array", + "items": [ + { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + { + "$ref": "#/definitions/RewardedSetNodeStatus" + } + ], + "maxItems": 2, + "minItems": 2 + } + }, + "start_next_after": { + "description": "Field indicating paging information for the following queries if the caller wishes to get further entries.", + "type": [ + "integer", + "null" + ], + "format": "uint32", + "minimum": 0.0 + } + }, + "additionalProperties": false, + "definitions": { + "RewardedSetNodeStatus": { + "description": "Current state of given node in the rewarded set.", + "oneOf": [ + { + "description": "Node that is currently active, i.e. is expected to be used by clients for mixing packets.", + "type": "string", + "enum": [ + "active" + ] + }, + { + "description": "Node that is currently in standby, i.e. it's present in the rewarded set but is not active.", + "type": "string", + "enum": [ + "standby" + ] + } + ] + } + } +} diff --git a/contracts/mixnet/schema/raw/response_to_get_rewarding_params.json b/contracts/mixnet/schema/raw/response_to_get_rewarding_params.json new file mode 100644 index 0000000000..8840bc31f2 --- /dev/null +++ b/contracts/mixnet/schema/raw/response_to_get_rewarding_params.json @@ -0,0 +1,129 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "RewardingParams", + "description": "Parameters used for reward calculation.", + "type": "object", + "required": [ + "active_set_size", + "interval", + "rewarded_set_size" + ], + "properties": { + "active_set_size": { + "description": "The expected number of mixnodes in the active set.", + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "interval": { + "description": "Parameters that should remain unchanged throughout an interval.", + "allOf": [ + { + "$ref": "#/definitions/IntervalRewardParams" + } + ] + }, + "rewarded_set_size": { + "description": "The expected number of mixnodes in the rewarded set (i.e. active + standby).", + "type": "integer", + "format": "uint32", + "minimum": 0.0 + } + }, + "additionalProperties": false, + "definitions": { + "Decimal": { + "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", + "type": "string" + }, + "IntervalRewardParams": { + "description": "Parameters required by the mix-mining reward distribution that do not change during an interval.", + "type": "object", + "required": [ + "active_set_work_factor", + "epoch_reward_budget", + "interval_pool_emission", + "reward_pool", + "stake_saturation_point", + "staking_supply", + "staking_supply_scale_factor", + "sybil_resistance" + ], + "properties": { + "active_set_work_factor": { + "description": "Current active set work factor. It is not really expected to be changing very often. As a matter of fact, unless there's a very specific reason, it should remain constant.", + "allOf": [ + { + "$ref": "#/definitions/Decimal" + } + ] + }, + "epoch_reward_budget": { + "description": "Current value of the computed reward budget per epoch, per node. It is expected to be constant throughout the interval.", + "allOf": [ + { + "$ref": "#/definitions/Decimal" + } + ] + }, + "interval_pool_emission": { + "description": "Current maximum interval pool emission. Assuming all nodes in the rewarded set are fully saturated and have 100% performance, this % of the reward pool would get distributed in rewards to all operators and its delegators. It is not really expected to be changing very often. As a matter of fact, unless there's a very specific reason, it should remain constant.", + "allOf": [ + { + "$ref": "#/definitions/Percent" + } + ] + }, + "reward_pool": { + "description": "Current value of the rewarding pool. It is expected to be constant throughout the interval.", + "allOf": [ + { + "$ref": "#/definitions/Decimal" + } + ] + }, + "stake_saturation_point": { + "description": "Current value of the stake saturation point. It is expected to be constant throughout the interval.", + "allOf": [ + { + "$ref": "#/definitions/Decimal" + } + ] + }, + "staking_supply": { + "description": "Current value of the staking supply. It is expected to be constant throughout the interval.", + "allOf": [ + { + "$ref": "#/definitions/Decimal" + } + ] + }, + "staking_supply_scale_factor": { + "description": "Defines the percentage of stake needed to reach saturation for all of the nodes in the rewarded set. Also known as `beta`.", + "allOf": [ + { + "$ref": "#/definitions/Percent" + } + ] + }, + "sybil_resistance": { + "description": "Current value of the sybil resistance percent (`alpha`). It is not really expected to be changing very often. As a matter of fact, unless there's a very specific reason, it should remain constant.", + "allOf": [ + { + "$ref": "#/definitions/Percent" + } + ] + } + }, + "additionalProperties": false + }, + "Percent": { + "description": "Percent represents a value between 0 and 100% (i.e. between 0.0 and 1.0)", + "allOf": [ + { + "$ref": "#/definitions/Decimal" + } + ] + } + } +} diff --git a/contracts/mixnet/schema/raw/response_to_get_rewarding_validator_address.json b/contracts/mixnet/schema/raw/response_to_get_rewarding_validator_address.json new file mode 100644 index 0000000000..f689acebfd --- /dev/null +++ b/contracts/mixnet/schema/raw/response_to_get_rewarding_validator_address.json @@ -0,0 +1,5 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "String", + "type": "string" +} diff --git a/contracts/mixnet/schema/raw/response_to_get_signing_nonce.json b/contracts/mixnet/schema/raw/response_to_get_signing_nonce.json new file mode 100644 index 0000000000..de85c3e56e --- /dev/null +++ b/contracts/mixnet/schema/raw/response_to_get_signing_nonce.json @@ -0,0 +1,7 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "uint32", + "type": "integer", + "format": "uint32", + "minimum": 0.0 +} diff --git a/contracts/mixnet/schema/raw/response_to_get_stake_saturation.json b/contracts/mixnet/schema/raw/response_to_get_stake_saturation.json new file mode 100644 index 0000000000..4a86c03934 --- /dev/null +++ b/contracts/mixnet/schema/raw/response_to_get_stake_saturation.json @@ -0,0 +1,46 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "StakeSaturationResponse", + "description": "Response containing the current state of the stake saturation of a mixnode with the provided id.", + "type": "object", + "required": [ + "mix_id" + ], + "properties": { + "current_saturation": { + "description": "The current stake saturation of this node that is indirectly used in reward calculation formulas. Note that it can't be larger than 1.", + "anyOf": [ + { + "$ref": "#/definitions/Decimal" + }, + { + "type": "null" + } + ] + }, + "mix_id": { + "description": "Id of the requested mixnode.", + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "uncapped_saturation": { + "description": "The current, absolute, stake saturation of this node. Note that as the name suggests it can be larger than 1. However, anything beyond that value has no effect on the total node reward.", + "anyOf": [ + { + "$ref": "#/definitions/Decimal" + }, + { + "type": "null" + } + ] + } + }, + "additionalProperties": false, + "definitions": { + "Decimal": { + "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", + "type": "string" + } + } +} diff --git a/contracts/mixnet/schema/raw/response_to_get_state.json b/contracts/mixnet/schema/raw/response_to_get_state.json new file mode 100644 index 0000000000..0888e30787 --- /dev/null +++ b/contracts/mixnet/schema/raw/response_to_get_state.json @@ -0,0 +1,115 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ContractState", + "description": "The current state of the mixnet contract.", + "type": "object", + "required": [ + "owner", + "params", + "rewarding_denom", + "rewarding_validator_address", + "vesting_contract_address" + ], + "properties": { + "owner": { + "description": "Address of the contract owner.", + "allOf": [ + { + "$ref": "#/definitions/Addr" + } + ] + }, + "params": { + "description": "Contract parameters that could be adjusted in a transaction the contract admin.", + "allOf": [ + { + "$ref": "#/definitions/ContractStateParams" + } + ] + }, + "rewarding_denom": { + "description": "The expected denom used for rewarding (and realistically any other operation). Default: `unym`", + "type": "string" + }, + "rewarding_validator_address": { + "description": "Address of \"rewarding validator\" (nym-api) that's allowed to send any rewarding-related transactions.", + "allOf": [ + { + "$ref": "#/definitions/Addr" + } + ] + }, + "vesting_contract_address": { + "description": "Address of the vesting contract to which the mixnet contract would be sending all track-related messages.", + "allOf": [ + { + "$ref": "#/definitions/Addr" + } + ] + } + }, + "additionalProperties": false, + "definitions": { + "Addr": { + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + }, + "Coin": { + "type": "object", + "required": [ + "amount", + "denom" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "denom": { + "type": "string" + } + } + }, + "ContractStateParams": { + "description": "Contract parameters that could be adjusted in a transaction by the contract admin.", + "type": "object", + "required": [ + "minimum_gateway_pledge", + "minimum_mixnode_pledge" + ], + "properties": { + "minimum_gateway_pledge": { + "description": "Minimum amount a gateway must pledge to get into the system.", + "allOf": [ + { + "$ref": "#/definitions/Coin" + } + ] + }, + "minimum_mixnode_delegation": { + "description": "Minimum amount a delegator must stake in orders for his delegation to get accepted.", + "anyOf": [ + { + "$ref": "#/definitions/Coin" + }, + { + "type": "null" + } + ] + }, + "minimum_mixnode_pledge": { + "description": "Minimum amount a mixnode must pledge to get into the system.", + "allOf": [ + { + "$ref": "#/definitions/Coin" + } + ] + } + }, + "additionalProperties": false + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } +} diff --git a/contracts/mixnet/schema/raw/response_to_get_state_params.json b/contracts/mixnet/schema/raw/response_to_get_state_params.json new file mode 100644 index 0000000000..6e05de3035 --- /dev/null +++ b/contracts/mixnet/schema/raw/response_to_get_state_params.json @@ -0,0 +1,61 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ContractStateParams", + "description": "Contract parameters that could be adjusted in a transaction by the contract admin.", + "type": "object", + "required": [ + "minimum_gateway_pledge", + "minimum_mixnode_pledge" + ], + "properties": { + "minimum_gateway_pledge": { + "description": "Minimum amount a gateway must pledge to get into the system.", + "allOf": [ + { + "$ref": "#/definitions/Coin" + } + ] + }, + "minimum_mixnode_delegation": { + "description": "Minimum amount a delegator must stake in orders for his delegation to get accepted.", + "anyOf": [ + { + "$ref": "#/definitions/Coin" + }, + { + "type": "null" + } + ] + }, + "minimum_mixnode_pledge": { + "description": "Minimum amount a mixnode must pledge to get into the system.", + "allOf": [ + { + "$ref": "#/definitions/Coin" + } + ] + } + }, + "additionalProperties": false, + "definitions": { + "Coin": { + "type": "object", + "required": [ + "amount", + "denom" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "denom": { + "type": "string" + } + } + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } +} diff --git a/contracts/mixnet/schema/raw/response_to_get_unbonded_mix_node_information.json b/contracts/mixnet/schema/raw/response_to_get_unbonded_mix_node_information.json new file mode 100644 index 0000000000..9fa546e39c --- /dev/null +++ b/contracts/mixnet/schema/raw/response_to_get_unbonded_mix_node_information.json @@ -0,0 +1,76 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "UnbondedMixnodeResponse", + "description": "Response containing basic information of an unbonded mixnode with the provided id.", + "type": "object", + "required": [ + "mix_id" + ], + "properties": { + "mix_id": { + "description": "Id of the requested mixnode.", + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "unbonded_info": { + "description": "If there existed a mixnode with the provided id, this field contains its basic information.", + "anyOf": [ + { + "$ref": "#/definitions/UnbondedMixnode" + }, + { + "type": "null" + } + ] + } + }, + "additionalProperties": false, + "definitions": { + "Addr": { + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + }, + "UnbondedMixnode": { + "description": "Basic information of a node that used to be part of the mix network but has already unbonded.", + "type": "object", + "required": [ + "identity_key", + "owner", + "unbonding_height" + ], + "properties": { + "identity_key": { + "description": "Base58-encoded ed25519 EdDSA public key.", + "type": "string" + }, + "owner": { + "description": "Address of the owner of this mixnode.", + "allOf": [ + { + "$ref": "#/definitions/Addr" + } + ] + }, + "proxy": { + "description": "Entity who bonded this mixnode on behalf of the owner. If exists, it's most likely the address of the vesting contract.", + "anyOf": [ + { + "$ref": "#/definitions/Addr" + }, + { + "type": "null" + } + ] + }, + "unbonding_height": { + "description": "Block height at which this mixnode has unbonded.", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + } +} diff --git a/contracts/mixnet/schema/raw/response_to_get_unbonded_mix_nodes.json b/contracts/mixnet/schema/raw/response_to_get_unbonded_mix_nodes.json new file mode 100644 index 0000000000..6209bcc677 --- /dev/null +++ b/contracts/mixnet/schema/raw/response_to_get_unbonded_mix_nodes.json @@ -0,0 +1,94 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "PagedUnbondedMixnodesResponse", + "description": "Response containing paged list of all mixnodes that have ever unbonded.", + "type": "object", + "required": [ + "nodes", + "per_page" + ], + "properties": { + "nodes": { + "description": "The past ids of unbonded mixnodes alongside their basic information such as the owner or the identity key.", + "type": "array", + "items": { + "type": "array", + "items": [ + { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + { + "$ref": "#/definitions/UnbondedMixnode" + } + ], + "maxItems": 2, + "minItems": 2 + } + }, + "per_page": { + "description": "Maximum number of entries that could be included in a response. `per_page <= nodes.len()`", + "type": "integer", + "format": "uint", + "minimum": 0.0 + }, + "start_next_after": { + "description": "Field indicating paging information for the following queries if the caller wishes to get further entries.", + "type": [ + "integer", + "null" + ], + "format": "uint32", + "minimum": 0.0 + } + }, + "additionalProperties": false, + "definitions": { + "Addr": { + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + }, + "UnbondedMixnode": { + "description": "Basic information of a node that used to be part of the mix network but has already unbonded.", + "type": "object", + "required": [ + "identity_key", + "owner", + "unbonding_height" + ], + "properties": { + "identity_key": { + "description": "Base58-encoded ed25519 EdDSA public key.", + "type": "string" + }, + "owner": { + "description": "Address of the owner of this mixnode.", + "allOf": [ + { + "$ref": "#/definitions/Addr" + } + ] + }, + "proxy": { + "description": "Entity who bonded this mixnode on behalf of the owner. If exists, it's most likely the address of the vesting contract.", + "anyOf": [ + { + "$ref": "#/definitions/Addr" + }, + { + "type": "null" + } + ] + }, + "unbonding_height": { + "description": "Block height at which this mixnode has unbonded.", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + } +} diff --git a/contracts/mixnet/schema/raw/response_to_get_unbonded_mix_nodes_by_identity_key.json b/contracts/mixnet/schema/raw/response_to_get_unbonded_mix_nodes_by_identity_key.json new file mode 100644 index 0000000000..6209bcc677 --- /dev/null +++ b/contracts/mixnet/schema/raw/response_to_get_unbonded_mix_nodes_by_identity_key.json @@ -0,0 +1,94 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "PagedUnbondedMixnodesResponse", + "description": "Response containing paged list of all mixnodes that have ever unbonded.", + "type": "object", + "required": [ + "nodes", + "per_page" + ], + "properties": { + "nodes": { + "description": "The past ids of unbonded mixnodes alongside their basic information such as the owner or the identity key.", + "type": "array", + "items": { + "type": "array", + "items": [ + { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + { + "$ref": "#/definitions/UnbondedMixnode" + } + ], + "maxItems": 2, + "minItems": 2 + } + }, + "per_page": { + "description": "Maximum number of entries that could be included in a response. `per_page <= nodes.len()`", + "type": "integer", + "format": "uint", + "minimum": 0.0 + }, + "start_next_after": { + "description": "Field indicating paging information for the following queries if the caller wishes to get further entries.", + "type": [ + "integer", + "null" + ], + "format": "uint32", + "minimum": 0.0 + } + }, + "additionalProperties": false, + "definitions": { + "Addr": { + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + }, + "UnbondedMixnode": { + "description": "Basic information of a node that used to be part of the mix network but has already unbonded.", + "type": "object", + "required": [ + "identity_key", + "owner", + "unbonding_height" + ], + "properties": { + "identity_key": { + "description": "Base58-encoded ed25519 EdDSA public key.", + "type": "string" + }, + "owner": { + "description": "Address of the owner of this mixnode.", + "allOf": [ + { + "$ref": "#/definitions/Addr" + } + ] + }, + "proxy": { + "description": "Entity who bonded this mixnode on behalf of the owner. If exists, it's most likely the address of the vesting contract.", + "anyOf": [ + { + "$ref": "#/definitions/Addr" + }, + { + "type": "null" + } + ] + }, + "unbonding_height": { + "description": "Block height at which this mixnode has unbonded.", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + } +} diff --git a/contracts/mixnet/schema/raw/response_to_get_unbonded_mix_nodes_by_owner.json b/contracts/mixnet/schema/raw/response_to_get_unbonded_mix_nodes_by_owner.json new file mode 100644 index 0000000000..6209bcc677 --- /dev/null +++ b/contracts/mixnet/schema/raw/response_to_get_unbonded_mix_nodes_by_owner.json @@ -0,0 +1,94 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "PagedUnbondedMixnodesResponse", + "description": "Response containing paged list of all mixnodes that have ever unbonded.", + "type": "object", + "required": [ + "nodes", + "per_page" + ], + "properties": { + "nodes": { + "description": "The past ids of unbonded mixnodes alongside their basic information such as the owner or the identity key.", + "type": "array", + "items": { + "type": "array", + "items": [ + { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + { + "$ref": "#/definitions/UnbondedMixnode" + } + ], + "maxItems": 2, + "minItems": 2 + } + }, + "per_page": { + "description": "Maximum number of entries that could be included in a response. `per_page <= nodes.len()`", + "type": "integer", + "format": "uint", + "minimum": 0.0 + }, + "start_next_after": { + "description": "Field indicating paging information for the following queries if the caller wishes to get further entries.", + "type": [ + "integer", + "null" + ], + "format": "uint32", + "minimum": 0.0 + } + }, + "additionalProperties": false, + "definitions": { + "Addr": { + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + }, + "UnbondedMixnode": { + "description": "Basic information of a node that used to be part of the mix network but has already unbonded.", + "type": "object", + "required": [ + "identity_key", + "owner", + "unbonding_height" + ], + "properties": { + "identity_key": { + "description": "Base58-encoded ed25519 EdDSA public key.", + "type": "string" + }, + "owner": { + "description": "Address of the owner of this mixnode.", + "allOf": [ + { + "$ref": "#/definitions/Addr" + } + ] + }, + "proxy": { + "description": "Entity who bonded this mixnode on behalf of the owner. If exists, it's most likely the address of the vesting contract.", + "anyOf": [ + { + "$ref": "#/definitions/Addr" + }, + { + "type": "null" + } + ] + }, + "unbonding_height": { + "description": "Block height at which this mixnode has unbonded.", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + } +} diff --git a/contracts/mixnet/src/bin/schema.rs b/contracts/mixnet/src/bin/schema.rs new file mode 100644 index 0000000000..c1e48ba16a --- /dev/null +++ b/contracts/mixnet/src/bin/schema.rs @@ -0,0 +1,14 @@ +// Copyright 2023 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use cosmwasm_schema::write_api; +use mixnet_contract_common::{ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg}; + +fn main() { + write_api! { + instantiate: InstantiateMsg, + query: QueryMsg, + execute: ExecuteMsg, + migrate: MigrateMsg, + } +} diff --git a/contracts/mixnet/src/contract.rs b/contracts/mixnet/src/contract.rs index f380b316f2..892f4d2298 100644 --- a/contracts/mixnet/src/contract.rs +++ b/contracts/mixnet/src/contract.rs @@ -404,12 +404,12 @@ pub fn query( to_binary(&crate::mixnet_contract_settings::queries::query_contract_version()) } QueryMsg::GetCW2ContractVersion {} => to_binary(&cw2::get_contract_version(deps.storage)?), - QueryMsg::GetStateParams {} => to_binary( - &crate::mixnet_contract_settings::queries::query_contract_settings_params(deps)?, - ), QueryMsg::GetRewardingValidatorAddress {} => to_binary( &crate::mixnet_contract_settings::queries::query_rewarding_validator_address(deps)?, ), + QueryMsg::GetStateParams {} => to_binary( + &crate::mixnet_contract_settings::queries::query_contract_settings_params(deps)?, + ), QueryMsg::GetState {} => { to_binary(&crate::mixnet_contract_settings::queries::query_contract_state(deps)?) } @@ -594,6 +594,8 @@ pub fn query( QueryMsg::GetNumberOfPendingEvents {} => to_binary( &crate::interval::queries::query_number_of_pending_events(deps)?, ), + + // signing-related QueryMsg::GetSigningNonce { address } => to_binary( &crate::signing::queries::query_current_signing_nonce(deps, address)?, ), diff --git a/contracts/mixnet/src/families/queries.rs b/contracts/mixnet/src/families/queries.rs index dc4799f0b9..96c7f79e23 100644 --- a/contracts/mixnet/src/families/queries.rs +++ b/contracts/mixnet/src/families/queries.rs @@ -1,47 +1,65 @@ // Copyright 2022-2023 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use super::storage::{families, get_family, get_members, MEMBERS}; +use super::storage::{families, get_members, must_get_family, MEMBERS}; use crate::constants::{FAMILIES_DEFAULT_RETRIEVAL_LIMIT, FAMILIES_MAX_RETRIEVAL_LIMIT}; +use crate::families::storage::must_get_family_by_label; use cosmwasm_std::{Order, Storage}; use cw_storage_plus::Bound; -use mixnet_contract_common::families::{Family, FamilyHead}; +use mixnet_contract_common::families::{ + Family, FamilyByHeadResponse, FamilyByLabelResponse, FamilyHead, FamilyMembersByHeadResponse, + PagedFamiliesResponse, PagedMembersResponse, +}; use mixnet_contract_common::{error::MixnetContractError, IdentityKeyRef}; -use mixnet_contract_common::{IdentityKey, PagedFamiliesResponse, PagedMembersResponse}; -use std::collections::HashSet; +use mixnet_contract_common::{FamilyMembersByLabelResponse, IdentityKey}; pub fn get_family_by_label( label: String, storage: &dyn Storage, -) -> Result, MixnetContractError> { - Ok(families().idx.label.item(storage, label)?.map(|o| o.1)) +) -> Result { + let family = families() + .idx + .label + .item(storage, label.clone())? + .map(|o| o.1); + Ok(FamilyByLabelResponse { label, family }) } pub fn get_family_by_head( head: IdentityKeyRef<'_>, storage: &dyn Storage, -) -> Result, MixnetContractError> { - Ok(families().may_load(storage, head.to_string())?) +) -> Result { + let family = families().may_load(storage, head.to_string())?; + Ok(FamilyByHeadResponse { + head: FamilyHead::new(head), + family, + }) } +// TODO: this should be returning a paged response! pub fn get_family_members_by_head( head: IdentityKeyRef<'_>, storage: &dyn Storage, -) -> Result, MixnetContractError> { +) -> Result { let family_head = FamilyHead::new(head); - let family = get_family(&family_head, storage)?; - get_members(&family, storage) + let family = must_get_family(&family_head, storage)?; + let members = get_members(&family, storage)?; + + Ok(FamilyMembersByHeadResponse { + head: family.head().to_owned(), + members, + }) } +// TODO: this should be returning a paged response! pub fn get_family_members_by_label( label: String, storage: &dyn Storage, -) -> Result>, MixnetContractError> { - if let Some(family) = families().idx.label.item(storage, label)?.map(|o| o.1) { - Ok(Some(get_members(&family, storage)?)) - } else { - Ok(None) - } +) -> Result { + let family = must_get_family_by_label(label.clone(), storage)?; + let members = get_members(&family, storage)?; + + Ok(FamilyMembersByLabelResponse { label, members }) } pub fn get_all_families_paged( diff --git a/contracts/mixnet/src/families/storage.rs b/contracts/mixnet/src/families/storage.rs index 1bdd421206..05b9566b7d 100644 --- a/contracts/mixnet/src/families/storage.rs +++ b/contracts/mixnet/src/families/storage.rs @@ -5,12 +5,13 @@ use cosmwasm_std::{Order, Storage}; use cw_storage_plus::{Index, IndexList, IndexedMap, Map, UniqueIndex}; use mixnet_contract_common::families::{Family, FamilyHead}; use mixnet_contract_common::{error::MixnetContractError, IdentityKey, IdentityKeyRef}; -use std::collections::HashSet; use crate::constants::{FAMILIES_INDEX_NAMESPACE, FAMILIES_MAP_NAMESPACE, MEMBERS_MAP_NAMESPACE}; +type FamilyHeadKey = IdentityKey; + pub struct FamilyIndex<'a> { - pub label: UniqueIndex<'a, String, Family>, + pub label: UniqueIndex<'a, FamilyHeadKey, Family>, } impl<'a> IndexList for FamilyIndex<'a> { @@ -21,7 +22,7 @@ impl<'a> IndexList for FamilyIndex<'a> { } // storage access function. -pub fn families<'a>() -> IndexedMap<'a, String, Family, FamilyIndex<'a>> { +pub fn families<'a>() -> IndexedMap<'a, FamilyHeadKey, Family, FamilyIndex<'a>> { let indexes = FamilyIndex { label: UniqueIndex::new(|d| d.label().to_string(), FAMILIES_INDEX_NAMESPACE), }; @@ -30,10 +31,11 @@ pub fn families<'a>() -> IndexedMap<'a, String, Family, FamilyIndex<'a>> { pub const MEMBERS: Map = Map::new(MEMBERS_MAP_NAMESPACE); +// TODO: this introduces an unbounded query. We should redesign it. pub fn get_members( family: &Family, store: &dyn Storage, -) -> Result, MixnetContractError> { +) -> Result, MixnetContractError> { Ok(MEMBERS .range(store, None, None, Order::Ascending) .filter_map(|res| res.ok()) @@ -42,15 +44,29 @@ pub fn get_members( .collect()) } -pub fn get_family(head: &FamilyHead, store: &dyn Storage) -> Result { +pub fn must_get_family( + head: &FamilyHead, + store: &dyn Storage, +) -> Result { let key = head.identity(); - if let Some(family) = families().may_load(store, key.to_string())? { - Ok(family) - } else { - Err(MixnetContractError::FamilyDoesNotExist { + + families() + .may_load(store, key.to_string())? + .ok_or(MixnetContractError::FamilyDoesNotExist { head: head.identity().to_string(), }) - } +} + +pub fn must_get_family_by_label( + label: String, + store: &dyn Storage, +) -> Result { + families() + .idx + .label + .item(store, label.clone())? + .map(|record| record.1) + .ok_or(MixnetContractError::FamilyLabelDoesNotExist { label }) } pub fn save_family(f: &Family, store: &mut dyn Storage) -> Result<(), MixnetContractError> { diff --git a/contracts/mixnet/src/families/transactions.rs b/contracts/mixnet/src/families/transactions.rs index 5a6a9b28e0..912cbc7146 100644 --- a/contracts/mixnet/src/families/transactions.rs +++ b/contracts/mixnet/src/families/transactions.rs @@ -2,7 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 use super::storage::{ - add_family_member, get_family, is_any_member, is_family_member, remove_family_member, + add_family_member, is_any_member, is_family_member, must_get_family, remove_family_member, save_family, }; use crate::families::queries::get_family_by_label; @@ -48,12 +48,15 @@ fn _try_create_family( let family_head = FamilyHead::new(existing_bond.identity()); // can't overwrite existing family - if get_family(&family_head, deps.storage).is_ok() { + if must_get_family(&family_head, deps.storage).is_ok() { return Err(MixnetContractError::FamilyCanHaveOnlyOne); } // the label must be unique - if get_family_by_label(label.clone(), deps.storage)?.is_some() { + if get_family_by_label(label.clone(), deps.storage)? + .family + .is_some() + { return Err(MixnetContractError::FamilyWithLabelExists(label)); } @@ -118,7 +121,7 @@ fn _try_join_family( join_permit, )?; - let family = get_family(&family_head, deps.storage)?; + let family = must_get_family(&family_head, deps.storage)?; add_family_member(&family, deps.storage, existing_bond.identity())?; @@ -162,7 +165,7 @@ fn _try_leave_family( }); } - let family = get_family(&family_head, deps.storage)?; + let family = must_get_family(&family_head, deps.storage)?; if !is_family_member(deps.storage, &family, existing_bond.identity())? { return Err(MixnetContractError::NotAMember { head: family_head.identity().to_string(), @@ -215,7 +218,7 @@ fn _try_head_kick_member( // get the family details let family_head = FamilyHead::new(head_bond.identity()); - let family = get_family(&family_head, deps.storage)?; + let family = must_get_family(&family_head, deps.storage)?; // make sure the member we're trying to kick is an actual member if !is_family_member(deps.storage, &family, &member)? { @@ -290,7 +293,7 @@ mod test { try_create_family(test.deps_mut(), mock_info(head, &[]), "test".to_string()).unwrap(); let family_head = FamilyHead::new(&head_mixnode.identity_key); - assert!(get_family(&family_head, test.deps().storage).is_ok()); + assert!(must_get_family(&family_head, test.deps().storage).is_ok()); let nope = try_create_family( test.deps_mut(), @@ -306,12 +309,15 @@ mod test { }, } - let family = get_family_by_label("test".to_string(), test.deps().storage).unwrap(); + let family = get_family_by_label("test".to_string(), test.deps().storage) + .unwrap() + .family; assert!(family.is_some()); assert_eq!(family.unwrap().head_identity(), family_head.identity()); let family = get_family_by_head(family_head.identity(), test.deps().storage) .unwrap() + .family .unwrap(); assert_eq!(family.head_identity(), family_head.identity()); @@ -326,7 +332,7 @@ mod test { ) .unwrap(); - let family = get_family(&family_head, test.deps().storage).unwrap(); + let family = must_get_family(&family_head, test.deps().storage).unwrap(); assert!( is_family_member(test.deps().storage, &family, &member_mixnode.identity_key).unwrap() @@ -334,7 +340,7 @@ mod test { try_leave_family(test.deps_mut(), mock_info(member, &[]), family_head.clone()).unwrap(); - let family = get_family(&family_head, test.deps().storage).unwrap(); + let family = must_get_family(&family_head, test.deps().storage).unwrap(); assert!( !is_family_member(test.deps().storage, &family, &member_mixnode.identity_key).unwrap() ); @@ -350,7 +356,7 @@ mod test { ) .unwrap(); - let family = get_family(&family_head, test.deps().storage).unwrap(); + let family = must_get_family(&family_head, test.deps().storage).unwrap(); assert!( is_family_member(test.deps().storage, &family, &member_mixnode.identity_key).unwrap() @@ -363,7 +369,7 @@ mod test { ) .unwrap(); - let family = get_family(&family_head, test.deps().storage).unwrap(); + let family = must_get_family(&family_head, test.deps().storage).unwrap(); assert!( !is_family_member(test.deps().storage, &family, &member_mixnode.identity_key).unwrap() ); @@ -429,7 +435,7 @@ mod test { ); let head_identity = head_keys.public_key().to_base58_string(); - let family_head = FamilyHead::new(&head_identity); + let family_head = FamilyHead::new(head_identity); let res = try_join_family_on_behalf( test.deps_mut(), mock_info(illegal_proxy.as_ref(), &[]), @@ -475,7 +481,7 @@ mod test { ); let head_identity = head_keys.public_key().to_base58_string(); - let family_head = FamilyHead::new(&head_identity); + let family_head = FamilyHead::new(head_identity); try_join_family_on_behalf( test.deps_mut(), mock_info(vesting_contract.as_ref(), &[]), @@ -529,7 +535,7 @@ mod test { ); let head_identity = head_keys.public_key().to_base58_string(); - let family_head = FamilyHead::new(&head_identity); + let family_head = FamilyHead::new(head_identity); try_join_family_on_behalf( test.deps_mut(), diff --git a/contracts/mixnet/src/mixnodes/queries.rs b/contracts/mixnet/src/mixnodes/queries.rs index 5bf845c4a9..5d7b61054d 100644 --- a/contracts/mixnet/src/mixnodes/queries.rs +++ b/contracts/mixnet/src/mixnodes/queries.rs @@ -19,8 +19,8 @@ use mixnet_contract_common::mixnode::{ PagedUnbondedMixnodesResponse, StakeSaturationResponse, UnbondedMixnodeResponse, }; use mixnet_contract_common::{ - IdentityKey, LayerDistribution, MixId, MixOwnershipResponse, MixnodeDetailsResponse, - PagedMixnodeBondsResponse, + IdentityKey, LayerDistribution, MixId, MixOwnershipResponse, MixnodeDetailsByIdentityResponse, + MixnodeDetailsResponse, PagedMixnodeBondsResponse, }; pub fn query_mixnode_bonds_paged( @@ -189,12 +189,16 @@ pub fn query_mixnode_details(deps: Deps<'_>, mix_id: MixId) -> StdResult, - mix_identity: IdentityKey, -) -> StdResult> { - get_mixnode_details_by_identity(deps.storage, mix_identity) + identity_key: IdentityKey, +) -> StdResult { + let mixnode_details = get_mixnode_details_by_identity(deps.storage, identity_key.clone())?; + + Ok(MixnodeDetailsByIdentityResponse { + identity_key, + mixnode_details, + }) } pub fn query_mixnode_rewarding_details( @@ -1110,7 +1114,9 @@ pub(crate) mod tests { let mut test = TestSetup::new(); // no node under this identity - let res = query_mixnode_details_by_identity(test.deps(), "foomp".into()).unwrap(); + let res = query_mixnode_details_by_identity(test.deps(), "foomp".into()) + .unwrap() + .mixnode_details; assert!(res.is_none()); // it exists @@ -1122,7 +1128,9 @@ pub(crate) mod tests { .unwrap(); let mix_identity = expected.bond_information.identity(); - let res = query_mixnode_details_by_identity(test.deps(), mix_identity.into()).unwrap(); + let res = query_mixnode_details_by_identity(test.deps(), mix_identity.into()) + .unwrap() + .mixnode_details; assert_eq!(expected, res.unwrap()); } diff --git a/contracts/mixnet/src/support/tests/mod.rs b/contracts/mixnet/src/support/tests/mod.rs index 6b0e838a1a..851d394bfe 100644 --- a/contracts/mixnet/src/support/tests/mod.rs +++ b/contracts/mixnet/src/support/tests/mod.rs @@ -224,7 +224,7 @@ pub mod test_helpers { let join_permit = self.generate_family_join_permit(head_keys, &member_identity, vesting); - let family_head = FamilyHead::new(&head_identity); + let family_head = FamilyHead::new(head_identity); try_join_family( self.deps_mut(), diff --git a/contracts/multisig/cw3-flex-multisig/.cargo/config b/contracts/multisig/cw3-flex-multisig/.cargo/config index 8d4bc738b1..dbc331ef7a 100644 --- a/contracts/multisig/cw3-flex-multisig/.cargo/config +++ b/contracts/multisig/cw3-flex-multisig/.cargo/config @@ -1,6 +1,6 @@ [alias] -wasm = "build --release --target wasm32-unknown-unknown" -wasm-debug = "build --target wasm32-unknown-unknown" +wasm = "build --release --lib --target wasm32-unknown-unknown" +wasm-debug = "build --lib --target wasm32-unknown-unknown" unit-test = "test --lib" integration-test = "test --test integration" -schema = "run --example schema" +schema = "run --bin schema --features=schema-gen" \ No newline at end of file diff --git a/contracts/multisig/cw3-flex-multisig/Cargo.toml b/contracts/multisig/cw3-flex-multisig/Cargo.toml index b849828069..bd0e980bde 100644 --- a/contracts/multisig/cw3-flex-multisig/Cargo.toml +++ b/contracts/multisig/cw3-flex-multisig/Cargo.toml @@ -9,6 +9,10 @@ repository = "https://github.com/CosmWasm/cw-plus" homepage = "https://cosmwasm.com" documentation = "https://docs.cosmwasm.com" +[[bin]] +name = "schema" +required-features = ["schema-gen"] + [lib] crate-type = ["cdylib", "rlib"] @@ -16,6 +20,7 @@ crate-type = ["cdylib", "rlib"] backtraces = ["cosmwasm-std/backtraces"] # use library feature to disable all instantiate/execute/query exports library = [] +schema-gen = ["cosmwasm-schema"] [dependencies] cw-utils = { workspace = true } @@ -25,8 +30,8 @@ cw3-fixed-multisig = { workspace = true, features = ["library"] } cw4 = { workspace = true } cw20 = { workspace = true } cw-storage-plus = { workspace = true } +cosmwasm-schema = { workspace = true, optional = true } cosmwasm-std = { workspace = true } -schemars = "0.8.1" nym-group-contract-common = { path = "../../../common/cosmwasm-smart-contracts/group-contract" } nym-multisig-contract-common = { path= "../../../common/cosmwasm-smart-contracts/multisig-contract" } diff --git a/contracts/multisig/cw3-flex-multisig/Makefile b/contracts/multisig/cw3-flex-multisig/Makefile new file mode 100644 index 0000000000..f138e2c879 --- /dev/null +++ b/contracts/multisig/cw3-flex-multisig/Makefile @@ -0,0 +1,2 @@ +generate-schema: + cargo schema \ No newline at end of file diff --git a/contracts/multisig/cw3-flex-multisig/schema/cw3-flex-multisig.json b/contracts/multisig/cw3-flex-multisig/schema/cw3-flex-multisig.json new file mode 100644 index 0000000000..7f49430331 --- /dev/null +++ b/contracts/multisig/cw3-flex-multisig/schema/cw3-flex-multisig.json @@ -0,0 +1,5201 @@ +{ + "contract_name": "cw3-flex-multisig", + "contract_version": "1.0.0", + "idl_version": "1.0.0", + "instantiate": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "InstantiateMsg", + "type": "object", + "required": [ + "coconut_bandwidth_contract_address", + "coconut_dkg_contract_address", + "group_addr", + "max_voting_period", + "threshold" + ], + "properties": { + "coconut_bandwidth_contract_address": { + "type": "string" + }, + "coconut_dkg_contract_address": { + "type": "string" + }, + "executor": { + "anyOf": [ + { + "$ref": "#/definitions/Executor" + }, + { + "type": "null" + } + ] + }, + "group_addr": { + "type": "string" + }, + "max_voting_period": { + "$ref": "#/definitions/Duration" + }, + "proposal_deposit": { + "description": "The cost of creating a proposal (if any).", + "anyOf": [ + { + "$ref": "#/definitions/UncheckedDepositInfo" + }, + { + "type": "null" + } + ] + }, + "threshold": { + "$ref": "#/definitions/Threshold" + } + }, + "additionalProperties": false, + "definitions": { + "Addr": { + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + }, + "Decimal": { + "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", + "type": "string" + }, + "Duration": { + "description": "Duration is a delta of time. You can add it to a BlockInfo or Expiration to move that further in the future. Note that an height-based Duration and a time-based Expiration cannot be combined", + "oneOf": [ + { + "type": "object", + "required": [ + "height" + ], + "properties": { + "height": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + }, + { + "description": "Time in seconds", + "type": "object", + "required": [ + "time" + ], + "properties": { + "time": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + ] + }, + "Executor": { + "description": "Defines who is able to execute proposals once passed", + "oneOf": [ + { + "description": "Any member of the voting group, even with 0 points", + "type": "string", + "enum": [ + "member" + ] + }, + { + "description": "Only the given address", + "type": "object", + "required": [ + "only" + ], + "properties": { + "only": { + "$ref": "#/definitions/Addr" + } + }, + "additionalProperties": false + } + ] + }, + "Threshold": { + "description": "This defines the different ways tallies can happen.\n\nThe total_weight used for calculating success as well as the weights of each individual voter used in tallying should be snapshotted at the beginning of the block at which the proposal starts (this is likely the responsibility of a correct cw4 implementation). See also `ThresholdResponse` in the cw3 spec.", + "oneOf": [ + { + "description": "Declares that a fixed weight of Yes votes is needed to pass. See `ThresholdResponse.AbsoluteCount` in the cw3 spec for details.", + "type": "object", + "required": [ + "absolute_count" + ], + "properties": { + "absolute_count": { + "type": "object", + "required": [ + "weight" + ], + "properties": { + "weight": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Declares a percentage of the total weight that must cast Yes votes in order for a proposal to pass. See `ThresholdResponse.AbsolutePercentage` in the cw3 spec for details.", + "type": "object", + "required": [ + "absolute_percentage" + ], + "properties": { + "absolute_percentage": { + "type": "object", + "required": [ + "percentage" + ], + "properties": { + "percentage": { + "$ref": "#/definitions/Decimal" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Declares a `quorum` of the total votes that must participate in the election in order for the vote to be considered at all. See `ThresholdResponse.ThresholdQuorum` in the cw3 spec for details.", + "type": "object", + "required": [ + "threshold_quorum" + ], + "properties": { + "threshold_quorum": { + "type": "object", + "required": [ + "quorum", + "threshold" + ], + "properties": { + "quorum": { + "$ref": "#/definitions/Decimal" + }, + "threshold": { + "$ref": "#/definitions/Decimal" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + ] + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + }, + "UncheckedDenom": { + "oneOf": [ + { + "type": "object", + "required": [ + "native" + ], + "properties": { + "native": { + "type": "string" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "cw20" + ], + "properties": { + "cw20": { + "type": "string" + } + }, + "additionalProperties": false + } + ] + }, + "UncheckedDepositInfo": { + "description": "Information about the deposit required to create a proposal. For use in messages. To validate, transform into `DepositInfo` via `into_checked()`.", + "type": "object", + "required": [ + "amount", + "denom", + "refund_failed_proposals" + ], + "properties": { + "amount": { + "description": "The number tokens required for payment.", + "allOf": [ + { + "$ref": "#/definitions/Uint128" + } + ] + }, + "denom": { + "description": "The denom of the deposit payment.", + "allOf": [ + { + "$ref": "#/definitions/UncheckedDenom" + } + ] + }, + "refund_failed_proposals": { + "description": "Should failed proposals have their deposits refunded?", + "type": "boolean" + } + }, + "additionalProperties": false + } + } + }, + "execute": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ExecuteMsg", + "oneOf": [ + { + "type": "object", + "required": [ + "propose" + ], + "properties": { + "propose": { + "type": "object", + "required": [ + "description", + "msgs", + "title" + ], + "properties": { + "description": { + "type": "string" + }, + "latest": { + "anyOf": [ + { + "$ref": "#/definitions/Expiration" + }, + { + "type": "null" + } + ] + }, + "msgs": { + "type": "array", + "items": { + "$ref": "#/definitions/CosmosMsg_for_Empty" + } + }, + "title": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "vote" + ], + "properties": { + "vote": { + "type": "object", + "required": [ + "proposal_id", + "vote" + ], + "properties": { + "proposal_id": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "vote": { + "$ref": "#/definitions/Vote" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "execute" + ], + "properties": { + "execute": { + "type": "object", + "required": [ + "proposal_id" + ], + "properties": { + "proposal_id": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "close" + ], + "properties": { + "close": { + "type": "object", + "required": [ + "proposal_id" + ], + "properties": { + "proposal_id": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Handles update hook messages from the group contract", + "type": "object", + "required": [ + "member_changed_hook" + ], + "properties": { + "member_changed_hook": { + "$ref": "#/definitions/MemberChangedHookMsg" + } + }, + "additionalProperties": false + } + ], + "definitions": { + "BankMsg": { + "description": "The message types of the bank module.\n\nSee https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/bank/v1beta1/tx.proto", + "oneOf": [ + { + "description": "Sends native tokens from the contract to the given address.\n\nThis is translated to a [MsgSend](https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/bank/v1beta1/tx.proto#L19-L28). `from_address` is automatically filled with the current contract's address.", + "type": "object", + "required": [ + "send" + ], + "properties": { + "send": { + "type": "object", + "required": [ + "amount", + "to_address" + ], + "properties": { + "amount": { + "type": "array", + "items": { + "$ref": "#/definitions/Coin" + } + }, + "to_address": { + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "This will burn the given coins from the contract's account. There is no Cosmos SDK message that performs this, but it can be done by calling the bank keeper. Important if a contract controls significant token supply that must be retired.", + "type": "object", + "required": [ + "burn" + ], + "properties": { + "burn": { + "type": "object", + "required": [ + "amount" + ], + "properties": { + "amount": { + "type": "array", + "items": { + "$ref": "#/definitions/Coin" + } + } + } + } + }, + "additionalProperties": false + } + ] + }, + "Binary": { + "description": "Binary is a wrapper around Vec to add base64 de/serialization with serde. It also adds some helper methods to help encode inline.\n\nThis is only needed as serde-json-{core,wasm} has a horrible encoding for Vec. See also .", + "type": "string" + }, + "Coin": { + "type": "object", + "required": [ + "amount", + "denom" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "denom": { + "type": "string" + } + } + }, + "CosmosMsg_for_Empty": { + "oneOf": [ + { + "type": "object", + "required": [ + "bank" + ], + "properties": { + "bank": { + "$ref": "#/definitions/BankMsg" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "custom" + ], + "properties": { + "custom": { + "$ref": "#/definitions/Empty" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "staking" + ], + "properties": { + "staking": { + "$ref": "#/definitions/StakingMsg" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "distribution" + ], + "properties": { + "distribution": { + "$ref": "#/definitions/DistributionMsg" + } + }, + "additionalProperties": false + }, + { + "description": "A Stargate message encoded the same way as a protobuf [Any](https://github.com/protocolbuffers/protobuf/blob/master/src/google/protobuf/any.proto). This is the same structure as messages in `TxBody` from [ADR-020](https://github.com/cosmos/cosmos-sdk/blob/master/docs/architecture/adr-020-protobuf-transaction-encoding.md)", + "type": "object", + "required": [ + "stargate" + ], + "properties": { + "stargate": { + "type": "object", + "required": [ + "type_url", + "value" + ], + "properties": { + "type_url": { + "type": "string" + }, + "value": { + "$ref": "#/definitions/Binary" + } + } + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "ibc" + ], + "properties": { + "ibc": { + "$ref": "#/definitions/IbcMsg" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "wasm" + ], + "properties": { + "wasm": { + "$ref": "#/definitions/WasmMsg" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "gov" + ], + "properties": { + "gov": { + "$ref": "#/definitions/GovMsg" + } + }, + "additionalProperties": false + } + ] + }, + "DistributionMsg": { + "description": "The message types of the distribution module.\n\nSee https://github.com/cosmos/cosmos-sdk/blob/v0.42.4/proto/cosmos/distribution/v1beta1/tx.proto", + "oneOf": [ + { + "description": "This is translated to a [MsgSetWithdrawAddress](https://github.com/cosmos/cosmos-sdk/blob/v0.42.4/proto/cosmos/distribution/v1beta1/tx.proto#L29-L37). `delegator_address` is automatically filled with the current contract's address.", + "type": "object", + "required": [ + "set_withdraw_address" + ], + "properties": { + "set_withdraw_address": { + "type": "object", + "required": [ + "address" + ], + "properties": { + "address": { + "description": "The `withdraw_address`", + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "This is translated to a [[MsgWithdrawDelegatorReward](https://github.com/cosmos/cosmos-sdk/blob/v0.42.4/proto/cosmos/distribution/v1beta1/tx.proto#L42-L50). `delegator_address` is automatically filled with the current contract's address.", + "type": "object", + "required": [ + "withdraw_delegator_reward" + ], + "properties": { + "withdraw_delegator_reward": { + "type": "object", + "required": [ + "validator" + ], + "properties": { + "validator": { + "description": "The `validator_address`", + "type": "string" + } + } + } + }, + "additionalProperties": false + } + ] + }, + "Empty": { + "description": "An empty struct that serves as a placeholder in different places, such as contracts that don't set a custom message.\n\nIt is designed to be expressable in correct JSON and JSON Schema but contains no meaningful data. Previously we used enums without cases, but those cannot represented as valid JSON Schema (https://github.com/CosmWasm/cosmwasm/issues/451)", + "type": "object" + }, + "Expiration": { + "description": "Expiration represents a point in time when some event happens. It can compare with a BlockInfo and will return is_expired() == true once the condition is hit (and for every block in the future)", + "oneOf": [ + { + "description": "AtHeight will expire when `env.block.height` >= height", + "type": "object", + "required": [ + "at_height" + ], + "properties": { + "at_height": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + }, + { + "description": "AtTime will expire when `env.block.time` >= time", + "type": "object", + "required": [ + "at_time" + ], + "properties": { + "at_time": { + "$ref": "#/definitions/Timestamp" + } + }, + "additionalProperties": false + }, + { + "description": "Never will never expire. Used to express the empty variant", + "type": "object", + "required": [ + "never" + ], + "properties": { + "never": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + } + ] + }, + "GovMsg": { + "description": "This message type allows the contract interact with the [x/gov] module in order to cast votes.\n\n[x/gov]: https://github.com/cosmos/cosmos-sdk/tree/v0.45.12/x/gov\n\n## Examples\n\nCast a simple vote:\n\n``` # use cosmwasm_std::{ # HexBinary, # Storage, Api, Querier, DepsMut, Deps, entry_point, Env, StdError, MessageInfo, # Response, QueryResponse, # }; # type ExecuteMsg = (); use cosmwasm_std::{GovMsg, VoteOption};\n\n#[entry_point] pub fn execute( deps: DepsMut, env: Env, info: MessageInfo, msg: ExecuteMsg, ) -> Result { // ... Ok(Response::new().add_message(GovMsg::Vote { proposal_id: 4, vote: VoteOption::Yes, })) } ```\n\nCast a weighted vote:\n\n``` # use cosmwasm_std::{ # HexBinary, # Storage, Api, Querier, DepsMut, Deps, entry_point, Env, StdError, MessageInfo, # Response, QueryResponse, # }; # type ExecuteMsg = (); # #[cfg(feature = \"cosmwasm_1_2\")] use cosmwasm_std::{Decimal, GovMsg, VoteOption, WeightedVoteOption};\n\n# #[cfg(feature = \"cosmwasm_1_2\")] #[entry_point] pub fn execute( deps: DepsMut, env: Env, info: MessageInfo, msg: ExecuteMsg, ) -> Result { // ... Ok(Response::new().add_message(GovMsg::VoteWeighted { proposal_id: 4, options: vec![ WeightedVoteOption { option: VoteOption::Yes, weight: Decimal::percent(65), }, WeightedVoteOption { option: VoteOption::Abstain, weight: Decimal::percent(35), }, ], })) } ```", + "oneOf": [ + { + "description": "This maps directly to [MsgVote](https://github.com/cosmos/cosmos-sdk/blob/v0.42.5/proto/cosmos/gov/v1beta1/tx.proto#L46-L56) in the Cosmos SDK with voter set to the contract address.", + "type": "object", + "required": [ + "vote" + ], + "properties": { + "vote": { + "type": "object", + "required": [ + "proposal_id", + "vote" + ], + "properties": { + "proposal_id": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "vote": { + "description": "The vote option.\n\nThis should be called \"option\" for consistency with Cosmos SDK. Sorry for that. See .", + "allOf": [ + { + "$ref": "#/definitions/VoteOption" + } + ] + } + } + } + }, + "additionalProperties": false + } + ] + }, + "IbcMsg": { + "description": "These are messages in the IBC lifecycle. Only usable by IBC-enabled contracts (contracts that directly speak the IBC protocol via 6 entry points)", + "oneOf": [ + { + "description": "Sends bank tokens owned by the contract to the given address on another chain. The channel must already be established between the ibctransfer module on this chain and a matching module on the remote chain. We cannot select the port_id, this is whatever the local chain has bound the ibctransfer module to.", + "type": "object", + "required": [ + "transfer" + ], + "properties": { + "transfer": { + "type": "object", + "required": [ + "amount", + "channel_id", + "timeout", + "to_address" + ], + "properties": { + "amount": { + "description": "packet data only supports one coin https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/ibc/applications/transfer/v1/transfer.proto#L11-L20", + "allOf": [ + { + "$ref": "#/definitions/Coin" + } + ] + }, + "channel_id": { + "description": "exisiting channel to send the tokens over", + "type": "string" + }, + "timeout": { + "description": "when packet times out, measured on remote chain", + "allOf": [ + { + "$ref": "#/definitions/IbcTimeout" + } + ] + }, + "to_address": { + "description": "address on the remote chain to receive these tokens", + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Sends an IBC packet with given data over the existing channel. Data should be encoded in a format defined by the channel version, and the module on the other side should know how to parse this.", + "type": "object", + "required": [ + "send_packet" + ], + "properties": { + "send_packet": { + "type": "object", + "required": [ + "channel_id", + "data", + "timeout" + ], + "properties": { + "channel_id": { + "type": "string" + }, + "data": { + "$ref": "#/definitions/Binary" + }, + "timeout": { + "description": "when packet times out, measured on remote chain", + "allOf": [ + { + "$ref": "#/definitions/IbcTimeout" + } + ] + } + } + } + }, + "additionalProperties": false + }, + { + "description": "This will close an existing channel that is owned by this contract. Port is auto-assigned to the contract's IBC port", + "type": "object", + "required": [ + "close_channel" + ], + "properties": { + "close_channel": { + "type": "object", + "required": [ + "channel_id" + ], + "properties": { + "channel_id": { + "type": "string" + } + } + } + }, + "additionalProperties": false + } + ] + }, + "IbcTimeout": { + "description": "In IBC each package must set at least one type of timeout: the timestamp or the block height. Using this rather complex enum instead of two timeout fields we ensure that at least one timeout is set.", + "type": "object", + "properties": { + "block": { + "anyOf": [ + { + "$ref": "#/definitions/IbcTimeoutBlock" + }, + { + "type": "null" + } + ] + }, + "timestamp": { + "anyOf": [ + { + "$ref": "#/definitions/Timestamp" + }, + { + "type": "null" + } + ] + } + } + }, + "IbcTimeoutBlock": { + "description": "IBCTimeoutHeight Height is a monotonically increasing data type that can be compared against another Height for the purposes of updating and freezing clients. Ordering is (revision_number, timeout_height)", + "type": "object", + "required": [ + "height", + "revision" + ], + "properties": { + "height": { + "description": "block height after which the packet times out. the height within the given revision", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "revision": { + "description": "the version that the client is currently on (eg. after reseting the chain this could increment 1 as height drops to 0)", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + } + }, + "MemberChangedHookMsg": { + "description": "MemberChangedHookMsg should be de/serialized under `MemberChangedHook()` variant in a ExecuteMsg. This contains a list of all diffs on the given transaction.", + "type": "object", + "required": [ + "diffs" + ], + "properties": { + "diffs": { + "type": "array", + "items": { + "$ref": "#/definitions/MemberDiff" + } + } + }, + "additionalProperties": false + }, + "MemberDiff": { + "description": "MemberDiff shows the old and new states for a given cw4 member They cannot both be None. old = None, new = Some -> Insert old = Some, new = Some -> Update old = Some, new = None -> Delete", + "type": "object", + "required": [ + "key" + ], + "properties": { + "key": { + "type": "string" + }, + "new": { + "type": [ + "integer", + "null" + ], + "format": "uint64", + "minimum": 0.0 + }, + "old": { + "type": [ + "integer", + "null" + ], + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + }, + "StakingMsg": { + "description": "The message types of the staking module.\n\nSee https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/staking/v1beta1/tx.proto", + "oneOf": [ + { + "description": "This is translated to a [MsgDelegate](https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/staking/v1beta1/tx.proto#L81-L90). `delegator_address` is automatically filled with the current contract's address.", + "type": "object", + "required": [ + "delegate" + ], + "properties": { + "delegate": { + "type": "object", + "required": [ + "amount", + "validator" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Coin" + }, + "validator": { + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "This is translated to a [MsgUndelegate](https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/staking/v1beta1/tx.proto#L112-L121). `delegator_address` is automatically filled with the current contract's address.", + "type": "object", + "required": [ + "undelegate" + ], + "properties": { + "undelegate": { + "type": "object", + "required": [ + "amount", + "validator" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Coin" + }, + "validator": { + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "This is translated to a [MsgBeginRedelegate](https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/staking/v1beta1/tx.proto#L95-L105). `delegator_address` is automatically filled with the current contract's address.", + "type": "object", + "required": [ + "redelegate" + ], + "properties": { + "redelegate": { + "type": "object", + "required": [ + "amount", + "dst_validator", + "src_validator" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Coin" + }, + "dst_validator": { + "type": "string" + }, + "src_validator": { + "type": "string" + } + } + } + }, + "additionalProperties": false + } + ] + }, + "Timestamp": { + "description": "A point in time in nanosecond precision.\n\nThis type can represent times from 1970-01-01T00:00:00Z to 2554-07-21T23:34:33Z.\n\n## Examples\n\n``` # use cosmwasm_std::Timestamp; let ts = Timestamp::from_nanos(1_000_000_202); assert_eq!(ts.nanos(), 1_000_000_202); assert_eq!(ts.seconds(), 1); assert_eq!(ts.subsec_nanos(), 202);\n\nlet ts = ts.plus_seconds(2); assert_eq!(ts.nanos(), 3_000_000_202); assert_eq!(ts.seconds(), 3); assert_eq!(ts.subsec_nanos(), 202); ```", + "allOf": [ + { + "$ref": "#/definitions/Uint64" + } + ] + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + }, + "Uint64": { + "description": "A thin wrapper around u64 that is using strings for JSON encoding/decoding, such that the full u64 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u64` to get the value out:\n\n``` # use cosmwasm_std::Uint64; let a = Uint64::from(42u64); assert_eq!(a.u64(), 42);\n\nlet b = Uint64::from(70u32); assert_eq!(b.u64(), 70); ```", + "type": "string" + }, + "Vote": { + "oneOf": [ + { + "description": "Marks support for the proposal.", + "type": "string", + "enum": [ + "yes" + ] + }, + { + "description": "Marks opposition to the proposal.", + "type": "string", + "enum": [ + "no" + ] + }, + { + "description": "Marks participation but does not count towards the ratio of support / opposed", + "type": "string", + "enum": [ + "abstain" + ] + }, + { + "description": "Veto is generally to be treated as a No vote. Some implementations may allow certain voters to be able to Veto, or them to be counted stronger than No in some way.", + "type": "string", + "enum": [ + "veto" + ] + } + ] + }, + "VoteOption": { + "type": "string", + "enum": [ + "yes", + "no", + "abstain", + "no_with_veto" + ] + }, + "WasmMsg": { + "description": "The message types of the wasm module.\n\nSee https://github.com/CosmWasm/wasmd/blob/v0.14.0/x/wasm/internal/types/tx.proto", + "oneOf": [ + { + "description": "Dispatches a call to another contract at a known address (with known ABI).\n\nThis is translated to a [MsgExecuteContract](https://github.com/CosmWasm/wasmd/blob/v0.14.0/x/wasm/internal/types/tx.proto#L68-L78). `sender` is automatically filled with the current contract's address.", + "type": "object", + "required": [ + "execute" + ], + "properties": { + "execute": { + "type": "object", + "required": [ + "contract_addr", + "funds", + "msg" + ], + "properties": { + "contract_addr": { + "type": "string" + }, + "funds": { + "type": "array", + "items": { + "$ref": "#/definitions/Coin" + } + }, + "msg": { + "description": "msg is the json-encoded ExecuteMsg struct (as raw Binary)", + "allOf": [ + { + "$ref": "#/definitions/Binary" + } + ] + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Instantiates a new contracts from previously uploaded Wasm code.\n\nThe contract address is non-predictable. But it is guaranteed that when emitting the same Instantiate message multiple times, multiple instances on different addresses will be generated. See also Instantiate2.\n\nThis is translated to a [MsgInstantiateContract](https://github.com/CosmWasm/wasmd/blob/v0.29.2/proto/cosmwasm/wasm/v1/tx.proto#L53-L71). `sender` is automatically filled with the current contract's address.", + "type": "object", + "required": [ + "instantiate" + ], + "properties": { + "instantiate": { + "type": "object", + "required": [ + "code_id", + "funds", + "label", + "msg" + ], + "properties": { + "admin": { + "type": [ + "string", + "null" + ] + }, + "code_id": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "funds": { + "type": "array", + "items": { + "$ref": "#/definitions/Coin" + } + }, + "label": { + "description": "A human-readbale label for the contract", + "type": "string" + }, + "msg": { + "description": "msg is the JSON-encoded InstantiateMsg struct (as raw Binary)", + "allOf": [ + { + "$ref": "#/definitions/Binary" + } + ] + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Migrates a given contracts to use new wasm code. Passes a MigrateMsg to allow us to customize behavior.\n\nOnly the contract admin (as defined in wasmd), if any, is able to make this call.\n\nThis is translated to a [MsgMigrateContract](https://github.com/CosmWasm/wasmd/blob/v0.14.0/x/wasm/internal/types/tx.proto#L86-L96). `sender` is automatically filled with the current contract's address.", + "type": "object", + "required": [ + "migrate" + ], + "properties": { + "migrate": { + "type": "object", + "required": [ + "contract_addr", + "msg", + "new_code_id" + ], + "properties": { + "contract_addr": { + "type": "string" + }, + "msg": { + "description": "msg is the json-encoded MigrateMsg struct that will be passed to the new code", + "allOf": [ + { + "$ref": "#/definitions/Binary" + } + ] + }, + "new_code_id": { + "description": "the code_id of the new logic to place in the given contract", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Sets a new admin (for migrate) on the given contract. Fails if this contract is not currently admin of the target contract.", + "type": "object", + "required": [ + "update_admin" + ], + "properties": { + "update_admin": { + "type": "object", + "required": [ + "admin", + "contract_addr" + ], + "properties": { + "admin": { + "type": "string" + }, + "contract_addr": { + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Clears the admin on the given contract, so no more migration possible. Fails if this contract is not currently admin of the target contract.", + "type": "object", + "required": [ + "clear_admin" + ], + "properties": { + "clear_admin": { + "type": "object", + "required": [ + "contract_addr" + ], + "properties": { + "contract_addr": { + "type": "string" + } + } + } + }, + "additionalProperties": false + } + ] + } + } + }, + "query": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "QueryMsg", + "oneOf": [ + { + "type": "object", + "required": [ + "threshold" + ], + "properties": { + "threshold": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "proposal" + ], + "properties": { + "proposal": { + "type": "object", + "required": [ + "proposal_id" + ], + "properties": { + "proposal_id": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "list_proposals" + ], + "properties": { + "list_proposals": { + "type": "object", + "properties": { + "limit": { + "type": [ + "integer", + "null" + ], + "format": "uint32", + "minimum": 0.0 + }, + "start_after": { + "type": [ + "integer", + "null" + ], + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "reverse_proposals" + ], + "properties": { + "reverse_proposals": { + "type": "object", + "properties": { + "limit": { + "type": [ + "integer", + "null" + ], + "format": "uint32", + "minimum": 0.0 + }, + "start_before": { + "type": [ + "integer", + "null" + ], + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "vote" + ], + "properties": { + "vote": { + "type": "object", + "required": [ + "proposal_id", + "voter" + ], + "properties": { + "proposal_id": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "voter": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "list_votes" + ], + "properties": { + "list_votes": { + "type": "object", + "required": [ + "proposal_id" + ], + "properties": { + "limit": { + "type": [ + "integer", + "null" + ], + "format": "uint32", + "minimum": 0.0 + }, + "proposal_id": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "start_after": { + "type": [ + "string", + "null" + ] + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "voter" + ], + "properties": { + "voter": { + "type": "object", + "required": [ + "address" + ], + "properties": { + "address": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "list_voters" + ], + "properties": { + "list_voters": { + "type": "object", + "properties": { + "limit": { + "type": [ + "integer", + "null" + ], + "format": "uint32", + "minimum": 0.0 + }, + "start_after": { + "type": [ + "string", + "null" + ] + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Gets the current configuration.", + "type": "object", + "required": [ + "config" + ], + "properties": { + "config": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + } + ] + }, + "migrate": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "MigrateMsg", + "type": "object", + "required": [ + "coconut_bandwidth_address", + "coconut_dkg_address" + ], + "properties": { + "coconut_bandwidth_address": { + "type": "string" + }, + "coconut_dkg_address": { + "type": "string" + } + }, + "additionalProperties": false + }, + "sudo": null, + "responses": { + "config": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Config", + "type": "object", + "required": [ + "coconut_bandwidth_addr", + "coconut_dkg_addr", + "group_addr", + "max_voting_period", + "threshold" + ], + "properties": { + "coconut_bandwidth_addr": { + "$ref": "#/definitions/Addr" + }, + "coconut_dkg_addr": { + "$ref": "#/definitions/Addr" + }, + "executor": { + "anyOf": [ + { + "$ref": "#/definitions/Executor" + }, + { + "type": "null" + } + ] + }, + "group_addr": { + "$ref": "#/definitions/Cw4Contract" + }, + "max_voting_period": { + "$ref": "#/definitions/Duration" + }, + "proposal_deposit": { + "description": "The price, if any, of creating a new proposal.", + "anyOf": [ + { + "$ref": "#/definitions/DepositInfo" + }, + { + "type": "null" + } + ] + }, + "threshold": { + "$ref": "#/definitions/Threshold" + } + }, + "additionalProperties": false, + "definitions": { + "Addr": { + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + }, + "Cw4Contract": { + "description": "Cw4Contract is a wrapper around Addr that provides a lot of helpers for working with cw4 contracts\n\nIf you wish to persist this, convert to Cw4CanonicalContract via .canonical()", + "allOf": [ + { + "$ref": "#/definitions/Addr" + } + ] + }, + "Decimal": { + "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", + "type": "string" + }, + "Denom": { + "oneOf": [ + { + "type": "object", + "required": [ + "native" + ], + "properties": { + "native": { + "type": "string" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "cw20" + ], + "properties": { + "cw20": { + "$ref": "#/definitions/Addr" + } + }, + "additionalProperties": false + } + ] + }, + "DepositInfo": { + "description": "Information about the deposit required to create a proposal.", + "type": "object", + "required": [ + "amount", + "denom", + "refund_failed_proposals" + ], + "properties": { + "amount": { + "description": "The number tokens required for payment.", + "allOf": [ + { + "$ref": "#/definitions/Uint128" + } + ] + }, + "denom": { + "description": "The denom of the deposit payment.", + "allOf": [ + { + "$ref": "#/definitions/Denom" + } + ] + }, + "refund_failed_proposals": { + "description": "Should failed proposals have their deposits refunded?", + "type": "boolean" + } + }, + "additionalProperties": false + }, + "Duration": { + "description": "Duration is a delta of time. You can add it to a BlockInfo or Expiration to move that further in the future. Note that an height-based Duration and a time-based Expiration cannot be combined", + "oneOf": [ + { + "type": "object", + "required": [ + "height" + ], + "properties": { + "height": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + }, + { + "description": "Time in seconds", + "type": "object", + "required": [ + "time" + ], + "properties": { + "time": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + ] + }, + "Executor": { + "description": "Defines who is able to execute proposals once passed", + "oneOf": [ + { + "description": "Any member of the voting group, even with 0 points", + "type": "string", + "enum": [ + "member" + ] + }, + { + "description": "Only the given address", + "type": "object", + "required": [ + "only" + ], + "properties": { + "only": { + "$ref": "#/definitions/Addr" + } + }, + "additionalProperties": false + } + ] + }, + "Threshold": { + "description": "This defines the different ways tallies can happen.\n\nThe total_weight used for calculating success as well as the weights of each individual voter used in tallying should be snapshotted at the beginning of the block at which the proposal starts (this is likely the responsibility of a correct cw4 implementation). See also `ThresholdResponse` in the cw3 spec.", + "oneOf": [ + { + "description": "Declares that a fixed weight of Yes votes is needed to pass. See `ThresholdResponse.AbsoluteCount` in the cw3 spec for details.", + "type": "object", + "required": [ + "absolute_count" + ], + "properties": { + "absolute_count": { + "type": "object", + "required": [ + "weight" + ], + "properties": { + "weight": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Declares a percentage of the total weight that must cast Yes votes in order for a proposal to pass. See `ThresholdResponse.AbsolutePercentage` in the cw3 spec for details.", + "type": "object", + "required": [ + "absolute_percentage" + ], + "properties": { + "absolute_percentage": { + "type": "object", + "required": [ + "percentage" + ], + "properties": { + "percentage": { + "$ref": "#/definitions/Decimal" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Declares a `quorum` of the total votes that must participate in the election in order for the vote to be considered at all. See `ThresholdResponse.ThresholdQuorum` in the cw3 spec for details.", + "type": "object", + "required": [ + "threshold_quorum" + ], + "properties": { + "threshold_quorum": { + "type": "object", + "required": [ + "quorum", + "threshold" + ], + "properties": { + "quorum": { + "$ref": "#/definitions/Decimal" + }, + "threshold": { + "$ref": "#/definitions/Decimal" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + ] + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } + }, + "list_proposals": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ProposalListResponse_for_Empty", + "type": "object", + "required": [ + "proposals" + ], + "properties": { + "proposals": { + "type": "array", + "items": { + "$ref": "#/definitions/ProposalResponse_for_Empty" + } + } + }, + "additionalProperties": false, + "definitions": { + "Addr": { + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + }, + "BankMsg": { + "description": "The message types of the bank module.\n\nSee https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/bank/v1beta1/tx.proto", + "oneOf": [ + { + "description": "Sends native tokens from the contract to the given address.\n\nThis is translated to a [MsgSend](https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/bank/v1beta1/tx.proto#L19-L28). `from_address` is automatically filled with the current contract's address.", + "type": "object", + "required": [ + "send" + ], + "properties": { + "send": { + "type": "object", + "required": [ + "amount", + "to_address" + ], + "properties": { + "amount": { + "type": "array", + "items": { + "$ref": "#/definitions/Coin" + } + }, + "to_address": { + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "This will burn the given coins from the contract's account. There is no Cosmos SDK message that performs this, but it can be done by calling the bank keeper. Important if a contract controls significant token supply that must be retired.", + "type": "object", + "required": [ + "burn" + ], + "properties": { + "burn": { + "type": "object", + "required": [ + "amount" + ], + "properties": { + "amount": { + "type": "array", + "items": { + "$ref": "#/definitions/Coin" + } + } + } + } + }, + "additionalProperties": false + } + ] + }, + "Binary": { + "description": "Binary is a wrapper around Vec to add base64 de/serialization with serde. It also adds some helper methods to help encode inline.\n\nThis is only needed as serde-json-{core,wasm} has a horrible encoding for Vec. See also .", + "type": "string" + }, + "Coin": { + "type": "object", + "required": [ + "amount", + "denom" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "denom": { + "type": "string" + } + } + }, + "CosmosMsg_for_Empty": { + "oneOf": [ + { + "type": "object", + "required": [ + "bank" + ], + "properties": { + "bank": { + "$ref": "#/definitions/BankMsg" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "custom" + ], + "properties": { + "custom": { + "$ref": "#/definitions/Empty" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "staking" + ], + "properties": { + "staking": { + "$ref": "#/definitions/StakingMsg" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "distribution" + ], + "properties": { + "distribution": { + "$ref": "#/definitions/DistributionMsg" + } + }, + "additionalProperties": false + }, + { + "description": "A Stargate message encoded the same way as a protobuf [Any](https://github.com/protocolbuffers/protobuf/blob/master/src/google/protobuf/any.proto). This is the same structure as messages in `TxBody` from [ADR-020](https://github.com/cosmos/cosmos-sdk/blob/master/docs/architecture/adr-020-protobuf-transaction-encoding.md)", + "type": "object", + "required": [ + "stargate" + ], + "properties": { + "stargate": { + "type": "object", + "required": [ + "type_url", + "value" + ], + "properties": { + "type_url": { + "type": "string" + }, + "value": { + "$ref": "#/definitions/Binary" + } + } + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "ibc" + ], + "properties": { + "ibc": { + "$ref": "#/definitions/IbcMsg" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "wasm" + ], + "properties": { + "wasm": { + "$ref": "#/definitions/WasmMsg" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "gov" + ], + "properties": { + "gov": { + "$ref": "#/definitions/GovMsg" + } + }, + "additionalProperties": false + } + ] + }, + "Decimal": { + "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", + "type": "string" + }, + "Denom": { + "oneOf": [ + { + "type": "object", + "required": [ + "native" + ], + "properties": { + "native": { + "type": "string" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "cw20" + ], + "properties": { + "cw20": { + "$ref": "#/definitions/Addr" + } + }, + "additionalProperties": false + } + ] + }, + "DepositInfo": { + "description": "Information about the deposit required to create a proposal.", + "type": "object", + "required": [ + "amount", + "denom", + "refund_failed_proposals" + ], + "properties": { + "amount": { + "description": "The number tokens required for payment.", + "allOf": [ + { + "$ref": "#/definitions/Uint128" + } + ] + }, + "denom": { + "description": "The denom of the deposit payment.", + "allOf": [ + { + "$ref": "#/definitions/Denom" + } + ] + }, + "refund_failed_proposals": { + "description": "Should failed proposals have their deposits refunded?", + "type": "boolean" + } + }, + "additionalProperties": false + }, + "DistributionMsg": { + "description": "The message types of the distribution module.\n\nSee https://github.com/cosmos/cosmos-sdk/blob/v0.42.4/proto/cosmos/distribution/v1beta1/tx.proto", + "oneOf": [ + { + "description": "This is translated to a [MsgSetWithdrawAddress](https://github.com/cosmos/cosmos-sdk/blob/v0.42.4/proto/cosmos/distribution/v1beta1/tx.proto#L29-L37). `delegator_address` is automatically filled with the current contract's address.", + "type": "object", + "required": [ + "set_withdraw_address" + ], + "properties": { + "set_withdraw_address": { + "type": "object", + "required": [ + "address" + ], + "properties": { + "address": { + "description": "The `withdraw_address`", + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "This is translated to a [[MsgWithdrawDelegatorReward](https://github.com/cosmos/cosmos-sdk/blob/v0.42.4/proto/cosmos/distribution/v1beta1/tx.proto#L42-L50). `delegator_address` is automatically filled with the current contract's address.", + "type": "object", + "required": [ + "withdraw_delegator_reward" + ], + "properties": { + "withdraw_delegator_reward": { + "type": "object", + "required": [ + "validator" + ], + "properties": { + "validator": { + "description": "The `validator_address`", + "type": "string" + } + } + } + }, + "additionalProperties": false + } + ] + }, + "Empty": { + "description": "An empty struct that serves as a placeholder in different places, such as contracts that don't set a custom message.\n\nIt is designed to be expressable in correct JSON and JSON Schema but contains no meaningful data. Previously we used enums without cases, but those cannot represented as valid JSON Schema (https://github.com/CosmWasm/cosmwasm/issues/451)", + "type": "object" + }, + "Expiration": { + "description": "Expiration represents a point in time when some event happens. It can compare with a BlockInfo and will return is_expired() == true once the condition is hit (and for every block in the future)", + "oneOf": [ + { + "description": "AtHeight will expire when `env.block.height` >= height", + "type": "object", + "required": [ + "at_height" + ], + "properties": { + "at_height": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + }, + { + "description": "AtTime will expire when `env.block.time` >= time", + "type": "object", + "required": [ + "at_time" + ], + "properties": { + "at_time": { + "$ref": "#/definitions/Timestamp" + } + }, + "additionalProperties": false + }, + { + "description": "Never will never expire. Used to express the empty variant", + "type": "object", + "required": [ + "never" + ], + "properties": { + "never": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + } + ] + }, + "GovMsg": { + "description": "This message type allows the contract interact with the [x/gov] module in order to cast votes.\n\n[x/gov]: https://github.com/cosmos/cosmos-sdk/tree/v0.45.12/x/gov\n\n## Examples\n\nCast a simple vote:\n\n``` # use cosmwasm_std::{ # HexBinary, # Storage, Api, Querier, DepsMut, Deps, entry_point, Env, StdError, MessageInfo, # Response, QueryResponse, # }; # type ExecuteMsg = (); use cosmwasm_std::{GovMsg, VoteOption};\n\n#[entry_point] pub fn execute( deps: DepsMut, env: Env, info: MessageInfo, msg: ExecuteMsg, ) -> Result { // ... Ok(Response::new().add_message(GovMsg::Vote { proposal_id: 4, vote: VoteOption::Yes, })) } ```\n\nCast a weighted vote:\n\n``` # use cosmwasm_std::{ # HexBinary, # Storage, Api, Querier, DepsMut, Deps, entry_point, Env, StdError, MessageInfo, # Response, QueryResponse, # }; # type ExecuteMsg = (); # #[cfg(feature = \"cosmwasm_1_2\")] use cosmwasm_std::{Decimal, GovMsg, VoteOption, WeightedVoteOption};\n\n# #[cfg(feature = \"cosmwasm_1_2\")] #[entry_point] pub fn execute( deps: DepsMut, env: Env, info: MessageInfo, msg: ExecuteMsg, ) -> Result { // ... Ok(Response::new().add_message(GovMsg::VoteWeighted { proposal_id: 4, options: vec![ WeightedVoteOption { option: VoteOption::Yes, weight: Decimal::percent(65), }, WeightedVoteOption { option: VoteOption::Abstain, weight: Decimal::percent(35), }, ], })) } ```", + "oneOf": [ + { + "description": "This maps directly to [MsgVote](https://github.com/cosmos/cosmos-sdk/blob/v0.42.5/proto/cosmos/gov/v1beta1/tx.proto#L46-L56) in the Cosmos SDK with voter set to the contract address.", + "type": "object", + "required": [ + "vote" + ], + "properties": { + "vote": { + "type": "object", + "required": [ + "proposal_id", + "vote" + ], + "properties": { + "proposal_id": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "vote": { + "description": "The vote option.\n\nThis should be called \"option\" for consistency with Cosmos SDK. Sorry for that. See .", + "allOf": [ + { + "$ref": "#/definitions/VoteOption" + } + ] + } + } + } + }, + "additionalProperties": false + } + ] + }, + "IbcMsg": { + "description": "These are messages in the IBC lifecycle. Only usable by IBC-enabled contracts (contracts that directly speak the IBC protocol via 6 entry points)", + "oneOf": [ + { + "description": "Sends bank tokens owned by the contract to the given address on another chain. The channel must already be established between the ibctransfer module on this chain and a matching module on the remote chain. We cannot select the port_id, this is whatever the local chain has bound the ibctransfer module to.", + "type": "object", + "required": [ + "transfer" + ], + "properties": { + "transfer": { + "type": "object", + "required": [ + "amount", + "channel_id", + "timeout", + "to_address" + ], + "properties": { + "amount": { + "description": "packet data only supports one coin https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/ibc/applications/transfer/v1/transfer.proto#L11-L20", + "allOf": [ + { + "$ref": "#/definitions/Coin" + } + ] + }, + "channel_id": { + "description": "exisiting channel to send the tokens over", + "type": "string" + }, + "timeout": { + "description": "when packet times out, measured on remote chain", + "allOf": [ + { + "$ref": "#/definitions/IbcTimeout" + } + ] + }, + "to_address": { + "description": "address on the remote chain to receive these tokens", + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Sends an IBC packet with given data over the existing channel. Data should be encoded in a format defined by the channel version, and the module on the other side should know how to parse this.", + "type": "object", + "required": [ + "send_packet" + ], + "properties": { + "send_packet": { + "type": "object", + "required": [ + "channel_id", + "data", + "timeout" + ], + "properties": { + "channel_id": { + "type": "string" + }, + "data": { + "$ref": "#/definitions/Binary" + }, + "timeout": { + "description": "when packet times out, measured on remote chain", + "allOf": [ + { + "$ref": "#/definitions/IbcTimeout" + } + ] + } + } + } + }, + "additionalProperties": false + }, + { + "description": "This will close an existing channel that is owned by this contract. Port is auto-assigned to the contract's IBC port", + "type": "object", + "required": [ + "close_channel" + ], + "properties": { + "close_channel": { + "type": "object", + "required": [ + "channel_id" + ], + "properties": { + "channel_id": { + "type": "string" + } + } + } + }, + "additionalProperties": false + } + ] + }, + "IbcTimeout": { + "description": "In IBC each package must set at least one type of timeout: the timestamp or the block height. Using this rather complex enum instead of two timeout fields we ensure that at least one timeout is set.", + "type": "object", + "properties": { + "block": { + "anyOf": [ + { + "$ref": "#/definitions/IbcTimeoutBlock" + }, + { + "type": "null" + } + ] + }, + "timestamp": { + "anyOf": [ + { + "$ref": "#/definitions/Timestamp" + }, + { + "type": "null" + } + ] + } + } + }, + "IbcTimeoutBlock": { + "description": "IBCTimeoutHeight Height is a monotonically increasing data type that can be compared against another Height for the purposes of updating and freezing clients. Ordering is (revision_number, timeout_height)", + "type": "object", + "required": [ + "height", + "revision" + ], + "properties": { + "height": { + "description": "block height after which the packet times out. the height within the given revision", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "revision": { + "description": "the version that the client is currently on (eg. after reseting the chain this could increment 1 as height drops to 0)", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + } + }, + "ProposalResponse_for_Empty": { + "description": "Note, if you are storing custom messages in the proposal, the querier needs to know what possible custom message types those are in order to parse the response", + "type": "object", + "required": [ + "description", + "expires", + "id", + "msgs", + "proposer", + "status", + "threshold", + "title" + ], + "properties": { + "deposit": { + "anyOf": [ + { + "$ref": "#/definitions/DepositInfo" + }, + { + "type": "null" + } + ] + }, + "description": { + "type": "string" + }, + "expires": { + "$ref": "#/definitions/Expiration" + }, + "id": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "msgs": { + "type": "array", + "items": { + "$ref": "#/definitions/CosmosMsg_for_Empty" + } + }, + "proposer": { + "$ref": "#/definitions/Addr" + }, + "status": { + "$ref": "#/definitions/Status" + }, + "threshold": { + "description": "This is the threshold that is applied to this proposal. Both the rules of the voting contract, as well as the total_weight of the voting group may have changed since this time. That means that the generic `Threshold{}` query does not provide valid information for existing proposals.", + "allOf": [ + { + "$ref": "#/definitions/ThresholdResponse" + } + ] + }, + "title": { + "type": "string" + } + }, + "additionalProperties": false + }, + "StakingMsg": { + "description": "The message types of the staking module.\n\nSee https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/staking/v1beta1/tx.proto", + "oneOf": [ + { + "description": "This is translated to a [MsgDelegate](https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/staking/v1beta1/tx.proto#L81-L90). `delegator_address` is automatically filled with the current contract's address.", + "type": "object", + "required": [ + "delegate" + ], + "properties": { + "delegate": { + "type": "object", + "required": [ + "amount", + "validator" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Coin" + }, + "validator": { + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "This is translated to a [MsgUndelegate](https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/staking/v1beta1/tx.proto#L112-L121). `delegator_address` is automatically filled with the current contract's address.", + "type": "object", + "required": [ + "undelegate" + ], + "properties": { + "undelegate": { + "type": "object", + "required": [ + "amount", + "validator" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Coin" + }, + "validator": { + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "This is translated to a [MsgBeginRedelegate](https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/staking/v1beta1/tx.proto#L95-L105). `delegator_address` is automatically filled with the current contract's address.", + "type": "object", + "required": [ + "redelegate" + ], + "properties": { + "redelegate": { + "type": "object", + "required": [ + "amount", + "dst_validator", + "src_validator" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Coin" + }, + "dst_validator": { + "type": "string" + }, + "src_validator": { + "type": "string" + } + } + } + }, + "additionalProperties": false + } + ] + }, + "Status": { + "oneOf": [ + { + "description": "proposal was created, but voting has not yet begun for whatever reason", + "type": "string", + "enum": [ + "pending" + ] + }, + { + "description": "you can vote on this", + "type": "string", + "enum": [ + "open" + ] + }, + { + "description": "voting is over and it did not pass", + "type": "string", + "enum": [ + "rejected" + ] + }, + { + "description": "voting is over and it did pass, but has not yet executed", + "type": "string", + "enum": [ + "passed" + ] + }, + { + "description": "voting is over it passed, and the proposal was executed", + "type": "string", + "enum": [ + "executed" + ] + } + ] + }, + "ThresholdResponse": { + "description": "This defines the different ways tallies can happen. Every contract should support a subset of these, ideally all.\n\nThe total_weight used for calculating success as well as the weights of each individual voter used in tallying should be snapshotted at the beginning of the block at which the proposal starts (this is likely the responsibility of a correct cw4 implementation).", + "oneOf": [ + { + "description": "Declares that a fixed weight of yes votes is needed to pass. It does not matter how many no votes are cast, or how many do not vote, as long as `weight` yes votes are cast.\n\nThis is the simplest format and usually suitable for small multisigs of trusted parties, like 3 of 5. (weight: 3, total_weight: 5)\n\nA proposal of this type can pass early as soon as the needed weight of yes votes has been cast.", + "type": "object", + "required": [ + "absolute_count" + ], + "properties": { + "absolute_count": { + "type": "object", + "required": [ + "total_weight", + "weight" + ], + "properties": { + "total_weight": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "weight": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Declares a percentage of the total weight that must cast Yes votes, in order for a proposal to pass. The passing weight is computed over the total weight minus the weight of the abstained votes.\n\nThis is useful for similar circumstances as `AbsoluteCount`, where we have a relatively small set of voters, and participation is required. It is understood that if the voting set (group) changes between different proposals that refer to the same group, each proposal will work with a different set of voter weights (the ones snapshotted at proposal creation), and the passing weight for each proposal will be computed based on the absolute percentage, times the total weights of the members at the time of each proposal creation.\n\nExample: we set `percentage` to 51%. Proposal 1 starts when there is a `total_weight` of 5. This will require 3 weight of Yes votes in order to pass. Later, the Proposal 2 starts but the `total_weight` of the group has increased to 9. That proposal will then automatically require 5 Yes of 9 to pass, rather than 3 yes of 9 as would be the case with `AbsoluteCount`.", + "type": "object", + "required": [ + "absolute_percentage" + ], + "properties": { + "absolute_percentage": { + "type": "object", + "required": [ + "percentage", + "total_weight" + ], + "properties": { + "percentage": { + "$ref": "#/definitions/Decimal" + }, + "total_weight": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "In addition to a `threshold`, declares a `quorum` of the total votes that must participate in the election in order for the vote to be considered at all. Within the votes that were cast, it requires `threshold` votes in favor. That is calculated by ignoring the Abstain votes (they count towards `quorum`, but do not influence `threshold`). That is, we calculate `Yes / (Yes + No + Veto)` and compare it with `threshold` to consider if the proposal was passed.\n\nIt is rather difficult for a proposal of this type to pass early. That can only happen if the required quorum has been already met, and there are already enough Yes votes for the proposal to pass.\n\n30% Yes votes, 10% No votes, and 20% Abstain would pass early if quorum <= 60% (who has cast votes) and if the threshold is <= 37.5% (the remaining 40% voting no => 30% yes + 50% no). Once the voting period has passed with no additional votes, that same proposal would be considered successful if quorum <= 60% and threshold <= 75% (percent in favor if we ignore abstain votes).\n\nThis type is more common in general elections, where participation is often expected to be low, and `AbsolutePercentage` would either be too high to pass anything, or allow low percentages to pass, independently of if there was high participation in the election or not.", + "type": "object", + "required": [ + "threshold_quorum" + ], + "properties": { + "threshold_quorum": { + "type": "object", + "required": [ + "quorum", + "threshold", + "total_weight" + ], + "properties": { + "quorum": { + "$ref": "#/definitions/Decimal" + }, + "threshold": { + "$ref": "#/definitions/Decimal" + }, + "total_weight": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + ] + }, + "Timestamp": { + "description": "A point in time in nanosecond precision.\n\nThis type can represent times from 1970-01-01T00:00:00Z to 2554-07-21T23:34:33Z.\n\n## Examples\n\n``` # use cosmwasm_std::Timestamp; let ts = Timestamp::from_nanos(1_000_000_202); assert_eq!(ts.nanos(), 1_000_000_202); assert_eq!(ts.seconds(), 1); assert_eq!(ts.subsec_nanos(), 202);\n\nlet ts = ts.plus_seconds(2); assert_eq!(ts.nanos(), 3_000_000_202); assert_eq!(ts.seconds(), 3); assert_eq!(ts.subsec_nanos(), 202); ```", + "allOf": [ + { + "$ref": "#/definitions/Uint64" + } + ] + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + }, + "Uint64": { + "description": "A thin wrapper around u64 that is using strings for JSON encoding/decoding, such that the full u64 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u64` to get the value out:\n\n``` # use cosmwasm_std::Uint64; let a = Uint64::from(42u64); assert_eq!(a.u64(), 42);\n\nlet b = Uint64::from(70u32); assert_eq!(b.u64(), 70); ```", + "type": "string" + }, + "VoteOption": { + "type": "string", + "enum": [ + "yes", + "no", + "abstain", + "no_with_veto" + ] + }, + "WasmMsg": { + "description": "The message types of the wasm module.\n\nSee https://github.com/CosmWasm/wasmd/blob/v0.14.0/x/wasm/internal/types/tx.proto", + "oneOf": [ + { + "description": "Dispatches a call to another contract at a known address (with known ABI).\n\nThis is translated to a [MsgExecuteContract](https://github.com/CosmWasm/wasmd/blob/v0.14.0/x/wasm/internal/types/tx.proto#L68-L78). `sender` is automatically filled with the current contract's address.", + "type": "object", + "required": [ + "execute" + ], + "properties": { + "execute": { + "type": "object", + "required": [ + "contract_addr", + "funds", + "msg" + ], + "properties": { + "contract_addr": { + "type": "string" + }, + "funds": { + "type": "array", + "items": { + "$ref": "#/definitions/Coin" + } + }, + "msg": { + "description": "msg is the json-encoded ExecuteMsg struct (as raw Binary)", + "allOf": [ + { + "$ref": "#/definitions/Binary" + } + ] + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Instantiates a new contracts from previously uploaded Wasm code.\n\nThe contract address is non-predictable. But it is guaranteed that when emitting the same Instantiate message multiple times, multiple instances on different addresses will be generated. See also Instantiate2.\n\nThis is translated to a [MsgInstantiateContract](https://github.com/CosmWasm/wasmd/blob/v0.29.2/proto/cosmwasm/wasm/v1/tx.proto#L53-L71). `sender` is automatically filled with the current contract's address.", + "type": "object", + "required": [ + "instantiate" + ], + "properties": { + "instantiate": { + "type": "object", + "required": [ + "code_id", + "funds", + "label", + "msg" + ], + "properties": { + "admin": { + "type": [ + "string", + "null" + ] + }, + "code_id": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "funds": { + "type": "array", + "items": { + "$ref": "#/definitions/Coin" + } + }, + "label": { + "description": "A human-readbale label for the contract", + "type": "string" + }, + "msg": { + "description": "msg is the JSON-encoded InstantiateMsg struct (as raw Binary)", + "allOf": [ + { + "$ref": "#/definitions/Binary" + } + ] + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Migrates a given contracts to use new wasm code. Passes a MigrateMsg to allow us to customize behavior.\n\nOnly the contract admin (as defined in wasmd), if any, is able to make this call.\n\nThis is translated to a [MsgMigrateContract](https://github.com/CosmWasm/wasmd/blob/v0.14.0/x/wasm/internal/types/tx.proto#L86-L96). `sender` is automatically filled with the current contract's address.", + "type": "object", + "required": [ + "migrate" + ], + "properties": { + "migrate": { + "type": "object", + "required": [ + "contract_addr", + "msg", + "new_code_id" + ], + "properties": { + "contract_addr": { + "type": "string" + }, + "msg": { + "description": "msg is the json-encoded MigrateMsg struct that will be passed to the new code", + "allOf": [ + { + "$ref": "#/definitions/Binary" + } + ] + }, + "new_code_id": { + "description": "the code_id of the new logic to place in the given contract", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Sets a new admin (for migrate) on the given contract. Fails if this contract is not currently admin of the target contract.", + "type": "object", + "required": [ + "update_admin" + ], + "properties": { + "update_admin": { + "type": "object", + "required": [ + "admin", + "contract_addr" + ], + "properties": { + "admin": { + "type": "string" + }, + "contract_addr": { + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Clears the admin on the given contract, so no more migration possible. Fails if this contract is not currently admin of the target contract.", + "type": "object", + "required": [ + "clear_admin" + ], + "properties": { + "clear_admin": { + "type": "object", + "required": [ + "contract_addr" + ], + "properties": { + "contract_addr": { + "type": "string" + } + } + } + }, + "additionalProperties": false + } + ] + } + } + }, + "list_voters": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "VoterListResponse", + "type": "object", + "required": [ + "voters" + ], + "properties": { + "voters": { + "type": "array", + "items": { + "$ref": "#/definitions/VoterDetail" + } + } + }, + "additionalProperties": false, + "definitions": { + "VoterDetail": { + "type": "object", + "required": [ + "addr", + "weight" + ], + "properties": { + "addr": { + "type": "string" + }, + "weight": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + } + }, + "list_votes": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "VoteListResponse", + "type": "object", + "required": [ + "votes" + ], + "properties": { + "votes": { + "type": "array", + "items": { + "$ref": "#/definitions/VoteInfo" + } + } + }, + "additionalProperties": false, + "definitions": { + "Vote": { + "oneOf": [ + { + "description": "Marks support for the proposal.", + "type": "string", + "enum": [ + "yes" + ] + }, + { + "description": "Marks opposition to the proposal.", + "type": "string", + "enum": [ + "no" + ] + }, + { + "description": "Marks participation but does not count towards the ratio of support / opposed", + "type": "string", + "enum": [ + "abstain" + ] + }, + { + "description": "Veto is generally to be treated as a No vote. Some implementations may allow certain voters to be able to Veto, or them to be counted stronger than No in some way.", + "type": "string", + "enum": [ + "veto" + ] + } + ] + }, + "VoteInfo": { + "description": "Returns the vote (opinion as well as weight counted) as well as the address of the voter who submitted it", + "type": "object", + "required": [ + "proposal_id", + "vote", + "voter", + "weight" + ], + "properties": { + "proposal_id": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "vote": { + "$ref": "#/definitions/Vote" + }, + "voter": { + "type": "string" + }, + "weight": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + } + }, + "proposal": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ProposalResponse_for_Empty", + "description": "Note, if you are storing custom messages in the proposal, the querier needs to know what possible custom message types those are in order to parse the response", + "type": "object", + "required": [ + "description", + "expires", + "id", + "msgs", + "proposer", + "status", + "threshold", + "title" + ], + "properties": { + "deposit": { + "anyOf": [ + { + "$ref": "#/definitions/DepositInfo" + }, + { + "type": "null" + } + ] + }, + "description": { + "type": "string" + }, + "expires": { + "$ref": "#/definitions/Expiration" + }, + "id": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "msgs": { + "type": "array", + "items": { + "$ref": "#/definitions/CosmosMsg_for_Empty" + } + }, + "proposer": { + "$ref": "#/definitions/Addr" + }, + "status": { + "$ref": "#/definitions/Status" + }, + "threshold": { + "description": "This is the threshold that is applied to this proposal. Both the rules of the voting contract, as well as the total_weight of the voting group may have changed since this time. That means that the generic `Threshold{}` query does not provide valid information for existing proposals.", + "allOf": [ + { + "$ref": "#/definitions/ThresholdResponse" + } + ] + }, + "title": { + "type": "string" + } + }, + "additionalProperties": false, + "definitions": { + "Addr": { + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + }, + "BankMsg": { + "description": "The message types of the bank module.\n\nSee https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/bank/v1beta1/tx.proto", + "oneOf": [ + { + "description": "Sends native tokens from the contract to the given address.\n\nThis is translated to a [MsgSend](https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/bank/v1beta1/tx.proto#L19-L28). `from_address` is automatically filled with the current contract's address.", + "type": "object", + "required": [ + "send" + ], + "properties": { + "send": { + "type": "object", + "required": [ + "amount", + "to_address" + ], + "properties": { + "amount": { + "type": "array", + "items": { + "$ref": "#/definitions/Coin" + } + }, + "to_address": { + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "This will burn the given coins from the contract's account. There is no Cosmos SDK message that performs this, but it can be done by calling the bank keeper. Important if a contract controls significant token supply that must be retired.", + "type": "object", + "required": [ + "burn" + ], + "properties": { + "burn": { + "type": "object", + "required": [ + "amount" + ], + "properties": { + "amount": { + "type": "array", + "items": { + "$ref": "#/definitions/Coin" + } + } + } + } + }, + "additionalProperties": false + } + ] + }, + "Binary": { + "description": "Binary is a wrapper around Vec to add base64 de/serialization with serde. It also adds some helper methods to help encode inline.\n\nThis is only needed as serde-json-{core,wasm} has a horrible encoding for Vec. See also .", + "type": "string" + }, + "Coin": { + "type": "object", + "required": [ + "amount", + "denom" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "denom": { + "type": "string" + } + } + }, + "CosmosMsg_for_Empty": { + "oneOf": [ + { + "type": "object", + "required": [ + "bank" + ], + "properties": { + "bank": { + "$ref": "#/definitions/BankMsg" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "custom" + ], + "properties": { + "custom": { + "$ref": "#/definitions/Empty" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "staking" + ], + "properties": { + "staking": { + "$ref": "#/definitions/StakingMsg" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "distribution" + ], + "properties": { + "distribution": { + "$ref": "#/definitions/DistributionMsg" + } + }, + "additionalProperties": false + }, + { + "description": "A Stargate message encoded the same way as a protobuf [Any](https://github.com/protocolbuffers/protobuf/blob/master/src/google/protobuf/any.proto). This is the same structure as messages in `TxBody` from [ADR-020](https://github.com/cosmos/cosmos-sdk/blob/master/docs/architecture/adr-020-protobuf-transaction-encoding.md)", + "type": "object", + "required": [ + "stargate" + ], + "properties": { + "stargate": { + "type": "object", + "required": [ + "type_url", + "value" + ], + "properties": { + "type_url": { + "type": "string" + }, + "value": { + "$ref": "#/definitions/Binary" + } + } + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "ibc" + ], + "properties": { + "ibc": { + "$ref": "#/definitions/IbcMsg" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "wasm" + ], + "properties": { + "wasm": { + "$ref": "#/definitions/WasmMsg" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "gov" + ], + "properties": { + "gov": { + "$ref": "#/definitions/GovMsg" + } + }, + "additionalProperties": false + } + ] + }, + "Decimal": { + "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", + "type": "string" + }, + "Denom": { + "oneOf": [ + { + "type": "object", + "required": [ + "native" + ], + "properties": { + "native": { + "type": "string" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "cw20" + ], + "properties": { + "cw20": { + "$ref": "#/definitions/Addr" + } + }, + "additionalProperties": false + } + ] + }, + "DepositInfo": { + "description": "Information about the deposit required to create a proposal.", + "type": "object", + "required": [ + "amount", + "denom", + "refund_failed_proposals" + ], + "properties": { + "amount": { + "description": "The number tokens required for payment.", + "allOf": [ + { + "$ref": "#/definitions/Uint128" + } + ] + }, + "denom": { + "description": "The denom of the deposit payment.", + "allOf": [ + { + "$ref": "#/definitions/Denom" + } + ] + }, + "refund_failed_proposals": { + "description": "Should failed proposals have their deposits refunded?", + "type": "boolean" + } + }, + "additionalProperties": false + }, + "DistributionMsg": { + "description": "The message types of the distribution module.\n\nSee https://github.com/cosmos/cosmos-sdk/blob/v0.42.4/proto/cosmos/distribution/v1beta1/tx.proto", + "oneOf": [ + { + "description": "This is translated to a [MsgSetWithdrawAddress](https://github.com/cosmos/cosmos-sdk/blob/v0.42.4/proto/cosmos/distribution/v1beta1/tx.proto#L29-L37). `delegator_address` is automatically filled with the current contract's address.", + "type": "object", + "required": [ + "set_withdraw_address" + ], + "properties": { + "set_withdraw_address": { + "type": "object", + "required": [ + "address" + ], + "properties": { + "address": { + "description": "The `withdraw_address`", + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "This is translated to a [[MsgWithdrawDelegatorReward](https://github.com/cosmos/cosmos-sdk/blob/v0.42.4/proto/cosmos/distribution/v1beta1/tx.proto#L42-L50). `delegator_address` is automatically filled with the current contract's address.", + "type": "object", + "required": [ + "withdraw_delegator_reward" + ], + "properties": { + "withdraw_delegator_reward": { + "type": "object", + "required": [ + "validator" + ], + "properties": { + "validator": { + "description": "The `validator_address`", + "type": "string" + } + } + } + }, + "additionalProperties": false + } + ] + }, + "Empty": { + "description": "An empty struct that serves as a placeholder in different places, such as contracts that don't set a custom message.\n\nIt is designed to be expressable in correct JSON and JSON Schema but contains no meaningful data. Previously we used enums without cases, but those cannot represented as valid JSON Schema (https://github.com/CosmWasm/cosmwasm/issues/451)", + "type": "object" + }, + "Expiration": { + "description": "Expiration represents a point in time when some event happens. It can compare with a BlockInfo and will return is_expired() == true once the condition is hit (and for every block in the future)", + "oneOf": [ + { + "description": "AtHeight will expire when `env.block.height` >= height", + "type": "object", + "required": [ + "at_height" + ], + "properties": { + "at_height": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + }, + { + "description": "AtTime will expire when `env.block.time` >= time", + "type": "object", + "required": [ + "at_time" + ], + "properties": { + "at_time": { + "$ref": "#/definitions/Timestamp" + } + }, + "additionalProperties": false + }, + { + "description": "Never will never expire. Used to express the empty variant", + "type": "object", + "required": [ + "never" + ], + "properties": { + "never": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + } + ] + }, + "GovMsg": { + "description": "This message type allows the contract interact with the [x/gov] module in order to cast votes.\n\n[x/gov]: https://github.com/cosmos/cosmos-sdk/tree/v0.45.12/x/gov\n\n## Examples\n\nCast a simple vote:\n\n``` # use cosmwasm_std::{ # HexBinary, # Storage, Api, Querier, DepsMut, Deps, entry_point, Env, StdError, MessageInfo, # Response, QueryResponse, # }; # type ExecuteMsg = (); use cosmwasm_std::{GovMsg, VoteOption};\n\n#[entry_point] pub fn execute( deps: DepsMut, env: Env, info: MessageInfo, msg: ExecuteMsg, ) -> Result { // ... Ok(Response::new().add_message(GovMsg::Vote { proposal_id: 4, vote: VoteOption::Yes, })) } ```\n\nCast a weighted vote:\n\n``` # use cosmwasm_std::{ # HexBinary, # Storage, Api, Querier, DepsMut, Deps, entry_point, Env, StdError, MessageInfo, # Response, QueryResponse, # }; # type ExecuteMsg = (); # #[cfg(feature = \"cosmwasm_1_2\")] use cosmwasm_std::{Decimal, GovMsg, VoteOption, WeightedVoteOption};\n\n# #[cfg(feature = \"cosmwasm_1_2\")] #[entry_point] pub fn execute( deps: DepsMut, env: Env, info: MessageInfo, msg: ExecuteMsg, ) -> Result { // ... Ok(Response::new().add_message(GovMsg::VoteWeighted { proposal_id: 4, options: vec![ WeightedVoteOption { option: VoteOption::Yes, weight: Decimal::percent(65), }, WeightedVoteOption { option: VoteOption::Abstain, weight: Decimal::percent(35), }, ], })) } ```", + "oneOf": [ + { + "description": "This maps directly to [MsgVote](https://github.com/cosmos/cosmos-sdk/blob/v0.42.5/proto/cosmos/gov/v1beta1/tx.proto#L46-L56) in the Cosmos SDK with voter set to the contract address.", + "type": "object", + "required": [ + "vote" + ], + "properties": { + "vote": { + "type": "object", + "required": [ + "proposal_id", + "vote" + ], + "properties": { + "proposal_id": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "vote": { + "description": "The vote option.\n\nThis should be called \"option\" for consistency with Cosmos SDK. Sorry for that. See .", + "allOf": [ + { + "$ref": "#/definitions/VoteOption" + } + ] + } + } + } + }, + "additionalProperties": false + } + ] + }, + "IbcMsg": { + "description": "These are messages in the IBC lifecycle. Only usable by IBC-enabled contracts (contracts that directly speak the IBC protocol via 6 entry points)", + "oneOf": [ + { + "description": "Sends bank tokens owned by the contract to the given address on another chain. The channel must already be established between the ibctransfer module on this chain and a matching module on the remote chain. We cannot select the port_id, this is whatever the local chain has bound the ibctransfer module to.", + "type": "object", + "required": [ + "transfer" + ], + "properties": { + "transfer": { + "type": "object", + "required": [ + "amount", + "channel_id", + "timeout", + "to_address" + ], + "properties": { + "amount": { + "description": "packet data only supports one coin https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/ibc/applications/transfer/v1/transfer.proto#L11-L20", + "allOf": [ + { + "$ref": "#/definitions/Coin" + } + ] + }, + "channel_id": { + "description": "exisiting channel to send the tokens over", + "type": "string" + }, + "timeout": { + "description": "when packet times out, measured on remote chain", + "allOf": [ + { + "$ref": "#/definitions/IbcTimeout" + } + ] + }, + "to_address": { + "description": "address on the remote chain to receive these tokens", + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Sends an IBC packet with given data over the existing channel. Data should be encoded in a format defined by the channel version, and the module on the other side should know how to parse this.", + "type": "object", + "required": [ + "send_packet" + ], + "properties": { + "send_packet": { + "type": "object", + "required": [ + "channel_id", + "data", + "timeout" + ], + "properties": { + "channel_id": { + "type": "string" + }, + "data": { + "$ref": "#/definitions/Binary" + }, + "timeout": { + "description": "when packet times out, measured on remote chain", + "allOf": [ + { + "$ref": "#/definitions/IbcTimeout" + } + ] + } + } + } + }, + "additionalProperties": false + }, + { + "description": "This will close an existing channel that is owned by this contract. Port is auto-assigned to the contract's IBC port", + "type": "object", + "required": [ + "close_channel" + ], + "properties": { + "close_channel": { + "type": "object", + "required": [ + "channel_id" + ], + "properties": { + "channel_id": { + "type": "string" + } + } + } + }, + "additionalProperties": false + } + ] + }, + "IbcTimeout": { + "description": "In IBC each package must set at least one type of timeout: the timestamp or the block height. Using this rather complex enum instead of two timeout fields we ensure that at least one timeout is set.", + "type": "object", + "properties": { + "block": { + "anyOf": [ + { + "$ref": "#/definitions/IbcTimeoutBlock" + }, + { + "type": "null" + } + ] + }, + "timestamp": { + "anyOf": [ + { + "$ref": "#/definitions/Timestamp" + }, + { + "type": "null" + } + ] + } + } + }, + "IbcTimeoutBlock": { + "description": "IBCTimeoutHeight Height is a monotonically increasing data type that can be compared against another Height for the purposes of updating and freezing clients. Ordering is (revision_number, timeout_height)", + "type": "object", + "required": [ + "height", + "revision" + ], + "properties": { + "height": { + "description": "block height after which the packet times out. the height within the given revision", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "revision": { + "description": "the version that the client is currently on (eg. after reseting the chain this could increment 1 as height drops to 0)", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + } + }, + "StakingMsg": { + "description": "The message types of the staking module.\n\nSee https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/staking/v1beta1/tx.proto", + "oneOf": [ + { + "description": "This is translated to a [MsgDelegate](https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/staking/v1beta1/tx.proto#L81-L90). `delegator_address` is automatically filled with the current contract's address.", + "type": "object", + "required": [ + "delegate" + ], + "properties": { + "delegate": { + "type": "object", + "required": [ + "amount", + "validator" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Coin" + }, + "validator": { + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "This is translated to a [MsgUndelegate](https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/staking/v1beta1/tx.proto#L112-L121). `delegator_address` is automatically filled with the current contract's address.", + "type": "object", + "required": [ + "undelegate" + ], + "properties": { + "undelegate": { + "type": "object", + "required": [ + "amount", + "validator" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Coin" + }, + "validator": { + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "This is translated to a [MsgBeginRedelegate](https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/staking/v1beta1/tx.proto#L95-L105). `delegator_address` is automatically filled with the current contract's address.", + "type": "object", + "required": [ + "redelegate" + ], + "properties": { + "redelegate": { + "type": "object", + "required": [ + "amount", + "dst_validator", + "src_validator" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Coin" + }, + "dst_validator": { + "type": "string" + }, + "src_validator": { + "type": "string" + } + } + } + }, + "additionalProperties": false + } + ] + }, + "Status": { + "oneOf": [ + { + "description": "proposal was created, but voting has not yet begun for whatever reason", + "type": "string", + "enum": [ + "pending" + ] + }, + { + "description": "you can vote on this", + "type": "string", + "enum": [ + "open" + ] + }, + { + "description": "voting is over and it did not pass", + "type": "string", + "enum": [ + "rejected" + ] + }, + { + "description": "voting is over and it did pass, but has not yet executed", + "type": "string", + "enum": [ + "passed" + ] + }, + { + "description": "voting is over it passed, and the proposal was executed", + "type": "string", + "enum": [ + "executed" + ] + } + ] + }, + "ThresholdResponse": { + "description": "This defines the different ways tallies can happen. Every contract should support a subset of these, ideally all.\n\nThe total_weight used for calculating success as well as the weights of each individual voter used in tallying should be snapshotted at the beginning of the block at which the proposal starts (this is likely the responsibility of a correct cw4 implementation).", + "oneOf": [ + { + "description": "Declares that a fixed weight of yes votes is needed to pass. It does not matter how many no votes are cast, or how many do not vote, as long as `weight` yes votes are cast.\n\nThis is the simplest format and usually suitable for small multisigs of trusted parties, like 3 of 5. (weight: 3, total_weight: 5)\n\nA proposal of this type can pass early as soon as the needed weight of yes votes has been cast.", + "type": "object", + "required": [ + "absolute_count" + ], + "properties": { + "absolute_count": { + "type": "object", + "required": [ + "total_weight", + "weight" + ], + "properties": { + "total_weight": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "weight": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Declares a percentage of the total weight that must cast Yes votes, in order for a proposal to pass. The passing weight is computed over the total weight minus the weight of the abstained votes.\n\nThis is useful for similar circumstances as `AbsoluteCount`, where we have a relatively small set of voters, and participation is required. It is understood that if the voting set (group) changes between different proposals that refer to the same group, each proposal will work with a different set of voter weights (the ones snapshotted at proposal creation), and the passing weight for each proposal will be computed based on the absolute percentage, times the total weights of the members at the time of each proposal creation.\n\nExample: we set `percentage` to 51%. Proposal 1 starts when there is a `total_weight` of 5. This will require 3 weight of Yes votes in order to pass. Later, the Proposal 2 starts but the `total_weight` of the group has increased to 9. That proposal will then automatically require 5 Yes of 9 to pass, rather than 3 yes of 9 as would be the case with `AbsoluteCount`.", + "type": "object", + "required": [ + "absolute_percentage" + ], + "properties": { + "absolute_percentage": { + "type": "object", + "required": [ + "percentage", + "total_weight" + ], + "properties": { + "percentage": { + "$ref": "#/definitions/Decimal" + }, + "total_weight": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "In addition to a `threshold`, declares a `quorum` of the total votes that must participate in the election in order for the vote to be considered at all. Within the votes that were cast, it requires `threshold` votes in favor. That is calculated by ignoring the Abstain votes (they count towards `quorum`, but do not influence `threshold`). That is, we calculate `Yes / (Yes + No + Veto)` and compare it with `threshold` to consider if the proposal was passed.\n\nIt is rather difficult for a proposal of this type to pass early. That can only happen if the required quorum has been already met, and there are already enough Yes votes for the proposal to pass.\n\n30% Yes votes, 10% No votes, and 20% Abstain would pass early if quorum <= 60% (who has cast votes) and if the threshold is <= 37.5% (the remaining 40% voting no => 30% yes + 50% no). Once the voting period has passed with no additional votes, that same proposal would be considered successful if quorum <= 60% and threshold <= 75% (percent in favor if we ignore abstain votes).\n\nThis type is more common in general elections, where participation is often expected to be low, and `AbsolutePercentage` would either be too high to pass anything, or allow low percentages to pass, independently of if there was high participation in the election or not.", + "type": "object", + "required": [ + "threshold_quorum" + ], + "properties": { + "threshold_quorum": { + "type": "object", + "required": [ + "quorum", + "threshold", + "total_weight" + ], + "properties": { + "quorum": { + "$ref": "#/definitions/Decimal" + }, + "threshold": { + "$ref": "#/definitions/Decimal" + }, + "total_weight": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + ] + }, + "Timestamp": { + "description": "A point in time in nanosecond precision.\n\nThis type can represent times from 1970-01-01T00:00:00Z to 2554-07-21T23:34:33Z.\n\n## Examples\n\n``` # use cosmwasm_std::Timestamp; let ts = Timestamp::from_nanos(1_000_000_202); assert_eq!(ts.nanos(), 1_000_000_202); assert_eq!(ts.seconds(), 1); assert_eq!(ts.subsec_nanos(), 202);\n\nlet ts = ts.plus_seconds(2); assert_eq!(ts.nanos(), 3_000_000_202); assert_eq!(ts.seconds(), 3); assert_eq!(ts.subsec_nanos(), 202); ```", + "allOf": [ + { + "$ref": "#/definitions/Uint64" + } + ] + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + }, + "Uint64": { + "description": "A thin wrapper around u64 that is using strings for JSON encoding/decoding, such that the full u64 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u64` to get the value out:\n\n``` # use cosmwasm_std::Uint64; let a = Uint64::from(42u64); assert_eq!(a.u64(), 42);\n\nlet b = Uint64::from(70u32); assert_eq!(b.u64(), 70); ```", + "type": "string" + }, + "VoteOption": { + "type": "string", + "enum": [ + "yes", + "no", + "abstain", + "no_with_veto" + ] + }, + "WasmMsg": { + "description": "The message types of the wasm module.\n\nSee https://github.com/CosmWasm/wasmd/blob/v0.14.0/x/wasm/internal/types/tx.proto", + "oneOf": [ + { + "description": "Dispatches a call to another contract at a known address (with known ABI).\n\nThis is translated to a [MsgExecuteContract](https://github.com/CosmWasm/wasmd/blob/v0.14.0/x/wasm/internal/types/tx.proto#L68-L78). `sender` is automatically filled with the current contract's address.", + "type": "object", + "required": [ + "execute" + ], + "properties": { + "execute": { + "type": "object", + "required": [ + "contract_addr", + "funds", + "msg" + ], + "properties": { + "contract_addr": { + "type": "string" + }, + "funds": { + "type": "array", + "items": { + "$ref": "#/definitions/Coin" + } + }, + "msg": { + "description": "msg is the json-encoded ExecuteMsg struct (as raw Binary)", + "allOf": [ + { + "$ref": "#/definitions/Binary" + } + ] + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Instantiates a new contracts from previously uploaded Wasm code.\n\nThe contract address is non-predictable. But it is guaranteed that when emitting the same Instantiate message multiple times, multiple instances on different addresses will be generated. See also Instantiate2.\n\nThis is translated to a [MsgInstantiateContract](https://github.com/CosmWasm/wasmd/blob/v0.29.2/proto/cosmwasm/wasm/v1/tx.proto#L53-L71). `sender` is automatically filled with the current contract's address.", + "type": "object", + "required": [ + "instantiate" + ], + "properties": { + "instantiate": { + "type": "object", + "required": [ + "code_id", + "funds", + "label", + "msg" + ], + "properties": { + "admin": { + "type": [ + "string", + "null" + ] + }, + "code_id": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "funds": { + "type": "array", + "items": { + "$ref": "#/definitions/Coin" + } + }, + "label": { + "description": "A human-readbale label for the contract", + "type": "string" + }, + "msg": { + "description": "msg is the JSON-encoded InstantiateMsg struct (as raw Binary)", + "allOf": [ + { + "$ref": "#/definitions/Binary" + } + ] + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Migrates a given contracts to use new wasm code. Passes a MigrateMsg to allow us to customize behavior.\n\nOnly the contract admin (as defined in wasmd), if any, is able to make this call.\n\nThis is translated to a [MsgMigrateContract](https://github.com/CosmWasm/wasmd/blob/v0.14.0/x/wasm/internal/types/tx.proto#L86-L96). `sender` is automatically filled with the current contract's address.", + "type": "object", + "required": [ + "migrate" + ], + "properties": { + "migrate": { + "type": "object", + "required": [ + "contract_addr", + "msg", + "new_code_id" + ], + "properties": { + "contract_addr": { + "type": "string" + }, + "msg": { + "description": "msg is the json-encoded MigrateMsg struct that will be passed to the new code", + "allOf": [ + { + "$ref": "#/definitions/Binary" + } + ] + }, + "new_code_id": { + "description": "the code_id of the new logic to place in the given contract", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Sets a new admin (for migrate) on the given contract. Fails if this contract is not currently admin of the target contract.", + "type": "object", + "required": [ + "update_admin" + ], + "properties": { + "update_admin": { + "type": "object", + "required": [ + "admin", + "contract_addr" + ], + "properties": { + "admin": { + "type": "string" + }, + "contract_addr": { + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Clears the admin on the given contract, so no more migration possible. Fails if this contract is not currently admin of the target contract.", + "type": "object", + "required": [ + "clear_admin" + ], + "properties": { + "clear_admin": { + "type": "object", + "required": [ + "contract_addr" + ], + "properties": { + "contract_addr": { + "type": "string" + } + } + } + }, + "additionalProperties": false + } + ] + } + } + }, + "reverse_proposals": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ProposalListResponse_for_Empty", + "type": "object", + "required": [ + "proposals" + ], + "properties": { + "proposals": { + "type": "array", + "items": { + "$ref": "#/definitions/ProposalResponse_for_Empty" + } + } + }, + "additionalProperties": false, + "definitions": { + "Addr": { + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + }, + "BankMsg": { + "description": "The message types of the bank module.\n\nSee https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/bank/v1beta1/tx.proto", + "oneOf": [ + { + "description": "Sends native tokens from the contract to the given address.\n\nThis is translated to a [MsgSend](https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/bank/v1beta1/tx.proto#L19-L28). `from_address` is automatically filled with the current contract's address.", + "type": "object", + "required": [ + "send" + ], + "properties": { + "send": { + "type": "object", + "required": [ + "amount", + "to_address" + ], + "properties": { + "amount": { + "type": "array", + "items": { + "$ref": "#/definitions/Coin" + } + }, + "to_address": { + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "This will burn the given coins from the contract's account. There is no Cosmos SDK message that performs this, but it can be done by calling the bank keeper. Important if a contract controls significant token supply that must be retired.", + "type": "object", + "required": [ + "burn" + ], + "properties": { + "burn": { + "type": "object", + "required": [ + "amount" + ], + "properties": { + "amount": { + "type": "array", + "items": { + "$ref": "#/definitions/Coin" + } + } + } + } + }, + "additionalProperties": false + } + ] + }, + "Binary": { + "description": "Binary is a wrapper around Vec to add base64 de/serialization with serde. It also adds some helper methods to help encode inline.\n\nThis is only needed as serde-json-{core,wasm} has a horrible encoding for Vec. See also .", + "type": "string" + }, + "Coin": { + "type": "object", + "required": [ + "amount", + "denom" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "denom": { + "type": "string" + } + } + }, + "CosmosMsg_for_Empty": { + "oneOf": [ + { + "type": "object", + "required": [ + "bank" + ], + "properties": { + "bank": { + "$ref": "#/definitions/BankMsg" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "custom" + ], + "properties": { + "custom": { + "$ref": "#/definitions/Empty" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "staking" + ], + "properties": { + "staking": { + "$ref": "#/definitions/StakingMsg" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "distribution" + ], + "properties": { + "distribution": { + "$ref": "#/definitions/DistributionMsg" + } + }, + "additionalProperties": false + }, + { + "description": "A Stargate message encoded the same way as a protobuf [Any](https://github.com/protocolbuffers/protobuf/blob/master/src/google/protobuf/any.proto). This is the same structure as messages in `TxBody` from [ADR-020](https://github.com/cosmos/cosmos-sdk/blob/master/docs/architecture/adr-020-protobuf-transaction-encoding.md)", + "type": "object", + "required": [ + "stargate" + ], + "properties": { + "stargate": { + "type": "object", + "required": [ + "type_url", + "value" + ], + "properties": { + "type_url": { + "type": "string" + }, + "value": { + "$ref": "#/definitions/Binary" + } + } + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "ibc" + ], + "properties": { + "ibc": { + "$ref": "#/definitions/IbcMsg" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "wasm" + ], + "properties": { + "wasm": { + "$ref": "#/definitions/WasmMsg" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "gov" + ], + "properties": { + "gov": { + "$ref": "#/definitions/GovMsg" + } + }, + "additionalProperties": false + } + ] + }, + "Decimal": { + "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", + "type": "string" + }, + "Denom": { + "oneOf": [ + { + "type": "object", + "required": [ + "native" + ], + "properties": { + "native": { + "type": "string" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "cw20" + ], + "properties": { + "cw20": { + "$ref": "#/definitions/Addr" + } + }, + "additionalProperties": false + } + ] + }, + "DepositInfo": { + "description": "Information about the deposit required to create a proposal.", + "type": "object", + "required": [ + "amount", + "denom", + "refund_failed_proposals" + ], + "properties": { + "amount": { + "description": "The number tokens required for payment.", + "allOf": [ + { + "$ref": "#/definitions/Uint128" + } + ] + }, + "denom": { + "description": "The denom of the deposit payment.", + "allOf": [ + { + "$ref": "#/definitions/Denom" + } + ] + }, + "refund_failed_proposals": { + "description": "Should failed proposals have their deposits refunded?", + "type": "boolean" + } + }, + "additionalProperties": false + }, + "DistributionMsg": { + "description": "The message types of the distribution module.\n\nSee https://github.com/cosmos/cosmos-sdk/blob/v0.42.4/proto/cosmos/distribution/v1beta1/tx.proto", + "oneOf": [ + { + "description": "This is translated to a [MsgSetWithdrawAddress](https://github.com/cosmos/cosmos-sdk/blob/v0.42.4/proto/cosmos/distribution/v1beta1/tx.proto#L29-L37). `delegator_address` is automatically filled with the current contract's address.", + "type": "object", + "required": [ + "set_withdraw_address" + ], + "properties": { + "set_withdraw_address": { + "type": "object", + "required": [ + "address" + ], + "properties": { + "address": { + "description": "The `withdraw_address`", + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "This is translated to a [[MsgWithdrawDelegatorReward](https://github.com/cosmos/cosmos-sdk/blob/v0.42.4/proto/cosmos/distribution/v1beta1/tx.proto#L42-L50). `delegator_address` is automatically filled with the current contract's address.", + "type": "object", + "required": [ + "withdraw_delegator_reward" + ], + "properties": { + "withdraw_delegator_reward": { + "type": "object", + "required": [ + "validator" + ], + "properties": { + "validator": { + "description": "The `validator_address`", + "type": "string" + } + } + } + }, + "additionalProperties": false + } + ] + }, + "Empty": { + "description": "An empty struct that serves as a placeholder in different places, such as contracts that don't set a custom message.\n\nIt is designed to be expressable in correct JSON and JSON Schema but contains no meaningful data. Previously we used enums without cases, but those cannot represented as valid JSON Schema (https://github.com/CosmWasm/cosmwasm/issues/451)", + "type": "object" + }, + "Expiration": { + "description": "Expiration represents a point in time when some event happens. It can compare with a BlockInfo and will return is_expired() == true once the condition is hit (and for every block in the future)", + "oneOf": [ + { + "description": "AtHeight will expire when `env.block.height` >= height", + "type": "object", + "required": [ + "at_height" + ], + "properties": { + "at_height": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + }, + { + "description": "AtTime will expire when `env.block.time` >= time", + "type": "object", + "required": [ + "at_time" + ], + "properties": { + "at_time": { + "$ref": "#/definitions/Timestamp" + } + }, + "additionalProperties": false + }, + { + "description": "Never will never expire. Used to express the empty variant", + "type": "object", + "required": [ + "never" + ], + "properties": { + "never": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + } + ] + }, + "GovMsg": { + "description": "This message type allows the contract interact with the [x/gov] module in order to cast votes.\n\n[x/gov]: https://github.com/cosmos/cosmos-sdk/tree/v0.45.12/x/gov\n\n## Examples\n\nCast a simple vote:\n\n``` # use cosmwasm_std::{ # HexBinary, # Storage, Api, Querier, DepsMut, Deps, entry_point, Env, StdError, MessageInfo, # Response, QueryResponse, # }; # type ExecuteMsg = (); use cosmwasm_std::{GovMsg, VoteOption};\n\n#[entry_point] pub fn execute( deps: DepsMut, env: Env, info: MessageInfo, msg: ExecuteMsg, ) -> Result { // ... Ok(Response::new().add_message(GovMsg::Vote { proposal_id: 4, vote: VoteOption::Yes, })) } ```\n\nCast a weighted vote:\n\n``` # use cosmwasm_std::{ # HexBinary, # Storage, Api, Querier, DepsMut, Deps, entry_point, Env, StdError, MessageInfo, # Response, QueryResponse, # }; # type ExecuteMsg = (); # #[cfg(feature = \"cosmwasm_1_2\")] use cosmwasm_std::{Decimal, GovMsg, VoteOption, WeightedVoteOption};\n\n# #[cfg(feature = \"cosmwasm_1_2\")] #[entry_point] pub fn execute( deps: DepsMut, env: Env, info: MessageInfo, msg: ExecuteMsg, ) -> Result { // ... Ok(Response::new().add_message(GovMsg::VoteWeighted { proposal_id: 4, options: vec![ WeightedVoteOption { option: VoteOption::Yes, weight: Decimal::percent(65), }, WeightedVoteOption { option: VoteOption::Abstain, weight: Decimal::percent(35), }, ], })) } ```", + "oneOf": [ + { + "description": "This maps directly to [MsgVote](https://github.com/cosmos/cosmos-sdk/blob/v0.42.5/proto/cosmos/gov/v1beta1/tx.proto#L46-L56) in the Cosmos SDK with voter set to the contract address.", + "type": "object", + "required": [ + "vote" + ], + "properties": { + "vote": { + "type": "object", + "required": [ + "proposal_id", + "vote" + ], + "properties": { + "proposal_id": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "vote": { + "description": "The vote option.\n\nThis should be called \"option\" for consistency with Cosmos SDK. Sorry for that. See .", + "allOf": [ + { + "$ref": "#/definitions/VoteOption" + } + ] + } + } + } + }, + "additionalProperties": false + } + ] + }, + "IbcMsg": { + "description": "These are messages in the IBC lifecycle. Only usable by IBC-enabled contracts (contracts that directly speak the IBC protocol via 6 entry points)", + "oneOf": [ + { + "description": "Sends bank tokens owned by the contract to the given address on another chain. The channel must already be established between the ibctransfer module on this chain and a matching module on the remote chain. We cannot select the port_id, this is whatever the local chain has bound the ibctransfer module to.", + "type": "object", + "required": [ + "transfer" + ], + "properties": { + "transfer": { + "type": "object", + "required": [ + "amount", + "channel_id", + "timeout", + "to_address" + ], + "properties": { + "amount": { + "description": "packet data only supports one coin https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/ibc/applications/transfer/v1/transfer.proto#L11-L20", + "allOf": [ + { + "$ref": "#/definitions/Coin" + } + ] + }, + "channel_id": { + "description": "exisiting channel to send the tokens over", + "type": "string" + }, + "timeout": { + "description": "when packet times out, measured on remote chain", + "allOf": [ + { + "$ref": "#/definitions/IbcTimeout" + } + ] + }, + "to_address": { + "description": "address on the remote chain to receive these tokens", + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Sends an IBC packet with given data over the existing channel. Data should be encoded in a format defined by the channel version, and the module on the other side should know how to parse this.", + "type": "object", + "required": [ + "send_packet" + ], + "properties": { + "send_packet": { + "type": "object", + "required": [ + "channel_id", + "data", + "timeout" + ], + "properties": { + "channel_id": { + "type": "string" + }, + "data": { + "$ref": "#/definitions/Binary" + }, + "timeout": { + "description": "when packet times out, measured on remote chain", + "allOf": [ + { + "$ref": "#/definitions/IbcTimeout" + } + ] + } + } + } + }, + "additionalProperties": false + }, + { + "description": "This will close an existing channel that is owned by this contract. Port is auto-assigned to the contract's IBC port", + "type": "object", + "required": [ + "close_channel" + ], + "properties": { + "close_channel": { + "type": "object", + "required": [ + "channel_id" + ], + "properties": { + "channel_id": { + "type": "string" + } + } + } + }, + "additionalProperties": false + } + ] + }, + "IbcTimeout": { + "description": "In IBC each package must set at least one type of timeout: the timestamp or the block height. Using this rather complex enum instead of two timeout fields we ensure that at least one timeout is set.", + "type": "object", + "properties": { + "block": { + "anyOf": [ + { + "$ref": "#/definitions/IbcTimeoutBlock" + }, + { + "type": "null" + } + ] + }, + "timestamp": { + "anyOf": [ + { + "$ref": "#/definitions/Timestamp" + }, + { + "type": "null" + } + ] + } + } + }, + "IbcTimeoutBlock": { + "description": "IBCTimeoutHeight Height is a monotonically increasing data type that can be compared against another Height for the purposes of updating and freezing clients. Ordering is (revision_number, timeout_height)", + "type": "object", + "required": [ + "height", + "revision" + ], + "properties": { + "height": { + "description": "block height after which the packet times out. the height within the given revision", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "revision": { + "description": "the version that the client is currently on (eg. after reseting the chain this could increment 1 as height drops to 0)", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + } + }, + "ProposalResponse_for_Empty": { + "description": "Note, if you are storing custom messages in the proposal, the querier needs to know what possible custom message types those are in order to parse the response", + "type": "object", + "required": [ + "description", + "expires", + "id", + "msgs", + "proposer", + "status", + "threshold", + "title" + ], + "properties": { + "deposit": { + "anyOf": [ + { + "$ref": "#/definitions/DepositInfo" + }, + { + "type": "null" + } + ] + }, + "description": { + "type": "string" + }, + "expires": { + "$ref": "#/definitions/Expiration" + }, + "id": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "msgs": { + "type": "array", + "items": { + "$ref": "#/definitions/CosmosMsg_for_Empty" + } + }, + "proposer": { + "$ref": "#/definitions/Addr" + }, + "status": { + "$ref": "#/definitions/Status" + }, + "threshold": { + "description": "This is the threshold that is applied to this proposal. Both the rules of the voting contract, as well as the total_weight of the voting group may have changed since this time. That means that the generic `Threshold{}` query does not provide valid information for existing proposals.", + "allOf": [ + { + "$ref": "#/definitions/ThresholdResponse" + } + ] + }, + "title": { + "type": "string" + } + }, + "additionalProperties": false + }, + "StakingMsg": { + "description": "The message types of the staking module.\n\nSee https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/staking/v1beta1/tx.proto", + "oneOf": [ + { + "description": "This is translated to a [MsgDelegate](https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/staking/v1beta1/tx.proto#L81-L90). `delegator_address` is automatically filled with the current contract's address.", + "type": "object", + "required": [ + "delegate" + ], + "properties": { + "delegate": { + "type": "object", + "required": [ + "amount", + "validator" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Coin" + }, + "validator": { + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "This is translated to a [MsgUndelegate](https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/staking/v1beta1/tx.proto#L112-L121). `delegator_address` is automatically filled with the current contract's address.", + "type": "object", + "required": [ + "undelegate" + ], + "properties": { + "undelegate": { + "type": "object", + "required": [ + "amount", + "validator" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Coin" + }, + "validator": { + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "This is translated to a [MsgBeginRedelegate](https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/staking/v1beta1/tx.proto#L95-L105). `delegator_address` is automatically filled with the current contract's address.", + "type": "object", + "required": [ + "redelegate" + ], + "properties": { + "redelegate": { + "type": "object", + "required": [ + "amount", + "dst_validator", + "src_validator" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Coin" + }, + "dst_validator": { + "type": "string" + }, + "src_validator": { + "type": "string" + } + } + } + }, + "additionalProperties": false + } + ] + }, + "Status": { + "oneOf": [ + { + "description": "proposal was created, but voting has not yet begun for whatever reason", + "type": "string", + "enum": [ + "pending" + ] + }, + { + "description": "you can vote on this", + "type": "string", + "enum": [ + "open" + ] + }, + { + "description": "voting is over and it did not pass", + "type": "string", + "enum": [ + "rejected" + ] + }, + { + "description": "voting is over and it did pass, but has not yet executed", + "type": "string", + "enum": [ + "passed" + ] + }, + { + "description": "voting is over it passed, and the proposal was executed", + "type": "string", + "enum": [ + "executed" + ] + } + ] + }, + "ThresholdResponse": { + "description": "This defines the different ways tallies can happen. Every contract should support a subset of these, ideally all.\n\nThe total_weight used for calculating success as well as the weights of each individual voter used in tallying should be snapshotted at the beginning of the block at which the proposal starts (this is likely the responsibility of a correct cw4 implementation).", + "oneOf": [ + { + "description": "Declares that a fixed weight of yes votes is needed to pass. It does not matter how many no votes are cast, or how many do not vote, as long as `weight` yes votes are cast.\n\nThis is the simplest format and usually suitable for small multisigs of trusted parties, like 3 of 5. (weight: 3, total_weight: 5)\n\nA proposal of this type can pass early as soon as the needed weight of yes votes has been cast.", + "type": "object", + "required": [ + "absolute_count" + ], + "properties": { + "absolute_count": { + "type": "object", + "required": [ + "total_weight", + "weight" + ], + "properties": { + "total_weight": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "weight": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Declares a percentage of the total weight that must cast Yes votes, in order for a proposal to pass. The passing weight is computed over the total weight minus the weight of the abstained votes.\n\nThis is useful for similar circumstances as `AbsoluteCount`, where we have a relatively small set of voters, and participation is required. It is understood that if the voting set (group) changes between different proposals that refer to the same group, each proposal will work with a different set of voter weights (the ones snapshotted at proposal creation), and the passing weight for each proposal will be computed based on the absolute percentage, times the total weights of the members at the time of each proposal creation.\n\nExample: we set `percentage` to 51%. Proposal 1 starts when there is a `total_weight` of 5. This will require 3 weight of Yes votes in order to pass. Later, the Proposal 2 starts but the `total_weight` of the group has increased to 9. That proposal will then automatically require 5 Yes of 9 to pass, rather than 3 yes of 9 as would be the case with `AbsoluteCount`.", + "type": "object", + "required": [ + "absolute_percentage" + ], + "properties": { + "absolute_percentage": { + "type": "object", + "required": [ + "percentage", + "total_weight" + ], + "properties": { + "percentage": { + "$ref": "#/definitions/Decimal" + }, + "total_weight": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "In addition to a `threshold`, declares a `quorum` of the total votes that must participate in the election in order for the vote to be considered at all. Within the votes that were cast, it requires `threshold` votes in favor. That is calculated by ignoring the Abstain votes (they count towards `quorum`, but do not influence `threshold`). That is, we calculate `Yes / (Yes + No + Veto)` and compare it with `threshold` to consider if the proposal was passed.\n\nIt is rather difficult for a proposal of this type to pass early. That can only happen if the required quorum has been already met, and there are already enough Yes votes for the proposal to pass.\n\n30% Yes votes, 10% No votes, and 20% Abstain would pass early if quorum <= 60% (who has cast votes) and if the threshold is <= 37.5% (the remaining 40% voting no => 30% yes + 50% no). Once the voting period has passed with no additional votes, that same proposal would be considered successful if quorum <= 60% and threshold <= 75% (percent in favor if we ignore abstain votes).\n\nThis type is more common in general elections, where participation is often expected to be low, and `AbsolutePercentage` would either be too high to pass anything, or allow low percentages to pass, independently of if there was high participation in the election or not.", + "type": "object", + "required": [ + "threshold_quorum" + ], + "properties": { + "threshold_quorum": { + "type": "object", + "required": [ + "quorum", + "threshold", + "total_weight" + ], + "properties": { + "quorum": { + "$ref": "#/definitions/Decimal" + }, + "threshold": { + "$ref": "#/definitions/Decimal" + }, + "total_weight": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + ] + }, + "Timestamp": { + "description": "A point in time in nanosecond precision.\n\nThis type can represent times from 1970-01-01T00:00:00Z to 2554-07-21T23:34:33Z.\n\n## Examples\n\n``` # use cosmwasm_std::Timestamp; let ts = Timestamp::from_nanos(1_000_000_202); assert_eq!(ts.nanos(), 1_000_000_202); assert_eq!(ts.seconds(), 1); assert_eq!(ts.subsec_nanos(), 202);\n\nlet ts = ts.plus_seconds(2); assert_eq!(ts.nanos(), 3_000_000_202); assert_eq!(ts.seconds(), 3); assert_eq!(ts.subsec_nanos(), 202); ```", + "allOf": [ + { + "$ref": "#/definitions/Uint64" + } + ] + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + }, + "Uint64": { + "description": "A thin wrapper around u64 that is using strings for JSON encoding/decoding, such that the full u64 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u64` to get the value out:\n\n``` # use cosmwasm_std::Uint64; let a = Uint64::from(42u64); assert_eq!(a.u64(), 42);\n\nlet b = Uint64::from(70u32); assert_eq!(b.u64(), 70); ```", + "type": "string" + }, + "VoteOption": { + "type": "string", + "enum": [ + "yes", + "no", + "abstain", + "no_with_veto" + ] + }, + "WasmMsg": { + "description": "The message types of the wasm module.\n\nSee https://github.com/CosmWasm/wasmd/blob/v0.14.0/x/wasm/internal/types/tx.proto", + "oneOf": [ + { + "description": "Dispatches a call to another contract at a known address (with known ABI).\n\nThis is translated to a [MsgExecuteContract](https://github.com/CosmWasm/wasmd/blob/v0.14.0/x/wasm/internal/types/tx.proto#L68-L78). `sender` is automatically filled with the current contract's address.", + "type": "object", + "required": [ + "execute" + ], + "properties": { + "execute": { + "type": "object", + "required": [ + "contract_addr", + "funds", + "msg" + ], + "properties": { + "contract_addr": { + "type": "string" + }, + "funds": { + "type": "array", + "items": { + "$ref": "#/definitions/Coin" + } + }, + "msg": { + "description": "msg is the json-encoded ExecuteMsg struct (as raw Binary)", + "allOf": [ + { + "$ref": "#/definitions/Binary" + } + ] + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Instantiates a new contracts from previously uploaded Wasm code.\n\nThe contract address is non-predictable. But it is guaranteed that when emitting the same Instantiate message multiple times, multiple instances on different addresses will be generated. See also Instantiate2.\n\nThis is translated to a [MsgInstantiateContract](https://github.com/CosmWasm/wasmd/blob/v0.29.2/proto/cosmwasm/wasm/v1/tx.proto#L53-L71). `sender` is automatically filled with the current contract's address.", + "type": "object", + "required": [ + "instantiate" + ], + "properties": { + "instantiate": { + "type": "object", + "required": [ + "code_id", + "funds", + "label", + "msg" + ], + "properties": { + "admin": { + "type": [ + "string", + "null" + ] + }, + "code_id": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "funds": { + "type": "array", + "items": { + "$ref": "#/definitions/Coin" + } + }, + "label": { + "description": "A human-readbale label for the contract", + "type": "string" + }, + "msg": { + "description": "msg is the JSON-encoded InstantiateMsg struct (as raw Binary)", + "allOf": [ + { + "$ref": "#/definitions/Binary" + } + ] + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Migrates a given contracts to use new wasm code. Passes a MigrateMsg to allow us to customize behavior.\n\nOnly the contract admin (as defined in wasmd), if any, is able to make this call.\n\nThis is translated to a [MsgMigrateContract](https://github.com/CosmWasm/wasmd/blob/v0.14.0/x/wasm/internal/types/tx.proto#L86-L96). `sender` is automatically filled with the current contract's address.", + "type": "object", + "required": [ + "migrate" + ], + "properties": { + "migrate": { + "type": "object", + "required": [ + "contract_addr", + "msg", + "new_code_id" + ], + "properties": { + "contract_addr": { + "type": "string" + }, + "msg": { + "description": "msg is the json-encoded MigrateMsg struct that will be passed to the new code", + "allOf": [ + { + "$ref": "#/definitions/Binary" + } + ] + }, + "new_code_id": { + "description": "the code_id of the new logic to place in the given contract", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Sets a new admin (for migrate) on the given contract. Fails if this contract is not currently admin of the target contract.", + "type": "object", + "required": [ + "update_admin" + ], + "properties": { + "update_admin": { + "type": "object", + "required": [ + "admin", + "contract_addr" + ], + "properties": { + "admin": { + "type": "string" + }, + "contract_addr": { + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Clears the admin on the given contract, so no more migration possible. Fails if this contract is not currently admin of the target contract.", + "type": "object", + "required": [ + "clear_admin" + ], + "properties": { + "clear_admin": { + "type": "object", + "required": [ + "contract_addr" + ], + "properties": { + "contract_addr": { + "type": "string" + } + } + } + }, + "additionalProperties": false + } + ] + } + } + }, + "threshold": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ThresholdResponse", + "description": "This defines the different ways tallies can happen. Every contract should support a subset of these, ideally all.\n\nThe total_weight used for calculating success as well as the weights of each individual voter used in tallying should be snapshotted at the beginning of the block at which the proposal starts (this is likely the responsibility of a correct cw4 implementation).", + "oneOf": [ + { + "description": "Declares that a fixed weight of yes votes is needed to pass. It does not matter how many no votes are cast, or how many do not vote, as long as `weight` yes votes are cast.\n\nThis is the simplest format and usually suitable for small multisigs of trusted parties, like 3 of 5. (weight: 3, total_weight: 5)\n\nA proposal of this type can pass early as soon as the needed weight of yes votes has been cast.", + "type": "object", + "required": [ + "absolute_count" + ], + "properties": { + "absolute_count": { + "type": "object", + "required": [ + "total_weight", + "weight" + ], + "properties": { + "total_weight": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "weight": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Declares a percentage of the total weight that must cast Yes votes, in order for a proposal to pass. The passing weight is computed over the total weight minus the weight of the abstained votes.\n\nThis is useful for similar circumstances as `AbsoluteCount`, where we have a relatively small set of voters, and participation is required. It is understood that if the voting set (group) changes between different proposals that refer to the same group, each proposal will work with a different set of voter weights (the ones snapshotted at proposal creation), and the passing weight for each proposal will be computed based on the absolute percentage, times the total weights of the members at the time of each proposal creation.\n\nExample: we set `percentage` to 51%. Proposal 1 starts when there is a `total_weight` of 5. This will require 3 weight of Yes votes in order to pass. Later, the Proposal 2 starts but the `total_weight` of the group has increased to 9. That proposal will then automatically require 5 Yes of 9 to pass, rather than 3 yes of 9 as would be the case with `AbsoluteCount`.", + "type": "object", + "required": [ + "absolute_percentage" + ], + "properties": { + "absolute_percentage": { + "type": "object", + "required": [ + "percentage", + "total_weight" + ], + "properties": { + "percentage": { + "$ref": "#/definitions/Decimal" + }, + "total_weight": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "In addition to a `threshold`, declares a `quorum` of the total votes that must participate in the election in order for the vote to be considered at all. Within the votes that were cast, it requires `threshold` votes in favor. That is calculated by ignoring the Abstain votes (they count towards `quorum`, but do not influence `threshold`). That is, we calculate `Yes / (Yes + No + Veto)` and compare it with `threshold` to consider if the proposal was passed.\n\nIt is rather difficult for a proposal of this type to pass early. That can only happen if the required quorum has been already met, and there are already enough Yes votes for the proposal to pass.\n\n30% Yes votes, 10% No votes, and 20% Abstain would pass early if quorum <= 60% (who has cast votes) and if the threshold is <= 37.5% (the remaining 40% voting no => 30% yes + 50% no). Once the voting period has passed with no additional votes, that same proposal would be considered successful if quorum <= 60% and threshold <= 75% (percent in favor if we ignore abstain votes).\n\nThis type is more common in general elections, where participation is often expected to be low, and `AbsolutePercentage` would either be too high to pass anything, or allow low percentages to pass, independently of if there was high participation in the election or not.", + "type": "object", + "required": [ + "threshold_quorum" + ], + "properties": { + "threshold_quorum": { + "type": "object", + "required": [ + "quorum", + "threshold", + "total_weight" + ], + "properties": { + "quorum": { + "$ref": "#/definitions/Decimal" + }, + "threshold": { + "$ref": "#/definitions/Decimal" + }, + "total_weight": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + ], + "definitions": { + "Decimal": { + "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", + "type": "string" + } + } + }, + "vote": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "VoteResponse", + "type": "object", + "properties": { + "vote": { + "anyOf": [ + { + "$ref": "#/definitions/VoteInfo" + }, + { + "type": "null" + } + ] + } + }, + "additionalProperties": false, + "definitions": { + "Vote": { + "oneOf": [ + { + "description": "Marks support for the proposal.", + "type": "string", + "enum": [ + "yes" + ] + }, + { + "description": "Marks opposition to the proposal.", + "type": "string", + "enum": [ + "no" + ] + }, + { + "description": "Marks participation but does not count towards the ratio of support / opposed", + "type": "string", + "enum": [ + "abstain" + ] + }, + { + "description": "Veto is generally to be treated as a No vote. Some implementations may allow certain voters to be able to Veto, or them to be counted stronger than No in some way.", + "type": "string", + "enum": [ + "veto" + ] + } + ] + }, + "VoteInfo": { + "description": "Returns the vote (opinion as well as weight counted) as well as the address of the voter who submitted it", + "type": "object", + "required": [ + "proposal_id", + "vote", + "voter", + "weight" + ], + "properties": { + "proposal_id": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "vote": { + "$ref": "#/definitions/Vote" + }, + "voter": { + "type": "string" + }, + "weight": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + } + }, + "voter": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "VoterResponse", + "type": "object", + "properties": { + "weight": { + "type": [ + "integer", + "null" + ], + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + } +} diff --git a/contracts/multisig/cw3-flex-multisig/schema/raw/execute.json b/contracts/multisig/cw3-flex-multisig/schema/raw/execute.json new file mode 100644 index 0000000000..8c5eb3cdae --- /dev/null +++ b/contracts/multisig/cw3-flex-multisig/schema/raw/execute.json @@ -0,0 +1,976 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ExecuteMsg", + "oneOf": [ + { + "type": "object", + "required": [ + "propose" + ], + "properties": { + "propose": { + "type": "object", + "required": [ + "description", + "msgs", + "title" + ], + "properties": { + "description": { + "type": "string" + }, + "latest": { + "anyOf": [ + { + "$ref": "#/definitions/Expiration" + }, + { + "type": "null" + } + ] + }, + "msgs": { + "type": "array", + "items": { + "$ref": "#/definitions/CosmosMsg_for_Empty" + } + }, + "title": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "vote" + ], + "properties": { + "vote": { + "type": "object", + "required": [ + "proposal_id", + "vote" + ], + "properties": { + "proposal_id": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "vote": { + "$ref": "#/definitions/Vote" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "execute" + ], + "properties": { + "execute": { + "type": "object", + "required": [ + "proposal_id" + ], + "properties": { + "proposal_id": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "close" + ], + "properties": { + "close": { + "type": "object", + "required": [ + "proposal_id" + ], + "properties": { + "proposal_id": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Handles update hook messages from the group contract", + "type": "object", + "required": [ + "member_changed_hook" + ], + "properties": { + "member_changed_hook": { + "$ref": "#/definitions/MemberChangedHookMsg" + } + }, + "additionalProperties": false + } + ], + "definitions": { + "BankMsg": { + "description": "The message types of the bank module.\n\nSee https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/bank/v1beta1/tx.proto", + "oneOf": [ + { + "description": "Sends native tokens from the contract to the given address.\n\nThis is translated to a [MsgSend](https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/bank/v1beta1/tx.proto#L19-L28). `from_address` is automatically filled with the current contract's address.", + "type": "object", + "required": [ + "send" + ], + "properties": { + "send": { + "type": "object", + "required": [ + "amount", + "to_address" + ], + "properties": { + "amount": { + "type": "array", + "items": { + "$ref": "#/definitions/Coin" + } + }, + "to_address": { + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "This will burn the given coins from the contract's account. There is no Cosmos SDK message that performs this, but it can be done by calling the bank keeper. Important if a contract controls significant token supply that must be retired.", + "type": "object", + "required": [ + "burn" + ], + "properties": { + "burn": { + "type": "object", + "required": [ + "amount" + ], + "properties": { + "amount": { + "type": "array", + "items": { + "$ref": "#/definitions/Coin" + } + } + } + } + }, + "additionalProperties": false + } + ] + }, + "Binary": { + "description": "Binary is a wrapper around Vec to add base64 de/serialization with serde. It also adds some helper methods to help encode inline.\n\nThis is only needed as serde-json-{core,wasm} has a horrible encoding for Vec. See also .", + "type": "string" + }, + "Coin": { + "type": "object", + "required": [ + "amount", + "denom" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "denom": { + "type": "string" + } + } + }, + "CosmosMsg_for_Empty": { + "oneOf": [ + { + "type": "object", + "required": [ + "bank" + ], + "properties": { + "bank": { + "$ref": "#/definitions/BankMsg" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "custom" + ], + "properties": { + "custom": { + "$ref": "#/definitions/Empty" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "staking" + ], + "properties": { + "staking": { + "$ref": "#/definitions/StakingMsg" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "distribution" + ], + "properties": { + "distribution": { + "$ref": "#/definitions/DistributionMsg" + } + }, + "additionalProperties": false + }, + { + "description": "A Stargate message encoded the same way as a protobuf [Any](https://github.com/protocolbuffers/protobuf/blob/master/src/google/protobuf/any.proto). This is the same structure as messages in `TxBody` from [ADR-020](https://github.com/cosmos/cosmos-sdk/blob/master/docs/architecture/adr-020-protobuf-transaction-encoding.md)", + "type": "object", + "required": [ + "stargate" + ], + "properties": { + "stargate": { + "type": "object", + "required": [ + "type_url", + "value" + ], + "properties": { + "type_url": { + "type": "string" + }, + "value": { + "$ref": "#/definitions/Binary" + } + } + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "ibc" + ], + "properties": { + "ibc": { + "$ref": "#/definitions/IbcMsg" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "wasm" + ], + "properties": { + "wasm": { + "$ref": "#/definitions/WasmMsg" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "gov" + ], + "properties": { + "gov": { + "$ref": "#/definitions/GovMsg" + } + }, + "additionalProperties": false + } + ] + }, + "DistributionMsg": { + "description": "The message types of the distribution module.\n\nSee https://github.com/cosmos/cosmos-sdk/blob/v0.42.4/proto/cosmos/distribution/v1beta1/tx.proto", + "oneOf": [ + { + "description": "This is translated to a [MsgSetWithdrawAddress](https://github.com/cosmos/cosmos-sdk/blob/v0.42.4/proto/cosmos/distribution/v1beta1/tx.proto#L29-L37). `delegator_address` is automatically filled with the current contract's address.", + "type": "object", + "required": [ + "set_withdraw_address" + ], + "properties": { + "set_withdraw_address": { + "type": "object", + "required": [ + "address" + ], + "properties": { + "address": { + "description": "The `withdraw_address`", + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "This is translated to a [[MsgWithdrawDelegatorReward](https://github.com/cosmos/cosmos-sdk/blob/v0.42.4/proto/cosmos/distribution/v1beta1/tx.proto#L42-L50). `delegator_address` is automatically filled with the current contract's address.", + "type": "object", + "required": [ + "withdraw_delegator_reward" + ], + "properties": { + "withdraw_delegator_reward": { + "type": "object", + "required": [ + "validator" + ], + "properties": { + "validator": { + "description": "The `validator_address`", + "type": "string" + } + } + } + }, + "additionalProperties": false + } + ] + }, + "Empty": { + "description": "An empty struct that serves as a placeholder in different places, such as contracts that don't set a custom message.\n\nIt is designed to be expressable in correct JSON and JSON Schema but contains no meaningful data. Previously we used enums without cases, but those cannot represented as valid JSON Schema (https://github.com/CosmWasm/cosmwasm/issues/451)", + "type": "object" + }, + "Expiration": { + "description": "Expiration represents a point in time when some event happens. It can compare with a BlockInfo and will return is_expired() == true once the condition is hit (and for every block in the future)", + "oneOf": [ + { + "description": "AtHeight will expire when `env.block.height` >= height", + "type": "object", + "required": [ + "at_height" + ], + "properties": { + "at_height": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + }, + { + "description": "AtTime will expire when `env.block.time` >= time", + "type": "object", + "required": [ + "at_time" + ], + "properties": { + "at_time": { + "$ref": "#/definitions/Timestamp" + } + }, + "additionalProperties": false + }, + { + "description": "Never will never expire. Used to express the empty variant", + "type": "object", + "required": [ + "never" + ], + "properties": { + "never": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + } + ] + }, + "GovMsg": { + "description": "This message type allows the contract interact with the [x/gov] module in order to cast votes.\n\n[x/gov]: https://github.com/cosmos/cosmos-sdk/tree/v0.45.12/x/gov\n\n## Examples\n\nCast a simple vote:\n\n``` # use cosmwasm_std::{ # HexBinary, # Storage, Api, Querier, DepsMut, Deps, entry_point, Env, StdError, MessageInfo, # Response, QueryResponse, # }; # type ExecuteMsg = (); use cosmwasm_std::{GovMsg, VoteOption};\n\n#[entry_point] pub fn execute( deps: DepsMut, env: Env, info: MessageInfo, msg: ExecuteMsg, ) -> Result { // ... Ok(Response::new().add_message(GovMsg::Vote { proposal_id: 4, vote: VoteOption::Yes, })) } ```\n\nCast a weighted vote:\n\n``` # use cosmwasm_std::{ # HexBinary, # Storage, Api, Querier, DepsMut, Deps, entry_point, Env, StdError, MessageInfo, # Response, QueryResponse, # }; # type ExecuteMsg = (); # #[cfg(feature = \"cosmwasm_1_2\")] use cosmwasm_std::{Decimal, GovMsg, VoteOption, WeightedVoteOption};\n\n# #[cfg(feature = \"cosmwasm_1_2\")] #[entry_point] pub fn execute( deps: DepsMut, env: Env, info: MessageInfo, msg: ExecuteMsg, ) -> Result { // ... Ok(Response::new().add_message(GovMsg::VoteWeighted { proposal_id: 4, options: vec![ WeightedVoteOption { option: VoteOption::Yes, weight: Decimal::percent(65), }, WeightedVoteOption { option: VoteOption::Abstain, weight: Decimal::percent(35), }, ], })) } ```", + "oneOf": [ + { + "description": "This maps directly to [MsgVote](https://github.com/cosmos/cosmos-sdk/blob/v0.42.5/proto/cosmos/gov/v1beta1/tx.proto#L46-L56) in the Cosmos SDK with voter set to the contract address.", + "type": "object", + "required": [ + "vote" + ], + "properties": { + "vote": { + "type": "object", + "required": [ + "proposal_id", + "vote" + ], + "properties": { + "proposal_id": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "vote": { + "description": "The vote option.\n\nThis should be called \"option\" for consistency with Cosmos SDK. Sorry for that. See .", + "allOf": [ + { + "$ref": "#/definitions/VoteOption" + } + ] + } + } + } + }, + "additionalProperties": false + } + ] + }, + "IbcMsg": { + "description": "These are messages in the IBC lifecycle. Only usable by IBC-enabled contracts (contracts that directly speak the IBC protocol via 6 entry points)", + "oneOf": [ + { + "description": "Sends bank tokens owned by the contract to the given address on another chain. The channel must already be established between the ibctransfer module on this chain and a matching module on the remote chain. We cannot select the port_id, this is whatever the local chain has bound the ibctransfer module to.", + "type": "object", + "required": [ + "transfer" + ], + "properties": { + "transfer": { + "type": "object", + "required": [ + "amount", + "channel_id", + "timeout", + "to_address" + ], + "properties": { + "amount": { + "description": "packet data only supports one coin https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/ibc/applications/transfer/v1/transfer.proto#L11-L20", + "allOf": [ + { + "$ref": "#/definitions/Coin" + } + ] + }, + "channel_id": { + "description": "exisiting channel to send the tokens over", + "type": "string" + }, + "timeout": { + "description": "when packet times out, measured on remote chain", + "allOf": [ + { + "$ref": "#/definitions/IbcTimeout" + } + ] + }, + "to_address": { + "description": "address on the remote chain to receive these tokens", + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Sends an IBC packet with given data over the existing channel. Data should be encoded in a format defined by the channel version, and the module on the other side should know how to parse this.", + "type": "object", + "required": [ + "send_packet" + ], + "properties": { + "send_packet": { + "type": "object", + "required": [ + "channel_id", + "data", + "timeout" + ], + "properties": { + "channel_id": { + "type": "string" + }, + "data": { + "$ref": "#/definitions/Binary" + }, + "timeout": { + "description": "when packet times out, measured on remote chain", + "allOf": [ + { + "$ref": "#/definitions/IbcTimeout" + } + ] + } + } + } + }, + "additionalProperties": false + }, + { + "description": "This will close an existing channel that is owned by this contract. Port is auto-assigned to the contract's IBC port", + "type": "object", + "required": [ + "close_channel" + ], + "properties": { + "close_channel": { + "type": "object", + "required": [ + "channel_id" + ], + "properties": { + "channel_id": { + "type": "string" + } + } + } + }, + "additionalProperties": false + } + ] + }, + "IbcTimeout": { + "description": "In IBC each package must set at least one type of timeout: the timestamp or the block height. Using this rather complex enum instead of two timeout fields we ensure that at least one timeout is set.", + "type": "object", + "properties": { + "block": { + "anyOf": [ + { + "$ref": "#/definitions/IbcTimeoutBlock" + }, + { + "type": "null" + } + ] + }, + "timestamp": { + "anyOf": [ + { + "$ref": "#/definitions/Timestamp" + }, + { + "type": "null" + } + ] + } + } + }, + "IbcTimeoutBlock": { + "description": "IBCTimeoutHeight Height is a monotonically increasing data type that can be compared against another Height for the purposes of updating and freezing clients. Ordering is (revision_number, timeout_height)", + "type": "object", + "required": [ + "height", + "revision" + ], + "properties": { + "height": { + "description": "block height after which the packet times out. the height within the given revision", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "revision": { + "description": "the version that the client is currently on (eg. after reseting the chain this could increment 1 as height drops to 0)", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + } + }, + "MemberChangedHookMsg": { + "description": "MemberChangedHookMsg should be de/serialized under `MemberChangedHook()` variant in a ExecuteMsg. This contains a list of all diffs on the given transaction.", + "type": "object", + "required": [ + "diffs" + ], + "properties": { + "diffs": { + "type": "array", + "items": { + "$ref": "#/definitions/MemberDiff" + } + } + }, + "additionalProperties": false + }, + "MemberDiff": { + "description": "MemberDiff shows the old and new states for a given cw4 member They cannot both be None. old = None, new = Some -> Insert old = Some, new = Some -> Update old = Some, new = None -> Delete", + "type": "object", + "required": [ + "key" + ], + "properties": { + "key": { + "type": "string" + }, + "new": { + "type": [ + "integer", + "null" + ], + "format": "uint64", + "minimum": 0.0 + }, + "old": { + "type": [ + "integer", + "null" + ], + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + }, + "StakingMsg": { + "description": "The message types of the staking module.\n\nSee https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/staking/v1beta1/tx.proto", + "oneOf": [ + { + "description": "This is translated to a [MsgDelegate](https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/staking/v1beta1/tx.proto#L81-L90). `delegator_address` is automatically filled with the current contract's address.", + "type": "object", + "required": [ + "delegate" + ], + "properties": { + "delegate": { + "type": "object", + "required": [ + "amount", + "validator" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Coin" + }, + "validator": { + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "This is translated to a [MsgUndelegate](https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/staking/v1beta1/tx.proto#L112-L121). `delegator_address` is automatically filled with the current contract's address.", + "type": "object", + "required": [ + "undelegate" + ], + "properties": { + "undelegate": { + "type": "object", + "required": [ + "amount", + "validator" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Coin" + }, + "validator": { + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "This is translated to a [MsgBeginRedelegate](https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/staking/v1beta1/tx.proto#L95-L105). `delegator_address` is automatically filled with the current contract's address.", + "type": "object", + "required": [ + "redelegate" + ], + "properties": { + "redelegate": { + "type": "object", + "required": [ + "amount", + "dst_validator", + "src_validator" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Coin" + }, + "dst_validator": { + "type": "string" + }, + "src_validator": { + "type": "string" + } + } + } + }, + "additionalProperties": false + } + ] + }, + "Timestamp": { + "description": "A point in time in nanosecond precision.\n\nThis type can represent times from 1970-01-01T00:00:00Z to 2554-07-21T23:34:33Z.\n\n## Examples\n\n``` # use cosmwasm_std::Timestamp; let ts = Timestamp::from_nanos(1_000_000_202); assert_eq!(ts.nanos(), 1_000_000_202); assert_eq!(ts.seconds(), 1); assert_eq!(ts.subsec_nanos(), 202);\n\nlet ts = ts.plus_seconds(2); assert_eq!(ts.nanos(), 3_000_000_202); assert_eq!(ts.seconds(), 3); assert_eq!(ts.subsec_nanos(), 202); ```", + "allOf": [ + { + "$ref": "#/definitions/Uint64" + } + ] + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + }, + "Uint64": { + "description": "A thin wrapper around u64 that is using strings for JSON encoding/decoding, such that the full u64 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u64` to get the value out:\n\n``` # use cosmwasm_std::Uint64; let a = Uint64::from(42u64); assert_eq!(a.u64(), 42);\n\nlet b = Uint64::from(70u32); assert_eq!(b.u64(), 70); ```", + "type": "string" + }, + "Vote": { + "oneOf": [ + { + "description": "Marks support for the proposal.", + "type": "string", + "enum": [ + "yes" + ] + }, + { + "description": "Marks opposition to the proposal.", + "type": "string", + "enum": [ + "no" + ] + }, + { + "description": "Marks participation but does not count towards the ratio of support / opposed", + "type": "string", + "enum": [ + "abstain" + ] + }, + { + "description": "Veto is generally to be treated as a No vote. Some implementations may allow certain voters to be able to Veto, or them to be counted stronger than No in some way.", + "type": "string", + "enum": [ + "veto" + ] + } + ] + }, + "VoteOption": { + "type": "string", + "enum": [ + "yes", + "no", + "abstain", + "no_with_veto" + ] + }, + "WasmMsg": { + "description": "The message types of the wasm module.\n\nSee https://github.com/CosmWasm/wasmd/blob/v0.14.0/x/wasm/internal/types/tx.proto", + "oneOf": [ + { + "description": "Dispatches a call to another contract at a known address (with known ABI).\n\nThis is translated to a [MsgExecuteContract](https://github.com/CosmWasm/wasmd/blob/v0.14.0/x/wasm/internal/types/tx.proto#L68-L78). `sender` is automatically filled with the current contract's address.", + "type": "object", + "required": [ + "execute" + ], + "properties": { + "execute": { + "type": "object", + "required": [ + "contract_addr", + "funds", + "msg" + ], + "properties": { + "contract_addr": { + "type": "string" + }, + "funds": { + "type": "array", + "items": { + "$ref": "#/definitions/Coin" + } + }, + "msg": { + "description": "msg is the json-encoded ExecuteMsg struct (as raw Binary)", + "allOf": [ + { + "$ref": "#/definitions/Binary" + } + ] + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Instantiates a new contracts from previously uploaded Wasm code.\n\nThe contract address is non-predictable. But it is guaranteed that when emitting the same Instantiate message multiple times, multiple instances on different addresses will be generated. See also Instantiate2.\n\nThis is translated to a [MsgInstantiateContract](https://github.com/CosmWasm/wasmd/blob/v0.29.2/proto/cosmwasm/wasm/v1/tx.proto#L53-L71). `sender` is automatically filled with the current contract's address.", + "type": "object", + "required": [ + "instantiate" + ], + "properties": { + "instantiate": { + "type": "object", + "required": [ + "code_id", + "funds", + "label", + "msg" + ], + "properties": { + "admin": { + "type": [ + "string", + "null" + ] + }, + "code_id": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "funds": { + "type": "array", + "items": { + "$ref": "#/definitions/Coin" + } + }, + "label": { + "description": "A human-readbale label for the contract", + "type": "string" + }, + "msg": { + "description": "msg is the JSON-encoded InstantiateMsg struct (as raw Binary)", + "allOf": [ + { + "$ref": "#/definitions/Binary" + } + ] + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Migrates a given contracts to use new wasm code. Passes a MigrateMsg to allow us to customize behavior.\n\nOnly the contract admin (as defined in wasmd), if any, is able to make this call.\n\nThis is translated to a [MsgMigrateContract](https://github.com/CosmWasm/wasmd/blob/v0.14.0/x/wasm/internal/types/tx.proto#L86-L96). `sender` is automatically filled with the current contract's address.", + "type": "object", + "required": [ + "migrate" + ], + "properties": { + "migrate": { + "type": "object", + "required": [ + "contract_addr", + "msg", + "new_code_id" + ], + "properties": { + "contract_addr": { + "type": "string" + }, + "msg": { + "description": "msg is the json-encoded MigrateMsg struct that will be passed to the new code", + "allOf": [ + { + "$ref": "#/definitions/Binary" + } + ] + }, + "new_code_id": { + "description": "the code_id of the new logic to place in the given contract", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Sets a new admin (for migrate) on the given contract. Fails if this contract is not currently admin of the target contract.", + "type": "object", + "required": [ + "update_admin" + ], + "properties": { + "update_admin": { + "type": "object", + "required": [ + "admin", + "contract_addr" + ], + "properties": { + "admin": { + "type": "string" + }, + "contract_addr": { + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Clears the admin on the given contract, so no more migration possible. Fails if this contract is not currently admin of the target contract.", + "type": "object", + "required": [ + "clear_admin" + ], + "properties": { + "clear_admin": { + "type": "object", + "required": [ + "contract_addr" + ], + "properties": { + "contract_addr": { + "type": "string" + } + } + } + }, + "additionalProperties": false + } + ] + } + } +} diff --git a/contracts/multisig/cw3-flex-multisig/schema/raw/instantiate.json b/contracts/multisig/cw3-flex-multisig/schema/raw/instantiate.json new file mode 100644 index 0000000000..90fa8736e6 --- /dev/null +++ b/contracts/multisig/cw3-flex-multisig/schema/raw/instantiate.json @@ -0,0 +1,261 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "InstantiateMsg", + "type": "object", + "required": [ + "coconut_bandwidth_contract_address", + "coconut_dkg_contract_address", + "group_addr", + "max_voting_period", + "threshold" + ], + "properties": { + "coconut_bandwidth_contract_address": { + "type": "string" + }, + "coconut_dkg_contract_address": { + "type": "string" + }, + "executor": { + "anyOf": [ + { + "$ref": "#/definitions/Executor" + }, + { + "type": "null" + } + ] + }, + "group_addr": { + "type": "string" + }, + "max_voting_period": { + "$ref": "#/definitions/Duration" + }, + "proposal_deposit": { + "description": "The cost of creating a proposal (if any).", + "anyOf": [ + { + "$ref": "#/definitions/UncheckedDepositInfo" + }, + { + "type": "null" + } + ] + }, + "threshold": { + "$ref": "#/definitions/Threshold" + } + }, + "additionalProperties": false, + "definitions": { + "Addr": { + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + }, + "Decimal": { + "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", + "type": "string" + }, + "Duration": { + "description": "Duration is a delta of time. You can add it to a BlockInfo or Expiration to move that further in the future. Note that an height-based Duration and a time-based Expiration cannot be combined", + "oneOf": [ + { + "type": "object", + "required": [ + "height" + ], + "properties": { + "height": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + }, + { + "description": "Time in seconds", + "type": "object", + "required": [ + "time" + ], + "properties": { + "time": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + ] + }, + "Executor": { + "description": "Defines who is able to execute proposals once passed", + "oneOf": [ + { + "description": "Any member of the voting group, even with 0 points", + "type": "string", + "enum": [ + "member" + ] + }, + { + "description": "Only the given address", + "type": "object", + "required": [ + "only" + ], + "properties": { + "only": { + "$ref": "#/definitions/Addr" + } + }, + "additionalProperties": false + } + ] + }, + "Threshold": { + "description": "This defines the different ways tallies can happen.\n\nThe total_weight used for calculating success as well as the weights of each individual voter used in tallying should be snapshotted at the beginning of the block at which the proposal starts (this is likely the responsibility of a correct cw4 implementation). See also `ThresholdResponse` in the cw3 spec.", + "oneOf": [ + { + "description": "Declares that a fixed weight of Yes votes is needed to pass. See `ThresholdResponse.AbsoluteCount` in the cw3 spec for details.", + "type": "object", + "required": [ + "absolute_count" + ], + "properties": { + "absolute_count": { + "type": "object", + "required": [ + "weight" + ], + "properties": { + "weight": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Declares a percentage of the total weight that must cast Yes votes in order for a proposal to pass. See `ThresholdResponse.AbsolutePercentage` in the cw3 spec for details.", + "type": "object", + "required": [ + "absolute_percentage" + ], + "properties": { + "absolute_percentage": { + "type": "object", + "required": [ + "percentage" + ], + "properties": { + "percentage": { + "$ref": "#/definitions/Decimal" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Declares a `quorum` of the total votes that must participate in the election in order for the vote to be considered at all. See `ThresholdResponse.ThresholdQuorum` in the cw3 spec for details.", + "type": "object", + "required": [ + "threshold_quorum" + ], + "properties": { + "threshold_quorum": { + "type": "object", + "required": [ + "quorum", + "threshold" + ], + "properties": { + "quorum": { + "$ref": "#/definitions/Decimal" + }, + "threshold": { + "$ref": "#/definitions/Decimal" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + ] + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + }, + "UncheckedDenom": { + "oneOf": [ + { + "type": "object", + "required": [ + "native" + ], + "properties": { + "native": { + "type": "string" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "cw20" + ], + "properties": { + "cw20": { + "type": "string" + } + }, + "additionalProperties": false + } + ] + }, + "UncheckedDepositInfo": { + "description": "Information about the deposit required to create a proposal. For use in messages. To validate, transform into `DepositInfo` via `into_checked()`.", + "type": "object", + "required": [ + "amount", + "denom", + "refund_failed_proposals" + ], + "properties": { + "amount": { + "description": "The number tokens required for payment.", + "allOf": [ + { + "$ref": "#/definitions/Uint128" + } + ] + }, + "denom": { + "description": "The denom of the deposit payment.", + "allOf": [ + { + "$ref": "#/definitions/UncheckedDenom" + } + ] + }, + "refund_failed_proposals": { + "description": "Should failed proposals have their deposits refunded?", + "type": "boolean" + } + }, + "additionalProperties": false + } + } +} diff --git a/contracts/multisig/cw3-flex-multisig/schema/raw/migrate.json b/contracts/multisig/cw3-flex-multisig/schema/raw/migrate.json new file mode 100644 index 0000000000..b983a8e05c --- /dev/null +++ b/contracts/multisig/cw3-flex-multisig/schema/raw/migrate.json @@ -0,0 +1,18 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "MigrateMsg", + "type": "object", + "required": [ + "coconut_bandwidth_address", + "coconut_dkg_address" + ], + "properties": { + "coconut_bandwidth_address": { + "type": "string" + }, + "coconut_dkg_address": { + "type": "string" + } + }, + "additionalProperties": false +} diff --git a/contracts/multisig/cw3-flex-multisig/schema/raw/query.json b/contracts/multisig/cw3-flex-multisig/schema/raw/query.json new file mode 100644 index 0000000000..570a727999 --- /dev/null +++ b/contracts/multisig/cw3-flex-multisig/schema/raw/query.json @@ -0,0 +1,232 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "QueryMsg", + "oneOf": [ + { + "type": "object", + "required": [ + "threshold" + ], + "properties": { + "threshold": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "proposal" + ], + "properties": { + "proposal": { + "type": "object", + "required": [ + "proposal_id" + ], + "properties": { + "proposal_id": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "list_proposals" + ], + "properties": { + "list_proposals": { + "type": "object", + "properties": { + "limit": { + "type": [ + "integer", + "null" + ], + "format": "uint32", + "minimum": 0.0 + }, + "start_after": { + "type": [ + "integer", + "null" + ], + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "reverse_proposals" + ], + "properties": { + "reverse_proposals": { + "type": "object", + "properties": { + "limit": { + "type": [ + "integer", + "null" + ], + "format": "uint32", + "minimum": 0.0 + }, + "start_before": { + "type": [ + "integer", + "null" + ], + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "vote" + ], + "properties": { + "vote": { + "type": "object", + "required": [ + "proposal_id", + "voter" + ], + "properties": { + "proposal_id": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "voter": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "list_votes" + ], + "properties": { + "list_votes": { + "type": "object", + "required": [ + "proposal_id" + ], + "properties": { + "limit": { + "type": [ + "integer", + "null" + ], + "format": "uint32", + "minimum": 0.0 + }, + "proposal_id": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "start_after": { + "type": [ + "string", + "null" + ] + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "voter" + ], + "properties": { + "voter": { + "type": "object", + "required": [ + "address" + ], + "properties": { + "address": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "list_voters" + ], + "properties": { + "list_voters": { + "type": "object", + "properties": { + "limit": { + "type": [ + "integer", + "null" + ], + "format": "uint32", + "minimum": 0.0 + }, + "start_after": { + "type": [ + "string", + "null" + ] + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Gets the current configuration.", + "type": "object", + "required": [ + "config" + ], + "properties": { + "config": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + } + ] +} diff --git a/contracts/multisig/cw3-flex-multisig/schema/raw/response_to_config.json b/contracts/multisig/cw3-flex-multisig/schema/raw/response_to_config.json new file mode 100644 index 0000000000..b5388ddae1 --- /dev/null +++ b/contracts/multisig/cw3-flex-multisig/schema/raw/response_to_config.json @@ -0,0 +1,269 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Config", + "type": "object", + "required": [ + "coconut_bandwidth_addr", + "coconut_dkg_addr", + "group_addr", + "max_voting_period", + "threshold" + ], + "properties": { + "coconut_bandwidth_addr": { + "$ref": "#/definitions/Addr" + }, + "coconut_dkg_addr": { + "$ref": "#/definitions/Addr" + }, + "executor": { + "anyOf": [ + { + "$ref": "#/definitions/Executor" + }, + { + "type": "null" + } + ] + }, + "group_addr": { + "$ref": "#/definitions/Cw4Contract" + }, + "max_voting_period": { + "$ref": "#/definitions/Duration" + }, + "proposal_deposit": { + "description": "The price, if any, of creating a new proposal.", + "anyOf": [ + { + "$ref": "#/definitions/DepositInfo" + }, + { + "type": "null" + } + ] + }, + "threshold": { + "$ref": "#/definitions/Threshold" + } + }, + "additionalProperties": false, + "definitions": { + "Addr": { + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + }, + "Cw4Contract": { + "description": "Cw4Contract is a wrapper around Addr that provides a lot of helpers for working with cw4 contracts\n\nIf you wish to persist this, convert to Cw4CanonicalContract via .canonical()", + "allOf": [ + { + "$ref": "#/definitions/Addr" + } + ] + }, + "Decimal": { + "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", + "type": "string" + }, + "Denom": { + "oneOf": [ + { + "type": "object", + "required": [ + "native" + ], + "properties": { + "native": { + "type": "string" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "cw20" + ], + "properties": { + "cw20": { + "$ref": "#/definitions/Addr" + } + }, + "additionalProperties": false + } + ] + }, + "DepositInfo": { + "description": "Information about the deposit required to create a proposal.", + "type": "object", + "required": [ + "amount", + "denom", + "refund_failed_proposals" + ], + "properties": { + "amount": { + "description": "The number tokens required for payment.", + "allOf": [ + { + "$ref": "#/definitions/Uint128" + } + ] + }, + "denom": { + "description": "The denom of the deposit payment.", + "allOf": [ + { + "$ref": "#/definitions/Denom" + } + ] + }, + "refund_failed_proposals": { + "description": "Should failed proposals have their deposits refunded?", + "type": "boolean" + } + }, + "additionalProperties": false + }, + "Duration": { + "description": "Duration is a delta of time. You can add it to a BlockInfo or Expiration to move that further in the future. Note that an height-based Duration and a time-based Expiration cannot be combined", + "oneOf": [ + { + "type": "object", + "required": [ + "height" + ], + "properties": { + "height": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + }, + { + "description": "Time in seconds", + "type": "object", + "required": [ + "time" + ], + "properties": { + "time": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + ] + }, + "Executor": { + "description": "Defines who is able to execute proposals once passed", + "oneOf": [ + { + "description": "Any member of the voting group, even with 0 points", + "type": "string", + "enum": [ + "member" + ] + }, + { + "description": "Only the given address", + "type": "object", + "required": [ + "only" + ], + "properties": { + "only": { + "$ref": "#/definitions/Addr" + } + }, + "additionalProperties": false + } + ] + }, + "Threshold": { + "description": "This defines the different ways tallies can happen.\n\nThe total_weight used for calculating success as well as the weights of each individual voter used in tallying should be snapshotted at the beginning of the block at which the proposal starts (this is likely the responsibility of a correct cw4 implementation). See also `ThresholdResponse` in the cw3 spec.", + "oneOf": [ + { + "description": "Declares that a fixed weight of Yes votes is needed to pass. See `ThresholdResponse.AbsoluteCount` in the cw3 spec for details.", + "type": "object", + "required": [ + "absolute_count" + ], + "properties": { + "absolute_count": { + "type": "object", + "required": [ + "weight" + ], + "properties": { + "weight": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Declares a percentage of the total weight that must cast Yes votes in order for a proposal to pass. See `ThresholdResponse.AbsolutePercentage` in the cw3 spec for details.", + "type": "object", + "required": [ + "absolute_percentage" + ], + "properties": { + "absolute_percentage": { + "type": "object", + "required": [ + "percentage" + ], + "properties": { + "percentage": { + "$ref": "#/definitions/Decimal" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Declares a `quorum` of the total votes that must participate in the election in order for the vote to be considered at all. See `ThresholdResponse.ThresholdQuorum` in the cw3 spec for details.", + "type": "object", + "required": [ + "threshold_quorum" + ], + "properties": { + "threshold_quorum": { + "type": "object", + "required": [ + "quorum", + "threshold" + ], + "properties": { + "quorum": { + "$ref": "#/definitions/Decimal" + }, + "threshold": { + "$ref": "#/definitions/Decimal" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + ] + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } +} diff --git a/contracts/multisig/cw3-flex-multisig/schema/raw/response_to_list_proposals.json b/contracts/multisig/cw3-flex-multisig/schema/raw/response_to_list_proposals.json new file mode 100644 index 0000000000..0012bec80b --- /dev/null +++ b/contracts/multisig/cw3-flex-multisig/schema/raw/response_to_list_proposals.json @@ -0,0 +1,1045 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ProposalListResponse_for_Empty", + "type": "object", + "required": [ + "proposals" + ], + "properties": { + "proposals": { + "type": "array", + "items": { + "$ref": "#/definitions/ProposalResponse_for_Empty" + } + } + }, + "additionalProperties": false, + "definitions": { + "Addr": { + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + }, + "BankMsg": { + "description": "The message types of the bank module.\n\nSee https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/bank/v1beta1/tx.proto", + "oneOf": [ + { + "description": "Sends native tokens from the contract to the given address.\n\nThis is translated to a [MsgSend](https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/bank/v1beta1/tx.proto#L19-L28). `from_address` is automatically filled with the current contract's address.", + "type": "object", + "required": [ + "send" + ], + "properties": { + "send": { + "type": "object", + "required": [ + "amount", + "to_address" + ], + "properties": { + "amount": { + "type": "array", + "items": { + "$ref": "#/definitions/Coin" + } + }, + "to_address": { + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "This will burn the given coins from the contract's account. There is no Cosmos SDK message that performs this, but it can be done by calling the bank keeper. Important if a contract controls significant token supply that must be retired.", + "type": "object", + "required": [ + "burn" + ], + "properties": { + "burn": { + "type": "object", + "required": [ + "amount" + ], + "properties": { + "amount": { + "type": "array", + "items": { + "$ref": "#/definitions/Coin" + } + } + } + } + }, + "additionalProperties": false + } + ] + }, + "Binary": { + "description": "Binary is a wrapper around Vec to add base64 de/serialization with serde. It also adds some helper methods to help encode inline.\n\nThis is only needed as serde-json-{core,wasm} has a horrible encoding for Vec. See also .", + "type": "string" + }, + "Coin": { + "type": "object", + "required": [ + "amount", + "denom" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "denom": { + "type": "string" + } + } + }, + "CosmosMsg_for_Empty": { + "oneOf": [ + { + "type": "object", + "required": [ + "bank" + ], + "properties": { + "bank": { + "$ref": "#/definitions/BankMsg" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "custom" + ], + "properties": { + "custom": { + "$ref": "#/definitions/Empty" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "staking" + ], + "properties": { + "staking": { + "$ref": "#/definitions/StakingMsg" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "distribution" + ], + "properties": { + "distribution": { + "$ref": "#/definitions/DistributionMsg" + } + }, + "additionalProperties": false + }, + { + "description": "A Stargate message encoded the same way as a protobuf [Any](https://github.com/protocolbuffers/protobuf/blob/master/src/google/protobuf/any.proto). This is the same structure as messages in `TxBody` from [ADR-020](https://github.com/cosmos/cosmos-sdk/blob/master/docs/architecture/adr-020-protobuf-transaction-encoding.md)", + "type": "object", + "required": [ + "stargate" + ], + "properties": { + "stargate": { + "type": "object", + "required": [ + "type_url", + "value" + ], + "properties": { + "type_url": { + "type": "string" + }, + "value": { + "$ref": "#/definitions/Binary" + } + } + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "ibc" + ], + "properties": { + "ibc": { + "$ref": "#/definitions/IbcMsg" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "wasm" + ], + "properties": { + "wasm": { + "$ref": "#/definitions/WasmMsg" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "gov" + ], + "properties": { + "gov": { + "$ref": "#/definitions/GovMsg" + } + }, + "additionalProperties": false + } + ] + }, + "Decimal": { + "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", + "type": "string" + }, + "Denom": { + "oneOf": [ + { + "type": "object", + "required": [ + "native" + ], + "properties": { + "native": { + "type": "string" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "cw20" + ], + "properties": { + "cw20": { + "$ref": "#/definitions/Addr" + } + }, + "additionalProperties": false + } + ] + }, + "DepositInfo": { + "description": "Information about the deposit required to create a proposal.", + "type": "object", + "required": [ + "amount", + "denom", + "refund_failed_proposals" + ], + "properties": { + "amount": { + "description": "The number tokens required for payment.", + "allOf": [ + { + "$ref": "#/definitions/Uint128" + } + ] + }, + "denom": { + "description": "The denom of the deposit payment.", + "allOf": [ + { + "$ref": "#/definitions/Denom" + } + ] + }, + "refund_failed_proposals": { + "description": "Should failed proposals have their deposits refunded?", + "type": "boolean" + } + }, + "additionalProperties": false + }, + "DistributionMsg": { + "description": "The message types of the distribution module.\n\nSee https://github.com/cosmos/cosmos-sdk/blob/v0.42.4/proto/cosmos/distribution/v1beta1/tx.proto", + "oneOf": [ + { + "description": "This is translated to a [MsgSetWithdrawAddress](https://github.com/cosmos/cosmos-sdk/blob/v0.42.4/proto/cosmos/distribution/v1beta1/tx.proto#L29-L37). `delegator_address` is automatically filled with the current contract's address.", + "type": "object", + "required": [ + "set_withdraw_address" + ], + "properties": { + "set_withdraw_address": { + "type": "object", + "required": [ + "address" + ], + "properties": { + "address": { + "description": "The `withdraw_address`", + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "This is translated to a [[MsgWithdrawDelegatorReward](https://github.com/cosmos/cosmos-sdk/blob/v0.42.4/proto/cosmos/distribution/v1beta1/tx.proto#L42-L50). `delegator_address` is automatically filled with the current contract's address.", + "type": "object", + "required": [ + "withdraw_delegator_reward" + ], + "properties": { + "withdraw_delegator_reward": { + "type": "object", + "required": [ + "validator" + ], + "properties": { + "validator": { + "description": "The `validator_address`", + "type": "string" + } + } + } + }, + "additionalProperties": false + } + ] + }, + "Empty": { + "description": "An empty struct that serves as a placeholder in different places, such as contracts that don't set a custom message.\n\nIt is designed to be expressable in correct JSON and JSON Schema but contains no meaningful data. Previously we used enums without cases, but those cannot represented as valid JSON Schema (https://github.com/CosmWasm/cosmwasm/issues/451)", + "type": "object" + }, + "Expiration": { + "description": "Expiration represents a point in time when some event happens. It can compare with a BlockInfo and will return is_expired() == true once the condition is hit (and for every block in the future)", + "oneOf": [ + { + "description": "AtHeight will expire when `env.block.height` >= height", + "type": "object", + "required": [ + "at_height" + ], + "properties": { + "at_height": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + }, + { + "description": "AtTime will expire when `env.block.time` >= time", + "type": "object", + "required": [ + "at_time" + ], + "properties": { + "at_time": { + "$ref": "#/definitions/Timestamp" + } + }, + "additionalProperties": false + }, + { + "description": "Never will never expire. Used to express the empty variant", + "type": "object", + "required": [ + "never" + ], + "properties": { + "never": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + } + ] + }, + "GovMsg": { + "description": "This message type allows the contract interact with the [x/gov] module in order to cast votes.\n\n[x/gov]: https://github.com/cosmos/cosmos-sdk/tree/v0.45.12/x/gov\n\n## Examples\n\nCast a simple vote:\n\n``` # use cosmwasm_std::{ # HexBinary, # Storage, Api, Querier, DepsMut, Deps, entry_point, Env, StdError, MessageInfo, # Response, QueryResponse, # }; # type ExecuteMsg = (); use cosmwasm_std::{GovMsg, VoteOption};\n\n#[entry_point] pub fn execute( deps: DepsMut, env: Env, info: MessageInfo, msg: ExecuteMsg, ) -> Result { // ... Ok(Response::new().add_message(GovMsg::Vote { proposal_id: 4, vote: VoteOption::Yes, })) } ```\n\nCast a weighted vote:\n\n``` # use cosmwasm_std::{ # HexBinary, # Storage, Api, Querier, DepsMut, Deps, entry_point, Env, StdError, MessageInfo, # Response, QueryResponse, # }; # type ExecuteMsg = (); # #[cfg(feature = \"cosmwasm_1_2\")] use cosmwasm_std::{Decimal, GovMsg, VoteOption, WeightedVoteOption};\n\n# #[cfg(feature = \"cosmwasm_1_2\")] #[entry_point] pub fn execute( deps: DepsMut, env: Env, info: MessageInfo, msg: ExecuteMsg, ) -> Result { // ... Ok(Response::new().add_message(GovMsg::VoteWeighted { proposal_id: 4, options: vec![ WeightedVoteOption { option: VoteOption::Yes, weight: Decimal::percent(65), }, WeightedVoteOption { option: VoteOption::Abstain, weight: Decimal::percent(35), }, ], })) } ```", + "oneOf": [ + { + "description": "This maps directly to [MsgVote](https://github.com/cosmos/cosmos-sdk/blob/v0.42.5/proto/cosmos/gov/v1beta1/tx.proto#L46-L56) in the Cosmos SDK with voter set to the contract address.", + "type": "object", + "required": [ + "vote" + ], + "properties": { + "vote": { + "type": "object", + "required": [ + "proposal_id", + "vote" + ], + "properties": { + "proposal_id": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "vote": { + "description": "The vote option.\n\nThis should be called \"option\" for consistency with Cosmos SDK. Sorry for that. See .", + "allOf": [ + { + "$ref": "#/definitions/VoteOption" + } + ] + } + } + } + }, + "additionalProperties": false + } + ] + }, + "IbcMsg": { + "description": "These are messages in the IBC lifecycle. Only usable by IBC-enabled contracts (contracts that directly speak the IBC protocol via 6 entry points)", + "oneOf": [ + { + "description": "Sends bank tokens owned by the contract to the given address on another chain. The channel must already be established between the ibctransfer module on this chain and a matching module on the remote chain. We cannot select the port_id, this is whatever the local chain has bound the ibctransfer module to.", + "type": "object", + "required": [ + "transfer" + ], + "properties": { + "transfer": { + "type": "object", + "required": [ + "amount", + "channel_id", + "timeout", + "to_address" + ], + "properties": { + "amount": { + "description": "packet data only supports one coin https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/ibc/applications/transfer/v1/transfer.proto#L11-L20", + "allOf": [ + { + "$ref": "#/definitions/Coin" + } + ] + }, + "channel_id": { + "description": "exisiting channel to send the tokens over", + "type": "string" + }, + "timeout": { + "description": "when packet times out, measured on remote chain", + "allOf": [ + { + "$ref": "#/definitions/IbcTimeout" + } + ] + }, + "to_address": { + "description": "address on the remote chain to receive these tokens", + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Sends an IBC packet with given data over the existing channel. Data should be encoded in a format defined by the channel version, and the module on the other side should know how to parse this.", + "type": "object", + "required": [ + "send_packet" + ], + "properties": { + "send_packet": { + "type": "object", + "required": [ + "channel_id", + "data", + "timeout" + ], + "properties": { + "channel_id": { + "type": "string" + }, + "data": { + "$ref": "#/definitions/Binary" + }, + "timeout": { + "description": "when packet times out, measured on remote chain", + "allOf": [ + { + "$ref": "#/definitions/IbcTimeout" + } + ] + } + } + } + }, + "additionalProperties": false + }, + { + "description": "This will close an existing channel that is owned by this contract. Port is auto-assigned to the contract's IBC port", + "type": "object", + "required": [ + "close_channel" + ], + "properties": { + "close_channel": { + "type": "object", + "required": [ + "channel_id" + ], + "properties": { + "channel_id": { + "type": "string" + } + } + } + }, + "additionalProperties": false + } + ] + }, + "IbcTimeout": { + "description": "In IBC each package must set at least one type of timeout: the timestamp or the block height. Using this rather complex enum instead of two timeout fields we ensure that at least one timeout is set.", + "type": "object", + "properties": { + "block": { + "anyOf": [ + { + "$ref": "#/definitions/IbcTimeoutBlock" + }, + { + "type": "null" + } + ] + }, + "timestamp": { + "anyOf": [ + { + "$ref": "#/definitions/Timestamp" + }, + { + "type": "null" + } + ] + } + } + }, + "IbcTimeoutBlock": { + "description": "IBCTimeoutHeight Height is a monotonically increasing data type that can be compared against another Height for the purposes of updating and freezing clients. Ordering is (revision_number, timeout_height)", + "type": "object", + "required": [ + "height", + "revision" + ], + "properties": { + "height": { + "description": "block height after which the packet times out. the height within the given revision", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "revision": { + "description": "the version that the client is currently on (eg. after reseting the chain this could increment 1 as height drops to 0)", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + } + }, + "ProposalResponse_for_Empty": { + "description": "Note, if you are storing custom messages in the proposal, the querier needs to know what possible custom message types those are in order to parse the response", + "type": "object", + "required": [ + "description", + "expires", + "id", + "msgs", + "proposer", + "status", + "threshold", + "title" + ], + "properties": { + "deposit": { + "anyOf": [ + { + "$ref": "#/definitions/DepositInfo" + }, + { + "type": "null" + } + ] + }, + "description": { + "type": "string" + }, + "expires": { + "$ref": "#/definitions/Expiration" + }, + "id": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "msgs": { + "type": "array", + "items": { + "$ref": "#/definitions/CosmosMsg_for_Empty" + } + }, + "proposer": { + "$ref": "#/definitions/Addr" + }, + "status": { + "$ref": "#/definitions/Status" + }, + "threshold": { + "description": "This is the threshold that is applied to this proposal. Both the rules of the voting contract, as well as the total_weight of the voting group may have changed since this time. That means that the generic `Threshold{}` query does not provide valid information for existing proposals.", + "allOf": [ + { + "$ref": "#/definitions/ThresholdResponse" + } + ] + }, + "title": { + "type": "string" + } + }, + "additionalProperties": false + }, + "StakingMsg": { + "description": "The message types of the staking module.\n\nSee https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/staking/v1beta1/tx.proto", + "oneOf": [ + { + "description": "This is translated to a [MsgDelegate](https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/staking/v1beta1/tx.proto#L81-L90). `delegator_address` is automatically filled with the current contract's address.", + "type": "object", + "required": [ + "delegate" + ], + "properties": { + "delegate": { + "type": "object", + "required": [ + "amount", + "validator" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Coin" + }, + "validator": { + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "This is translated to a [MsgUndelegate](https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/staking/v1beta1/tx.proto#L112-L121). `delegator_address` is automatically filled with the current contract's address.", + "type": "object", + "required": [ + "undelegate" + ], + "properties": { + "undelegate": { + "type": "object", + "required": [ + "amount", + "validator" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Coin" + }, + "validator": { + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "This is translated to a [MsgBeginRedelegate](https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/staking/v1beta1/tx.proto#L95-L105). `delegator_address` is automatically filled with the current contract's address.", + "type": "object", + "required": [ + "redelegate" + ], + "properties": { + "redelegate": { + "type": "object", + "required": [ + "amount", + "dst_validator", + "src_validator" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Coin" + }, + "dst_validator": { + "type": "string" + }, + "src_validator": { + "type": "string" + } + } + } + }, + "additionalProperties": false + } + ] + }, + "Status": { + "oneOf": [ + { + "description": "proposal was created, but voting has not yet begun for whatever reason", + "type": "string", + "enum": [ + "pending" + ] + }, + { + "description": "you can vote on this", + "type": "string", + "enum": [ + "open" + ] + }, + { + "description": "voting is over and it did not pass", + "type": "string", + "enum": [ + "rejected" + ] + }, + { + "description": "voting is over and it did pass, but has not yet executed", + "type": "string", + "enum": [ + "passed" + ] + }, + { + "description": "voting is over it passed, and the proposal was executed", + "type": "string", + "enum": [ + "executed" + ] + } + ] + }, + "ThresholdResponse": { + "description": "This defines the different ways tallies can happen. Every contract should support a subset of these, ideally all.\n\nThe total_weight used for calculating success as well as the weights of each individual voter used in tallying should be snapshotted at the beginning of the block at which the proposal starts (this is likely the responsibility of a correct cw4 implementation).", + "oneOf": [ + { + "description": "Declares that a fixed weight of yes votes is needed to pass. It does not matter how many no votes are cast, or how many do not vote, as long as `weight` yes votes are cast.\n\nThis is the simplest format and usually suitable for small multisigs of trusted parties, like 3 of 5. (weight: 3, total_weight: 5)\n\nA proposal of this type can pass early as soon as the needed weight of yes votes has been cast.", + "type": "object", + "required": [ + "absolute_count" + ], + "properties": { + "absolute_count": { + "type": "object", + "required": [ + "total_weight", + "weight" + ], + "properties": { + "total_weight": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "weight": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Declares a percentage of the total weight that must cast Yes votes, in order for a proposal to pass. The passing weight is computed over the total weight minus the weight of the abstained votes.\n\nThis is useful for similar circumstances as `AbsoluteCount`, where we have a relatively small set of voters, and participation is required. It is understood that if the voting set (group) changes between different proposals that refer to the same group, each proposal will work with a different set of voter weights (the ones snapshotted at proposal creation), and the passing weight for each proposal will be computed based on the absolute percentage, times the total weights of the members at the time of each proposal creation.\n\nExample: we set `percentage` to 51%. Proposal 1 starts when there is a `total_weight` of 5. This will require 3 weight of Yes votes in order to pass. Later, the Proposal 2 starts but the `total_weight` of the group has increased to 9. That proposal will then automatically require 5 Yes of 9 to pass, rather than 3 yes of 9 as would be the case with `AbsoluteCount`.", + "type": "object", + "required": [ + "absolute_percentage" + ], + "properties": { + "absolute_percentage": { + "type": "object", + "required": [ + "percentage", + "total_weight" + ], + "properties": { + "percentage": { + "$ref": "#/definitions/Decimal" + }, + "total_weight": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "In addition to a `threshold`, declares a `quorum` of the total votes that must participate in the election in order for the vote to be considered at all. Within the votes that were cast, it requires `threshold` votes in favor. That is calculated by ignoring the Abstain votes (they count towards `quorum`, but do not influence `threshold`). That is, we calculate `Yes / (Yes + No + Veto)` and compare it with `threshold` to consider if the proposal was passed.\n\nIt is rather difficult for a proposal of this type to pass early. That can only happen if the required quorum has been already met, and there are already enough Yes votes for the proposal to pass.\n\n30% Yes votes, 10% No votes, and 20% Abstain would pass early if quorum <= 60% (who has cast votes) and if the threshold is <= 37.5% (the remaining 40% voting no => 30% yes + 50% no). Once the voting period has passed with no additional votes, that same proposal would be considered successful if quorum <= 60% and threshold <= 75% (percent in favor if we ignore abstain votes).\n\nThis type is more common in general elections, where participation is often expected to be low, and `AbsolutePercentage` would either be too high to pass anything, or allow low percentages to pass, independently of if there was high participation in the election or not.", + "type": "object", + "required": [ + "threshold_quorum" + ], + "properties": { + "threshold_quorum": { + "type": "object", + "required": [ + "quorum", + "threshold", + "total_weight" + ], + "properties": { + "quorum": { + "$ref": "#/definitions/Decimal" + }, + "threshold": { + "$ref": "#/definitions/Decimal" + }, + "total_weight": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + ] + }, + "Timestamp": { + "description": "A point in time in nanosecond precision.\n\nThis type can represent times from 1970-01-01T00:00:00Z to 2554-07-21T23:34:33Z.\n\n## Examples\n\n``` # use cosmwasm_std::Timestamp; let ts = Timestamp::from_nanos(1_000_000_202); assert_eq!(ts.nanos(), 1_000_000_202); assert_eq!(ts.seconds(), 1); assert_eq!(ts.subsec_nanos(), 202);\n\nlet ts = ts.plus_seconds(2); assert_eq!(ts.nanos(), 3_000_000_202); assert_eq!(ts.seconds(), 3); assert_eq!(ts.subsec_nanos(), 202); ```", + "allOf": [ + { + "$ref": "#/definitions/Uint64" + } + ] + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + }, + "Uint64": { + "description": "A thin wrapper around u64 that is using strings for JSON encoding/decoding, such that the full u64 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u64` to get the value out:\n\n``` # use cosmwasm_std::Uint64; let a = Uint64::from(42u64); assert_eq!(a.u64(), 42);\n\nlet b = Uint64::from(70u32); assert_eq!(b.u64(), 70); ```", + "type": "string" + }, + "VoteOption": { + "type": "string", + "enum": [ + "yes", + "no", + "abstain", + "no_with_veto" + ] + }, + "WasmMsg": { + "description": "The message types of the wasm module.\n\nSee https://github.com/CosmWasm/wasmd/blob/v0.14.0/x/wasm/internal/types/tx.proto", + "oneOf": [ + { + "description": "Dispatches a call to another contract at a known address (with known ABI).\n\nThis is translated to a [MsgExecuteContract](https://github.com/CosmWasm/wasmd/blob/v0.14.0/x/wasm/internal/types/tx.proto#L68-L78). `sender` is automatically filled with the current contract's address.", + "type": "object", + "required": [ + "execute" + ], + "properties": { + "execute": { + "type": "object", + "required": [ + "contract_addr", + "funds", + "msg" + ], + "properties": { + "contract_addr": { + "type": "string" + }, + "funds": { + "type": "array", + "items": { + "$ref": "#/definitions/Coin" + } + }, + "msg": { + "description": "msg is the json-encoded ExecuteMsg struct (as raw Binary)", + "allOf": [ + { + "$ref": "#/definitions/Binary" + } + ] + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Instantiates a new contracts from previously uploaded Wasm code.\n\nThe contract address is non-predictable. But it is guaranteed that when emitting the same Instantiate message multiple times, multiple instances on different addresses will be generated. See also Instantiate2.\n\nThis is translated to a [MsgInstantiateContract](https://github.com/CosmWasm/wasmd/blob/v0.29.2/proto/cosmwasm/wasm/v1/tx.proto#L53-L71). `sender` is automatically filled with the current contract's address.", + "type": "object", + "required": [ + "instantiate" + ], + "properties": { + "instantiate": { + "type": "object", + "required": [ + "code_id", + "funds", + "label", + "msg" + ], + "properties": { + "admin": { + "type": [ + "string", + "null" + ] + }, + "code_id": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "funds": { + "type": "array", + "items": { + "$ref": "#/definitions/Coin" + } + }, + "label": { + "description": "A human-readbale label for the contract", + "type": "string" + }, + "msg": { + "description": "msg is the JSON-encoded InstantiateMsg struct (as raw Binary)", + "allOf": [ + { + "$ref": "#/definitions/Binary" + } + ] + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Migrates a given contracts to use new wasm code. Passes a MigrateMsg to allow us to customize behavior.\n\nOnly the contract admin (as defined in wasmd), if any, is able to make this call.\n\nThis is translated to a [MsgMigrateContract](https://github.com/CosmWasm/wasmd/blob/v0.14.0/x/wasm/internal/types/tx.proto#L86-L96). `sender` is automatically filled with the current contract's address.", + "type": "object", + "required": [ + "migrate" + ], + "properties": { + "migrate": { + "type": "object", + "required": [ + "contract_addr", + "msg", + "new_code_id" + ], + "properties": { + "contract_addr": { + "type": "string" + }, + "msg": { + "description": "msg is the json-encoded MigrateMsg struct that will be passed to the new code", + "allOf": [ + { + "$ref": "#/definitions/Binary" + } + ] + }, + "new_code_id": { + "description": "the code_id of the new logic to place in the given contract", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Sets a new admin (for migrate) on the given contract. Fails if this contract is not currently admin of the target contract.", + "type": "object", + "required": [ + "update_admin" + ], + "properties": { + "update_admin": { + "type": "object", + "required": [ + "admin", + "contract_addr" + ], + "properties": { + "admin": { + "type": "string" + }, + "contract_addr": { + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Clears the admin on the given contract, so no more migration possible. Fails if this contract is not currently admin of the target contract.", + "type": "object", + "required": [ + "clear_admin" + ], + "properties": { + "clear_admin": { + "type": "object", + "required": [ + "contract_addr" + ], + "properties": { + "contract_addr": { + "type": "string" + } + } + } + }, + "additionalProperties": false + } + ] + } + } +} diff --git a/contracts/multisig/cw3-flex-multisig/schema/raw/response_to_list_voters.json b/contracts/multisig/cw3-flex-multisig/schema/raw/response_to_list_voters.json new file mode 100644 index 0000000000..9e337d64ea --- /dev/null +++ b/contracts/multisig/cw3-flex-multisig/schema/raw/response_to_list_voters.json @@ -0,0 +1,37 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "VoterListResponse", + "type": "object", + "required": [ + "voters" + ], + "properties": { + "voters": { + "type": "array", + "items": { + "$ref": "#/definitions/VoterDetail" + } + } + }, + "additionalProperties": false, + "definitions": { + "VoterDetail": { + "type": "object", + "required": [ + "addr", + "weight" + ], + "properties": { + "addr": { + "type": "string" + }, + "weight": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + } +} diff --git a/contracts/multisig/cw3-flex-multisig/schema/raw/response_to_list_votes.json b/contracts/multisig/cw3-flex-multisig/schema/raw/response_to_list_votes.json new file mode 100644 index 0000000000..90a4c459b0 --- /dev/null +++ b/contracts/multisig/cw3-flex-multisig/schema/raw/response_to_list_votes.json @@ -0,0 +1,80 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "VoteListResponse", + "type": "object", + "required": [ + "votes" + ], + "properties": { + "votes": { + "type": "array", + "items": { + "$ref": "#/definitions/VoteInfo" + } + } + }, + "additionalProperties": false, + "definitions": { + "Vote": { + "oneOf": [ + { + "description": "Marks support for the proposal.", + "type": "string", + "enum": [ + "yes" + ] + }, + { + "description": "Marks opposition to the proposal.", + "type": "string", + "enum": [ + "no" + ] + }, + { + "description": "Marks participation but does not count towards the ratio of support / opposed", + "type": "string", + "enum": [ + "abstain" + ] + }, + { + "description": "Veto is generally to be treated as a No vote. Some implementations may allow certain voters to be able to Veto, or them to be counted stronger than No in some way.", + "type": "string", + "enum": [ + "veto" + ] + } + ] + }, + "VoteInfo": { + "description": "Returns the vote (opinion as well as weight counted) as well as the address of the voter who submitted it", + "type": "object", + "required": [ + "proposal_id", + "vote", + "voter", + "weight" + ], + "properties": { + "proposal_id": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "vote": { + "$ref": "#/definitions/Vote" + }, + "voter": { + "type": "string" + }, + "weight": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + } +} diff --git a/contracts/multisig/cw3-flex-multisig/schema/raw/response_to_proposal.json b/contracts/multisig/cw3-flex-multisig/schema/raw/response_to_proposal.json new file mode 100644 index 0000000000..751ed3c7bc --- /dev/null +++ b/contracts/multisig/cw3-flex-multisig/schema/raw/response_to_proposal.json @@ -0,0 +1,1030 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ProposalResponse_for_Empty", + "description": "Note, if you are storing custom messages in the proposal, the querier needs to know what possible custom message types those are in order to parse the response", + "type": "object", + "required": [ + "description", + "expires", + "id", + "msgs", + "proposer", + "status", + "threshold", + "title" + ], + "properties": { + "deposit": { + "anyOf": [ + { + "$ref": "#/definitions/DepositInfo" + }, + { + "type": "null" + } + ] + }, + "description": { + "type": "string" + }, + "expires": { + "$ref": "#/definitions/Expiration" + }, + "id": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "msgs": { + "type": "array", + "items": { + "$ref": "#/definitions/CosmosMsg_for_Empty" + } + }, + "proposer": { + "$ref": "#/definitions/Addr" + }, + "status": { + "$ref": "#/definitions/Status" + }, + "threshold": { + "description": "This is the threshold that is applied to this proposal. Both the rules of the voting contract, as well as the total_weight of the voting group may have changed since this time. That means that the generic `Threshold{}` query does not provide valid information for existing proposals.", + "allOf": [ + { + "$ref": "#/definitions/ThresholdResponse" + } + ] + }, + "title": { + "type": "string" + } + }, + "additionalProperties": false, + "definitions": { + "Addr": { + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + }, + "BankMsg": { + "description": "The message types of the bank module.\n\nSee https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/bank/v1beta1/tx.proto", + "oneOf": [ + { + "description": "Sends native tokens from the contract to the given address.\n\nThis is translated to a [MsgSend](https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/bank/v1beta1/tx.proto#L19-L28). `from_address` is automatically filled with the current contract's address.", + "type": "object", + "required": [ + "send" + ], + "properties": { + "send": { + "type": "object", + "required": [ + "amount", + "to_address" + ], + "properties": { + "amount": { + "type": "array", + "items": { + "$ref": "#/definitions/Coin" + } + }, + "to_address": { + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "This will burn the given coins from the contract's account. There is no Cosmos SDK message that performs this, but it can be done by calling the bank keeper. Important if a contract controls significant token supply that must be retired.", + "type": "object", + "required": [ + "burn" + ], + "properties": { + "burn": { + "type": "object", + "required": [ + "amount" + ], + "properties": { + "amount": { + "type": "array", + "items": { + "$ref": "#/definitions/Coin" + } + } + } + } + }, + "additionalProperties": false + } + ] + }, + "Binary": { + "description": "Binary is a wrapper around Vec to add base64 de/serialization with serde. It also adds some helper methods to help encode inline.\n\nThis is only needed as serde-json-{core,wasm} has a horrible encoding for Vec. See also .", + "type": "string" + }, + "Coin": { + "type": "object", + "required": [ + "amount", + "denom" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "denom": { + "type": "string" + } + } + }, + "CosmosMsg_for_Empty": { + "oneOf": [ + { + "type": "object", + "required": [ + "bank" + ], + "properties": { + "bank": { + "$ref": "#/definitions/BankMsg" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "custom" + ], + "properties": { + "custom": { + "$ref": "#/definitions/Empty" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "staking" + ], + "properties": { + "staking": { + "$ref": "#/definitions/StakingMsg" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "distribution" + ], + "properties": { + "distribution": { + "$ref": "#/definitions/DistributionMsg" + } + }, + "additionalProperties": false + }, + { + "description": "A Stargate message encoded the same way as a protobuf [Any](https://github.com/protocolbuffers/protobuf/blob/master/src/google/protobuf/any.proto). This is the same structure as messages in `TxBody` from [ADR-020](https://github.com/cosmos/cosmos-sdk/blob/master/docs/architecture/adr-020-protobuf-transaction-encoding.md)", + "type": "object", + "required": [ + "stargate" + ], + "properties": { + "stargate": { + "type": "object", + "required": [ + "type_url", + "value" + ], + "properties": { + "type_url": { + "type": "string" + }, + "value": { + "$ref": "#/definitions/Binary" + } + } + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "ibc" + ], + "properties": { + "ibc": { + "$ref": "#/definitions/IbcMsg" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "wasm" + ], + "properties": { + "wasm": { + "$ref": "#/definitions/WasmMsg" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "gov" + ], + "properties": { + "gov": { + "$ref": "#/definitions/GovMsg" + } + }, + "additionalProperties": false + } + ] + }, + "Decimal": { + "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", + "type": "string" + }, + "Denom": { + "oneOf": [ + { + "type": "object", + "required": [ + "native" + ], + "properties": { + "native": { + "type": "string" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "cw20" + ], + "properties": { + "cw20": { + "$ref": "#/definitions/Addr" + } + }, + "additionalProperties": false + } + ] + }, + "DepositInfo": { + "description": "Information about the deposit required to create a proposal.", + "type": "object", + "required": [ + "amount", + "denom", + "refund_failed_proposals" + ], + "properties": { + "amount": { + "description": "The number tokens required for payment.", + "allOf": [ + { + "$ref": "#/definitions/Uint128" + } + ] + }, + "denom": { + "description": "The denom of the deposit payment.", + "allOf": [ + { + "$ref": "#/definitions/Denom" + } + ] + }, + "refund_failed_proposals": { + "description": "Should failed proposals have their deposits refunded?", + "type": "boolean" + } + }, + "additionalProperties": false + }, + "DistributionMsg": { + "description": "The message types of the distribution module.\n\nSee https://github.com/cosmos/cosmos-sdk/blob/v0.42.4/proto/cosmos/distribution/v1beta1/tx.proto", + "oneOf": [ + { + "description": "This is translated to a [MsgSetWithdrawAddress](https://github.com/cosmos/cosmos-sdk/blob/v0.42.4/proto/cosmos/distribution/v1beta1/tx.proto#L29-L37). `delegator_address` is automatically filled with the current contract's address.", + "type": "object", + "required": [ + "set_withdraw_address" + ], + "properties": { + "set_withdraw_address": { + "type": "object", + "required": [ + "address" + ], + "properties": { + "address": { + "description": "The `withdraw_address`", + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "This is translated to a [[MsgWithdrawDelegatorReward](https://github.com/cosmos/cosmos-sdk/blob/v0.42.4/proto/cosmos/distribution/v1beta1/tx.proto#L42-L50). `delegator_address` is automatically filled with the current contract's address.", + "type": "object", + "required": [ + "withdraw_delegator_reward" + ], + "properties": { + "withdraw_delegator_reward": { + "type": "object", + "required": [ + "validator" + ], + "properties": { + "validator": { + "description": "The `validator_address`", + "type": "string" + } + } + } + }, + "additionalProperties": false + } + ] + }, + "Empty": { + "description": "An empty struct that serves as a placeholder in different places, such as contracts that don't set a custom message.\n\nIt is designed to be expressable in correct JSON and JSON Schema but contains no meaningful data. Previously we used enums without cases, but those cannot represented as valid JSON Schema (https://github.com/CosmWasm/cosmwasm/issues/451)", + "type": "object" + }, + "Expiration": { + "description": "Expiration represents a point in time when some event happens. It can compare with a BlockInfo and will return is_expired() == true once the condition is hit (and for every block in the future)", + "oneOf": [ + { + "description": "AtHeight will expire when `env.block.height` >= height", + "type": "object", + "required": [ + "at_height" + ], + "properties": { + "at_height": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + }, + { + "description": "AtTime will expire when `env.block.time` >= time", + "type": "object", + "required": [ + "at_time" + ], + "properties": { + "at_time": { + "$ref": "#/definitions/Timestamp" + } + }, + "additionalProperties": false + }, + { + "description": "Never will never expire. Used to express the empty variant", + "type": "object", + "required": [ + "never" + ], + "properties": { + "never": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + } + ] + }, + "GovMsg": { + "description": "This message type allows the contract interact with the [x/gov] module in order to cast votes.\n\n[x/gov]: https://github.com/cosmos/cosmos-sdk/tree/v0.45.12/x/gov\n\n## Examples\n\nCast a simple vote:\n\n``` # use cosmwasm_std::{ # HexBinary, # Storage, Api, Querier, DepsMut, Deps, entry_point, Env, StdError, MessageInfo, # Response, QueryResponse, # }; # type ExecuteMsg = (); use cosmwasm_std::{GovMsg, VoteOption};\n\n#[entry_point] pub fn execute( deps: DepsMut, env: Env, info: MessageInfo, msg: ExecuteMsg, ) -> Result { // ... Ok(Response::new().add_message(GovMsg::Vote { proposal_id: 4, vote: VoteOption::Yes, })) } ```\n\nCast a weighted vote:\n\n``` # use cosmwasm_std::{ # HexBinary, # Storage, Api, Querier, DepsMut, Deps, entry_point, Env, StdError, MessageInfo, # Response, QueryResponse, # }; # type ExecuteMsg = (); # #[cfg(feature = \"cosmwasm_1_2\")] use cosmwasm_std::{Decimal, GovMsg, VoteOption, WeightedVoteOption};\n\n# #[cfg(feature = \"cosmwasm_1_2\")] #[entry_point] pub fn execute( deps: DepsMut, env: Env, info: MessageInfo, msg: ExecuteMsg, ) -> Result { // ... Ok(Response::new().add_message(GovMsg::VoteWeighted { proposal_id: 4, options: vec![ WeightedVoteOption { option: VoteOption::Yes, weight: Decimal::percent(65), }, WeightedVoteOption { option: VoteOption::Abstain, weight: Decimal::percent(35), }, ], })) } ```", + "oneOf": [ + { + "description": "This maps directly to [MsgVote](https://github.com/cosmos/cosmos-sdk/blob/v0.42.5/proto/cosmos/gov/v1beta1/tx.proto#L46-L56) in the Cosmos SDK with voter set to the contract address.", + "type": "object", + "required": [ + "vote" + ], + "properties": { + "vote": { + "type": "object", + "required": [ + "proposal_id", + "vote" + ], + "properties": { + "proposal_id": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "vote": { + "description": "The vote option.\n\nThis should be called \"option\" for consistency with Cosmos SDK. Sorry for that. See .", + "allOf": [ + { + "$ref": "#/definitions/VoteOption" + } + ] + } + } + } + }, + "additionalProperties": false + } + ] + }, + "IbcMsg": { + "description": "These are messages in the IBC lifecycle. Only usable by IBC-enabled contracts (contracts that directly speak the IBC protocol via 6 entry points)", + "oneOf": [ + { + "description": "Sends bank tokens owned by the contract to the given address on another chain. The channel must already be established between the ibctransfer module on this chain and a matching module on the remote chain. We cannot select the port_id, this is whatever the local chain has bound the ibctransfer module to.", + "type": "object", + "required": [ + "transfer" + ], + "properties": { + "transfer": { + "type": "object", + "required": [ + "amount", + "channel_id", + "timeout", + "to_address" + ], + "properties": { + "amount": { + "description": "packet data only supports one coin https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/ibc/applications/transfer/v1/transfer.proto#L11-L20", + "allOf": [ + { + "$ref": "#/definitions/Coin" + } + ] + }, + "channel_id": { + "description": "exisiting channel to send the tokens over", + "type": "string" + }, + "timeout": { + "description": "when packet times out, measured on remote chain", + "allOf": [ + { + "$ref": "#/definitions/IbcTimeout" + } + ] + }, + "to_address": { + "description": "address on the remote chain to receive these tokens", + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Sends an IBC packet with given data over the existing channel. Data should be encoded in a format defined by the channel version, and the module on the other side should know how to parse this.", + "type": "object", + "required": [ + "send_packet" + ], + "properties": { + "send_packet": { + "type": "object", + "required": [ + "channel_id", + "data", + "timeout" + ], + "properties": { + "channel_id": { + "type": "string" + }, + "data": { + "$ref": "#/definitions/Binary" + }, + "timeout": { + "description": "when packet times out, measured on remote chain", + "allOf": [ + { + "$ref": "#/definitions/IbcTimeout" + } + ] + } + } + } + }, + "additionalProperties": false + }, + { + "description": "This will close an existing channel that is owned by this contract. Port is auto-assigned to the contract's IBC port", + "type": "object", + "required": [ + "close_channel" + ], + "properties": { + "close_channel": { + "type": "object", + "required": [ + "channel_id" + ], + "properties": { + "channel_id": { + "type": "string" + } + } + } + }, + "additionalProperties": false + } + ] + }, + "IbcTimeout": { + "description": "In IBC each package must set at least one type of timeout: the timestamp or the block height. Using this rather complex enum instead of two timeout fields we ensure that at least one timeout is set.", + "type": "object", + "properties": { + "block": { + "anyOf": [ + { + "$ref": "#/definitions/IbcTimeoutBlock" + }, + { + "type": "null" + } + ] + }, + "timestamp": { + "anyOf": [ + { + "$ref": "#/definitions/Timestamp" + }, + { + "type": "null" + } + ] + } + } + }, + "IbcTimeoutBlock": { + "description": "IBCTimeoutHeight Height is a monotonically increasing data type that can be compared against another Height for the purposes of updating and freezing clients. Ordering is (revision_number, timeout_height)", + "type": "object", + "required": [ + "height", + "revision" + ], + "properties": { + "height": { + "description": "block height after which the packet times out. the height within the given revision", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "revision": { + "description": "the version that the client is currently on (eg. after reseting the chain this could increment 1 as height drops to 0)", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + } + }, + "StakingMsg": { + "description": "The message types of the staking module.\n\nSee https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/staking/v1beta1/tx.proto", + "oneOf": [ + { + "description": "This is translated to a [MsgDelegate](https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/staking/v1beta1/tx.proto#L81-L90). `delegator_address` is automatically filled with the current contract's address.", + "type": "object", + "required": [ + "delegate" + ], + "properties": { + "delegate": { + "type": "object", + "required": [ + "amount", + "validator" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Coin" + }, + "validator": { + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "This is translated to a [MsgUndelegate](https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/staking/v1beta1/tx.proto#L112-L121). `delegator_address` is automatically filled with the current contract's address.", + "type": "object", + "required": [ + "undelegate" + ], + "properties": { + "undelegate": { + "type": "object", + "required": [ + "amount", + "validator" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Coin" + }, + "validator": { + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "This is translated to a [MsgBeginRedelegate](https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/staking/v1beta1/tx.proto#L95-L105). `delegator_address` is automatically filled with the current contract's address.", + "type": "object", + "required": [ + "redelegate" + ], + "properties": { + "redelegate": { + "type": "object", + "required": [ + "amount", + "dst_validator", + "src_validator" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Coin" + }, + "dst_validator": { + "type": "string" + }, + "src_validator": { + "type": "string" + } + } + } + }, + "additionalProperties": false + } + ] + }, + "Status": { + "oneOf": [ + { + "description": "proposal was created, but voting has not yet begun for whatever reason", + "type": "string", + "enum": [ + "pending" + ] + }, + { + "description": "you can vote on this", + "type": "string", + "enum": [ + "open" + ] + }, + { + "description": "voting is over and it did not pass", + "type": "string", + "enum": [ + "rejected" + ] + }, + { + "description": "voting is over and it did pass, but has not yet executed", + "type": "string", + "enum": [ + "passed" + ] + }, + { + "description": "voting is over it passed, and the proposal was executed", + "type": "string", + "enum": [ + "executed" + ] + } + ] + }, + "ThresholdResponse": { + "description": "This defines the different ways tallies can happen. Every contract should support a subset of these, ideally all.\n\nThe total_weight used for calculating success as well as the weights of each individual voter used in tallying should be snapshotted at the beginning of the block at which the proposal starts (this is likely the responsibility of a correct cw4 implementation).", + "oneOf": [ + { + "description": "Declares that a fixed weight of yes votes is needed to pass. It does not matter how many no votes are cast, or how many do not vote, as long as `weight` yes votes are cast.\n\nThis is the simplest format and usually suitable for small multisigs of trusted parties, like 3 of 5. (weight: 3, total_weight: 5)\n\nA proposal of this type can pass early as soon as the needed weight of yes votes has been cast.", + "type": "object", + "required": [ + "absolute_count" + ], + "properties": { + "absolute_count": { + "type": "object", + "required": [ + "total_weight", + "weight" + ], + "properties": { + "total_weight": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "weight": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Declares a percentage of the total weight that must cast Yes votes, in order for a proposal to pass. The passing weight is computed over the total weight minus the weight of the abstained votes.\n\nThis is useful for similar circumstances as `AbsoluteCount`, where we have a relatively small set of voters, and participation is required. It is understood that if the voting set (group) changes between different proposals that refer to the same group, each proposal will work with a different set of voter weights (the ones snapshotted at proposal creation), and the passing weight for each proposal will be computed based on the absolute percentage, times the total weights of the members at the time of each proposal creation.\n\nExample: we set `percentage` to 51%. Proposal 1 starts when there is a `total_weight` of 5. This will require 3 weight of Yes votes in order to pass. Later, the Proposal 2 starts but the `total_weight` of the group has increased to 9. That proposal will then automatically require 5 Yes of 9 to pass, rather than 3 yes of 9 as would be the case with `AbsoluteCount`.", + "type": "object", + "required": [ + "absolute_percentage" + ], + "properties": { + "absolute_percentage": { + "type": "object", + "required": [ + "percentage", + "total_weight" + ], + "properties": { + "percentage": { + "$ref": "#/definitions/Decimal" + }, + "total_weight": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "In addition to a `threshold`, declares a `quorum` of the total votes that must participate in the election in order for the vote to be considered at all. Within the votes that were cast, it requires `threshold` votes in favor. That is calculated by ignoring the Abstain votes (they count towards `quorum`, but do not influence `threshold`). That is, we calculate `Yes / (Yes + No + Veto)` and compare it with `threshold` to consider if the proposal was passed.\n\nIt is rather difficult for a proposal of this type to pass early. That can only happen if the required quorum has been already met, and there are already enough Yes votes for the proposal to pass.\n\n30% Yes votes, 10% No votes, and 20% Abstain would pass early if quorum <= 60% (who has cast votes) and if the threshold is <= 37.5% (the remaining 40% voting no => 30% yes + 50% no). Once the voting period has passed with no additional votes, that same proposal would be considered successful if quorum <= 60% and threshold <= 75% (percent in favor if we ignore abstain votes).\n\nThis type is more common in general elections, where participation is often expected to be low, and `AbsolutePercentage` would either be too high to pass anything, or allow low percentages to pass, independently of if there was high participation in the election or not.", + "type": "object", + "required": [ + "threshold_quorum" + ], + "properties": { + "threshold_quorum": { + "type": "object", + "required": [ + "quorum", + "threshold", + "total_weight" + ], + "properties": { + "quorum": { + "$ref": "#/definitions/Decimal" + }, + "threshold": { + "$ref": "#/definitions/Decimal" + }, + "total_weight": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + ] + }, + "Timestamp": { + "description": "A point in time in nanosecond precision.\n\nThis type can represent times from 1970-01-01T00:00:00Z to 2554-07-21T23:34:33Z.\n\n## Examples\n\n``` # use cosmwasm_std::Timestamp; let ts = Timestamp::from_nanos(1_000_000_202); assert_eq!(ts.nanos(), 1_000_000_202); assert_eq!(ts.seconds(), 1); assert_eq!(ts.subsec_nanos(), 202);\n\nlet ts = ts.plus_seconds(2); assert_eq!(ts.nanos(), 3_000_000_202); assert_eq!(ts.seconds(), 3); assert_eq!(ts.subsec_nanos(), 202); ```", + "allOf": [ + { + "$ref": "#/definitions/Uint64" + } + ] + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + }, + "Uint64": { + "description": "A thin wrapper around u64 that is using strings for JSON encoding/decoding, such that the full u64 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u64` to get the value out:\n\n``` # use cosmwasm_std::Uint64; let a = Uint64::from(42u64); assert_eq!(a.u64(), 42);\n\nlet b = Uint64::from(70u32); assert_eq!(b.u64(), 70); ```", + "type": "string" + }, + "VoteOption": { + "type": "string", + "enum": [ + "yes", + "no", + "abstain", + "no_with_veto" + ] + }, + "WasmMsg": { + "description": "The message types of the wasm module.\n\nSee https://github.com/CosmWasm/wasmd/blob/v0.14.0/x/wasm/internal/types/tx.proto", + "oneOf": [ + { + "description": "Dispatches a call to another contract at a known address (with known ABI).\n\nThis is translated to a [MsgExecuteContract](https://github.com/CosmWasm/wasmd/blob/v0.14.0/x/wasm/internal/types/tx.proto#L68-L78). `sender` is automatically filled with the current contract's address.", + "type": "object", + "required": [ + "execute" + ], + "properties": { + "execute": { + "type": "object", + "required": [ + "contract_addr", + "funds", + "msg" + ], + "properties": { + "contract_addr": { + "type": "string" + }, + "funds": { + "type": "array", + "items": { + "$ref": "#/definitions/Coin" + } + }, + "msg": { + "description": "msg is the json-encoded ExecuteMsg struct (as raw Binary)", + "allOf": [ + { + "$ref": "#/definitions/Binary" + } + ] + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Instantiates a new contracts from previously uploaded Wasm code.\n\nThe contract address is non-predictable. But it is guaranteed that when emitting the same Instantiate message multiple times, multiple instances on different addresses will be generated. See also Instantiate2.\n\nThis is translated to a [MsgInstantiateContract](https://github.com/CosmWasm/wasmd/blob/v0.29.2/proto/cosmwasm/wasm/v1/tx.proto#L53-L71). `sender` is automatically filled with the current contract's address.", + "type": "object", + "required": [ + "instantiate" + ], + "properties": { + "instantiate": { + "type": "object", + "required": [ + "code_id", + "funds", + "label", + "msg" + ], + "properties": { + "admin": { + "type": [ + "string", + "null" + ] + }, + "code_id": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "funds": { + "type": "array", + "items": { + "$ref": "#/definitions/Coin" + } + }, + "label": { + "description": "A human-readbale label for the contract", + "type": "string" + }, + "msg": { + "description": "msg is the JSON-encoded InstantiateMsg struct (as raw Binary)", + "allOf": [ + { + "$ref": "#/definitions/Binary" + } + ] + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Migrates a given contracts to use new wasm code. Passes a MigrateMsg to allow us to customize behavior.\n\nOnly the contract admin (as defined in wasmd), if any, is able to make this call.\n\nThis is translated to a [MsgMigrateContract](https://github.com/CosmWasm/wasmd/blob/v0.14.0/x/wasm/internal/types/tx.proto#L86-L96). `sender` is automatically filled with the current contract's address.", + "type": "object", + "required": [ + "migrate" + ], + "properties": { + "migrate": { + "type": "object", + "required": [ + "contract_addr", + "msg", + "new_code_id" + ], + "properties": { + "contract_addr": { + "type": "string" + }, + "msg": { + "description": "msg is the json-encoded MigrateMsg struct that will be passed to the new code", + "allOf": [ + { + "$ref": "#/definitions/Binary" + } + ] + }, + "new_code_id": { + "description": "the code_id of the new logic to place in the given contract", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Sets a new admin (for migrate) on the given contract. Fails if this contract is not currently admin of the target contract.", + "type": "object", + "required": [ + "update_admin" + ], + "properties": { + "update_admin": { + "type": "object", + "required": [ + "admin", + "contract_addr" + ], + "properties": { + "admin": { + "type": "string" + }, + "contract_addr": { + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Clears the admin on the given contract, so no more migration possible. Fails if this contract is not currently admin of the target contract.", + "type": "object", + "required": [ + "clear_admin" + ], + "properties": { + "clear_admin": { + "type": "object", + "required": [ + "contract_addr" + ], + "properties": { + "contract_addr": { + "type": "string" + } + } + } + }, + "additionalProperties": false + } + ] + } + } +} diff --git a/contracts/multisig/cw3-flex-multisig/schema/raw/response_to_reverse_proposals.json b/contracts/multisig/cw3-flex-multisig/schema/raw/response_to_reverse_proposals.json new file mode 100644 index 0000000000..0012bec80b --- /dev/null +++ b/contracts/multisig/cw3-flex-multisig/schema/raw/response_to_reverse_proposals.json @@ -0,0 +1,1045 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ProposalListResponse_for_Empty", + "type": "object", + "required": [ + "proposals" + ], + "properties": { + "proposals": { + "type": "array", + "items": { + "$ref": "#/definitions/ProposalResponse_for_Empty" + } + } + }, + "additionalProperties": false, + "definitions": { + "Addr": { + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + }, + "BankMsg": { + "description": "The message types of the bank module.\n\nSee https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/bank/v1beta1/tx.proto", + "oneOf": [ + { + "description": "Sends native tokens from the contract to the given address.\n\nThis is translated to a [MsgSend](https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/bank/v1beta1/tx.proto#L19-L28). `from_address` is automatically filled with the current contract's address.", + "type": "object", + "required": [ + "send" + ], + "properties": { + "send": { + "type": "object", + "required": [ + "amount", + "to_address" + ], + "properties": { + "amount": { + "type": "array", + "items": { + "$ref": "#/definitions/Coin" + } + }, + "to_address": { + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "This will burn the given coins from the contract's account. There is no Cosmos SDK message that performs this, but it can be done by calling the bank keeper. Important if a contract controls significant token supply that must be retired.", + "type": "object", + "required": [ + "burn" + ], + "properties": { + "burn": { + "type": "object", + "required": [ + "amount" + ], + "properties": { + "amount": { + "type": "array", + "items": { + "$ref": "#/definitions/Coin" + } + } + } + } + }, + "additionalProperties": false + } + ] + }, + "Binary": { + "description": "Binary is a wrapper around Vec to add base64 de/serialization with serde. It also adds some helper methods to help encode inline.\n\nThis is only needed as serde-json-{core,wasm} has a horrible encoding for Vec. See also .", + "type": "string" + }, + "Coin": { + "type": "object", + "required": [ + "amount", + "denom" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "denom": { + "type": "string" + } + } + }, + "CosmosMsg_for_Empty": { + "oneOf": [ + { + "type": "object", + "required": [ + "bank" + ], + "properties": { + "bank": { + "$ref": "#/definitions/BankMsg" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "custom" + ], + "properties": { + "custom": { + "$ref": "#/definitions/Empty" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "staking" + ], + "properties": { + "staking": { + "$ref": "#/definitions/StakingMsg" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "distribution" + ], + "properties": { + "distribution": { + "$ref": "#/definitions/DistributionMsg" + } + }, + "additionalProperties": false + }, + { + "description": "A Stargate message encoded the same way as a protobuf [Any](https://github.com/protocolbuffers/protobuf/blob/master/src/google/protobuf/any.proto). This is the same structure as messages in `TxBody` from [ADR-020](https://github.com/cosmos/cosmos-sdk/blob/master/docs/architecture/adr-020-protobuf-transaction-encoding.md)", + "type": "object", + "required": [ + "stargate" + ], + "properties": { + "stargate": { + "type": "object", + "required": [ + "type_url", + "value" + ], + "properties": { + "type_url": { + "type": "string" + }, + "value": { + "$ref": "#/definitions/Binary" + } + } + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "ibc" + ], + "properties": { + "ibc": { + "$ref": "#/definitions/IbcMsg" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "wasm" + ], + "properties": { + "wasm": { + "$ref": "#/definitions/WasmMsg" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "gov" + ], + "properties": { + "gov": { + "$ref": "#/definitions/GovMsg" + } + }, + "additionalProperties": false + } + ] + }, + "Decimal": { + "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", + "type": "string" + }, + "Denom": { + "oneOf": [ + { + "type": "object", + "required": [ + "native" + ], + "properties": { + "native": { + "type": "string" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "cw20" + ], + "properties": { + "cw20": { + "$ref": "#/definitions/Addr" + } + }, + "additionalProperties": false + } + ] + }, + "DepositInfo": { + "description": "Information about the deposit required to create a proposal.", + "type": "object", + "required": [ + "amount", + "denom", + "refund_failed_proposals" + ], + "properties": { + "amount": { + "description": "The number tokens required for payment.", + "allOf": [ + { + "$ref": "#/definitions/Uint128" + } + ] + }, + "denom": { + "description": "The denom of the deposit payment.", + "allOf": [ + { + "$ref": "#/definitions/Denom" + } + ] + }, + "refund_failed_proposals": { + "description": "Should failed proposals have their deposits refunded?", + "type": "boolean" + } + }, + "additionalProperties": false + }, + "DistributionMsg": { + "description": "The message types of the distribution module.\n\nSee https://github.com/cosmos/cosmos-sdk/blob/v0.42.4/proto/cosmos/distribution/v1beta1/tx.proto", + "oneOf": [ + { + "description": "This is translated to a [MsgSetWithdrawAddress](https://github.com/cosmos/cosmos-sdk/blob/v0.42.4/proto/cosmos/distribution/v1beta1/tx.proto#L29-L37). `delegator_address` is automatically filled with the current contract's address.", + "type": "object", + "required": [ + "set_withdraw_address" + ], + "properties": { + "set_withdraw_address": { + "type": "object", + "required": [ + "address" + ], + "properties": { + "address": { + "description": "The `withdraw_address`", + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "This is translated to a [[MsgWithdrawDelegatorReward](https://github.com/cosmos/cosmos-sdk/blob/v0.42.4/proto/cosmos/distribution/v1beta1/tx.proto#L42-L50). `delegator_address` is automatically filled with the current contract's address.", + "type": "object", + "required": [ + "withdraw_delegator_reward" + ], + "properties": { + "withdraw_delegator_reward": { + "type": "object", + "required": [ + "validator" + ], + "properties": { + "validator": { + "description": "The `validator_address`", + "type": "string" + } + } + } + }, + "additionalProperties": false + } + ] + }, + "Empty": { + "description": "An empty struct that serves as a placeholder in different places, such as contracts that don't set a custom message.\n\nIt is designed to be expressable in correct JSON and JSON Schema but contains no meaningful data. Previously we used enums without cases, but those cannot represented as valid JSON Schema (https://github.com/CosmWasm/cosmwasm/issues/451)", + "type": "object" + }, + "Expiration": { + "description": "Expiration represents a point in time when some event happens. It can compare with a BlockInfo and will return is_expired() == true once the condition is hit (and for every block in the future)", + "oneOf": [ + { + "description": "AtHeight will expire when `env.block.height` >= height", + "type": "object", + "required": [ + "at_height" + ], + "properties": { + "at_height": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + }, + { + "description": "AtTime will expire when `env.block.time` >= time", + "type": "object", + "required": [ + "at_time" + ], + "properties": { + "at_time": { + "$ref": "#/definitions/Timestamp" + } + }, + "additionalProperties": false + }, + { + "description": "Never will never expire. Used to express the empty variant", + "type": "object", + "required": [ + "never" + ], + "properties": { + "never": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + } + ] + }, + "GovMsg": { + "description": "This message type allows the contract interact with the [x/gov] module in order to cast votes.\n\n[x/gov]: https://github.com/cosmos/cosmos-sdk/tree/v0.45.12/x/gov\n\n## Examples\n\nCast a simple vote:\n\n``` # use cosmwasm_std::{ # HexBinary, # Storage, Api, Querier, DepsMut, Deps, entry_point, Env, StdError, MessageInfo, # Response, QueryResponse, # }; # type ExecuteMsg = (); use cosmwasm_std::{GovMsg, VoteOption};\n\n#[entry_point] pub fn execute( deps: DepsMut, env: Env, info: MessageInfo, msg: ExecuteMsg, ) -> Result { // ... Ok(Response::new().add_message(GovMsg::Vote { proposal_id: 4, vote: VoteOption::Yes, })) } ```\n\nCast a weighted vote:\n\n``` # use cosmwasm_std::{ # HexBinary, # Storage, Api, Querier, DepsMut, Deps, entry_point, Env, StdError, MessageInfo, # Response, QueryResponse, # }; # type ExecuteMsg = (); # #[cfg(feature = \"cosmwasm_1_2\")] use cosmwasm_std::{Decimal, GovMsg, VoteOption, WeightedVoteOption};\n\n# #[cfg(feature = \"cosmwasm_1_2\")] #[entry_point] pub fn execute( deps: DepsMut, env: Env, info: MessageInfo, msg: ExecuteMsg, ) -> Result { // ... Ok(Response::new().add_message(GovMsg::VoteWeighted { proposal_id: 4, options: vec![ WeightedVoteOption { option: VoteOption::Yes, weight: Decimal::percent(65), }, WeightedVoteOption { option: VoteOption::Abstain, weight: Decimal::percent(35), }, ], })) } ```", + "oneOf": [ + { + "description": "This maps directly to [MsgVote](https://github.com/cosmos/cosmos-sdk/blob/v0.42.5/proto/cosmos/gov/v1beta1/tx.proto#L46-L56) in the Cosmos SDK with voter set to the contract address.", + "type": "object", + "required": [ + "vote" + ], + "properties": { + "vote": { + "type": "object", + "required": [ + "proposal_id", + "vote" + ], + "properties": { + "proposal_id": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "vote": { + "description": "The vote option.\n\nThis should be called \"option\" for consistency with Cosmos SDK. Sorry for that. See .", + "allOf": [ + { + "$ref": "#/definitions/VoteOption" + } + ] + } + } + } + }, + "additionalProperties": false + } + ] + }, + "IbcMsg": { + "description": "These are messages in the IBC lifecycle. Only usable by IBC-enabled contracts (contracts that directly speak the IBC protocol via 6 entry points)", + "oneOf": [ + { + "description": "Sends bank tokens owned by the contract to the given address on another chain. The channel must already be established between the ibctransfer module on this chain and a matching module on the remote chain. We cannot select the port_id, this is whatever the local chain has bound the ibctransfer module to.", + "type": "object", + "required": [ + "transfer" + ], + "properties": { + "transfer": { + "type": "object", + "required": [ + "amount", + "channel_id", + "timeout", + "to_address" + ], + "properties": { + "amount": { + "description": "packet data only supports one coin https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/ibc/applications/transfer/v1/transfer.proto#L11-L20", + "allOf": [ + { + "$ref": "#/definitions/Coin" + } + ] + }, + "channel_id": { + "description": "exisiting channel to send the tokens over", + "type": "string" + }, + "timeout": { + "description": "when packet times out, measured on remote chain", + "allOf": [ + { + "$ref": "#/definitions/IbcTimeout" + } + ] + }, + "to_address": { + "description": "address on the remote chain to receive these tokens", + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Sends an IBC packet with given data over the existing channel. Data should be encoded in a format defined by the channel version, and the module on the other side should know how to parse this.", + "type": "object", + "required": [ + "send_packet" + ], + "properties": { + "send_packet": { + "type": "object", + "required": [ + "channel_id", + "data", + "timeout" + ], + "properties": { + "channel_id": { + "type": "string" + }, + "data": { + "$ref": "#/definitions/Binary" + }, + "timeout": { + "description": "when packet times out, measured on remote chain", + "allOf": [ + { + "$ref": "#/definitions/IbcTimeout" + } + ] + } + } + } + }, + "additionalProperties": false + }, + { + "description": "This will close an existing channel that is owned by this contract. Port is auto-assigned to the contract's IBC port", + "type": "object", + "required": [ + "close_channel" + ], + "properties": { + "close_channel": { + "type": "object", + "required": [ + "channel_id" + ], + "properties": { + "channel_id": { + "type": "string" + } + } + } + }, + "additionalProperties": false + } + ] + }, + "IbcTimeout": { + "description": "In IBC each package must set at least one type of timeout: the timestamp or the block height. Using this rather complex enum instead of two timeout fields we ensure that at least one timeout is set.", + "type": "object", + "properties": { + "block": { + "anyOf": [ + { + "$ref": "#/definitions/IbcTimeoutBlock" + }, + { + "type": "null" + } + ] + }, + "timestamp": { + "anyOf": [ + { + "$ref": "#/definitions/Timestamp" + }, + { + "type": "null" + } + ] + } + } + }, + "IbcTimeoutBlock": { + "description": "IBCTimeoutHeight Height is a monotonically increasing data type that can be compared against another Height for the purposes of updating and freezing clients. Ordering is (revision_number, timeout_height)", + "type": "object", + "required": [ + "height", + "revision" + ], + "properties": { + "height": { + "description": "block height after which the packet times out. the height within the given revision", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "revision": { + "description": "the version that the client is currently on (eg. after reseting the chain this could increment 1 as height drops to 0)", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + } + }, + "ProposalResponse_for_Empty": { + "description": "Note, if you are storing custom messages in the proposal, the querier needs to know what possible custom message types those are in order to parse the response", + "type": "object", + "required": [ + "description", + "expires", + "id", + "msgs", + "proposer", + "status", + "threshold", + "title" + ], + "properties": { + "deposit": { + "anyOf": [ + { + "$ref": "#/definitions/DepositInfo" + }, + { + "type": "null" + } + ] + }, + "description": { + "type": "string" + }, + "expires": { + "$ref": "#/definitions/Expiration" + }, + "id": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "msgs": { + "type": "array", + "items": { + "$ref": "#/definitions/CosmosMsg_for_Empty" + } + }, + "proposer": { + "$ref": "#/definitions/Addr" + }, + "status": { + "$ref": "#/definitions/Status" + }, + "threshold": { + "description": "This is the threshold that is applied to this proposal. Both the rules of the voting contract, as well as the total_weight of the voting group may have changed since this time. That means that the generic `Threshold{}` query does not provide valid information for existing proposals.", + "allOf": [ + { + "$ref": "#/definitions/ThresholdResponse" + } + ] + }, + "title": { + "type": "string" + } + }, + "additionalProperties": false + }, + "StakingMsg": { + "description": "The message types of the staking module.\n\nSee https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/staking/v1beta1/tx.proto", + "oneOf": [ + { + "description": "This is translated to a [MsgDelegate](https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/staking/v1beta1/tx.proto#L81-L90). `delegator_address` is automatically filled with the current contract's address.", + "type": "object", + "required": [ + "delegate" + ], + "properties": { + "delegate": { + "type": "object", + "required": [ + "amount", + "validator" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Coin" + }, + "validator": { + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "This is translated to a [MsgUndelegate](https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/staking/v1beta1/tx.proto#L112-L121). `delegator_address` is automatically filled with the current contract's address.", + "type": "object", + "required": [ + "undelegate" + ], + "properties": { + "undelegate": { + "type": "object", + "required": [ + "amount", + "validator" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Coin" + }, + "validator": { + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "This is translated to a [MsgBeginRedelegate](https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/staking/v1beta1/tx.proto#L95-L105). `delegator_address` is automatically filled with the current contract's address.", + "type": "object", + "required": [ + "redelegate" + ], + "properties": { + "redelegate": { + "type": "object", + "required": [ + "amount", + "dst_validator", + "src_validator" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Coin" + }, + "dst_validator": { + "type": "string" + }, + "src_validator": { + "type": "string" + } + } + } + }, + "additionalProperties": false + } + ] + }, + "Status": { + "oneOf": [ + { + "description": "proposal was created, but voting has not yet begun for whatever reason", + "type": "string", + "enum": [ + "pending" + ] + }, + { + "description": "you can vote on this", + "type": "string", + "enum": [ + "open" + ] + }, + { + "description": "voting is over and it did not pass", + "type": "string", + "enum": [ + "rejected" + ] + }, + { + "description": "voting is over and it did pass, but has not yet executed", + "type": "string", + "enum": [ + "passed" + ] + }, + { + "description": "voting is over it passed, and the proposal was executed", + "type": "string", + "enum": [ + "executed" + ] + } + ] + }, + "ThresholdResponse": { + "description": "This defines the different ways tallies can happen. Every contract should support a subset of these, ideally all.\n\nThe total_weight used for calculating success as well as the weights of each individual voter used in tallying should be snapshotted at the beginning of the block at which the proposal starts (this is likely the responsibility of a correct cw4 implementation).", + "oneOf": [ + { + "description": "Declares that a fixed weight of yes votes is needed to pass. It does not matter how many no votes are cast, or how many do not vote, as long as `weight` yes votes are cast.\n\nThis is the simplest format and usually suitable for small multisigs of trusted parties, like 3 of 5. (weight: 3, total_weight: 5)\n\nA proposal of this type can pass early as soon as the needed weight of yes votes has been cast.", + "type": "object", + "required": [ + "absolute_count" + ], + "properties": { + "absolute_count": { + "type": "object", + "required": [ + "total_weight", + "weight" + ], + "properties": { + "total_weight": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "weight": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Declares a percentage of the total weight that must cast Yes votes, in order for a proposal to pass. The passing weight is computed over the total weight minus the weight of the abstained votes.\n\nThis is useful for similar circumstances as `AbsoluteCount`, where we have a relatively small set of voters, and participation is required. It is understood that if the voting set (group) changes between different proposals that refer to the same group, each proposal will work with a different set of voter weights (the ones snapshotted at proposal creation), and the passing weight for each proposal will be computed based on the absolute percentage, times the total weights of the members at the time of each proposal creation.\n\nExample: we set `percentage` to 51%. Proposal 1 starts when there is a `total_weight` of 5. This will require 3 weight of Yes votes in order to pass. Later, the Proposal 2 starts but the `total_weight` of the group has increased to 9. That proposal will then automatically require 5 Yes of 9 to pass, rather than 3 yes of 9 as would be the case with `AbsoluteCount`.", + "type": "object", + "required": [ + "absolute_percentage" + ], + "properties": { + "absolute_percentage": { + "type": "object", + "required": [ + "percentage", + "total_weight" + ], + "properties": { + "percentage": { + "$ref": "#/definitions/Decimal" + }, + "total_weight": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "In addition to a `threshold`, declares a `quorum` of the total votes that must participate in the election in order for the vote to be considered at all. Within the votes that were cast, it requires `threshold` votes in favor. That is calculated by ignoring the Abstain votes (they count towards `quorum`, but do not influence `threshold`). That is, we calculate `Yes / (Yes + No + Veto)` and compare it with `threshold` to consider if the proposal was passed.\n\nIt is rather difficult for a proposal of this type to pass early. That can only happen if the required quorum has been already met, and there are already enough Yes votes for the proposal to pass.\n\n30% Yes votes, 10% No votes, and 20% Abstain would pass early if quorum <= 60% (who has cast votes) and if the threshold is <= 37.5% (the remaining 40% voting no => 30% yes + 50% no). Once the voting period has passed with no additional votes, that same proposal would be considered successful if quorum <= 60% and threshold <= 75% (percent in favor if we ignore abstain votes).\n\nThis type is more common in general elections, where participation is often expected to be low, and `AbsolutePercentage` would either be too high to pass anything, or allow low percentages to pass, independently of if there was high participation in the election or not.", + "type": "object", + "required": [ + "threshold_quorum" + ], + "properties": { + "threshold_quorum": { + "type": "object", + "required": [ + "quorum", + "threshold", + "total_weight" + ], + "properties": { + "quorum": { + "$ref": "#/definitions/Decimal" + }, + "threshold": { + "$ref": "#/definitions/Decimal" + }, + "total_weight": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + ] + }, + "Timestamp": { + "description": "A point in time in nanosecond precision.\n\nThis type can represent times from 1970-01-01T00:00:00Z to 2554-07-21T23:34:33Z.\n\n## Examples\n\n``` # use cosmwasm_std::Timestamp; let ts = Timestamp::from_nanos(1_000_000_202); assert_eq!(ts.nanos(), 1_000_000_202); assert_eq!(ts.seconds(), 1); assert_eq!(ts.subsec_nanos(), 202);\n\nlet ts = ts.plus_seconds(2); assert_eq!(ts.nanos(), 3_000_000_202); assert_eq!(ts.seconds(), 3); assert_eq!(ts.subsec_nanos(), 202); ```", + "allOf": [ + { + "$ref": "#/definitions/Uint64" + } + ] + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + }, + "Uint64": { + "description": "A thin wrapper around u64 that is using strings for JSON encoding/decoding, such that the full u64 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u64` to get the value out:\n\n``` # use cosmwasm_std::Uint64; let a = Uint64::from(42u64); assert_eq!(a.u64(), 42);\n\nlet b = Uint64::from(70u32); assert_eq!(b.u64(), 70); ```", + "type": "string" + }, + "VoteOption": { + "type": "string", + "enum": [ + "yes", + "no", + "abstain", + "no_with_veto" + ] + }, + "WasmMsg": { + "description": "The message types of the wasm module.\n\nSee https://github.com/CosmWasm/wasmd/blob/v0.14.0/x/wasm/internal/types/tx.proto", + "oneOf": [ + { + "description": "Dispatches a call to another contract at a known address (with known ABI).\n\nThis is translated to a [MsgExecuteContract](https://github.com/CosmWasm/wasmd/blob/v0.14.0/x/wasm/internal/types/tx.proto#L68-L78). `sender` is automatically filled with the current contract's address.", + "type": "object", + "required": [ + "execute" + ], + "properties": { + "execute": { + "type": "object", + "required": [ + "contract_addr", + "funds", + "msg" + ], + "properties": { + "contract_addr": { + "type": "string" + }, + "funds": { + "type": "array", + "items": { + "$ref": "#/definitions/Coin" + } + }, + "msg": { + "description": "msg is the json-encoded ExecuteMsg struct (as raw Binary)", + "allOf": [ + { + "$ref": "#/definitions/Binary" + } + ] + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Instantiates a new contracts from previously uploaded Wasm code.\n\nThe contract address is non-predictable. But it is guaranteed that when emitting the same Instantiate message multiple times, multiple instances on different addresses will be generated. See also Instantiate2.\n\nThis is translated to a [MsgInstantiateContract](https://github.com/CosmWasm/wasmd/blob/v0.29.2/proto/cosmwasm/wasm/v1/tx.proto#L53-L71). `sender` is automatically filled with the current contract's address.", + "type": "object", + "required": [ + "instantiate" + ], + "properties": { + "instantiate": { + "type": "object", + "required": [ + "code_id", + "funds", + "label", + "msg" + ], + "properties": { + "admin": { + "type": [ + "string", + "null" + ] + }, + "code_id": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "funds": { + "type": "array", + "items": { + "$ref": "#/definitions/Coin" + } + }, + "label": { + "description": "A human-readbale label for the contract", + "type": "string" + }, + "msg": { + "description": "msg is the JSON-encoded InstantiateMsg struct (as raw Binary)", + "allOf": [ + { + "$ref": "#/definitions/Binary" + } + ] + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Migrates a given contracts to use new wasm code. Passes a MigrateMsg to allow us to customize behavior.\n\nOnly the contract admin (as defined in wasmd), if any, is able to make this call.\n\nThis is translated to a [MsgMigrateContract](https://github.com/CosmWasm/wasmd/blob/v0.14.0/x/wasm/internal/types/tx.proto#L86-L96). `sender` is automatically filled with the current contract's address.", + "type": "object", + "required": [ + "migrate" + ], + "properties": { + "migrate": { + "type": "object", + "required": [ + "contract_addr", + "msg", + "new_code_id" + ], + "properties": { + "contract_addr": { + "type": "string" + }, + "msg": { + "description": "msg is the json-encoded MigrateMsg struct that will be passed to the new code", + "allOf": [ + { + "$ref": "#/definitions/Binary" + } + ] + }, + "new_code_id": { + "description": "the code_id of the new logic to place in the given contract", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Sets a new admin (for migrate) on the given contract. Fails if this contract is not currently admin of the target contract.", + "type": "object", + "required": [ + "update_admin" + ], + "properties": { + "update_admin": { + "type": "object", + "required": [ + "admin", + "contract_addr" + ], + "properties": { + "admin": { + "type": "string" + }, + "contract_addr": { + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Clears the admin on the given contract, so no more migration possible. Fails if this contract is not currently admin of the target contract.", + "type": "object", + "required": [ + "clear_admin" + ], + "properties": { + "clear_admin": { + "type": "object", + "required": [ + "contract_addr" + ], + "properties": { + "contract_addr": { + "type": "string" + } + } + } + }, + "additionalProperties": false + } + ] + } + } +} diff --git a/contracts/multisig/cw3-flex-multisig/schema/raw/response_to_threshold.json b/contracts/multisig/cw3-flex-multisig/schema/raw/response_to_threshold.json new file mode 100644 index 0000000000..3c7dae60fe --- /dev/null +++ b/contracts/multisig/cw3-flex-multisig/schema/raw/response_to_threshold.json @@ -0,0 +1,103 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ThresholdResponse", + "description": "This defines the different ways tallies can happen. Every contract should support a subset of these, ideally all.\n\nThe total_weight used for calculating success as well as the weights of each individual voter used in tallying should be snapshotted at the beginning of the block at which the proposal starts (this is likely the responsibility of a correct cw4 implementation).", + "oneOf": [ + { + "description": "Declares that a fixed weight of yes votes is needed to pass. It does not matter how many no votes are cast, or how many do not vote, as long as `weight` yes votes are cast.\n\nThis is the simplest format and usually suitable for small multisigs of trusted parties, like 3 of 5. (weight: 3, total_weight: 5)\n\nA proposal of this type can pass early as soon as the needed weight of yes votes has been cast.", + "type": "object", + "required": [ + "absolute_count" + ], + "properties": { + "absolute_count": { + "type": "object", + "required": [ + "total_weight", + "weight" + ], + "properties": { + "total_weight": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "weight": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Declares a percentage of the total weight that must cast Yes votes, in order for a proposal to pass. The passing weight is computed over the total weight minus the weight of the abstained votes.\n\nThis is useful for similar circumstances as `AbsoluteCount`, where we have a relatively small set of voters, and participation is required. It is understood that if the voting set (group) changes between different proposals that refer to the same group, each proposal will work with a different set of voter weights (the ones snapshotted at proposal creation), and the passing weight for each proposal will be computed based on the absolute percentage, times the total weights of the members at the time of each proposal creation.\n\nExample: we set `percentage` to 51%. Proposal 1 starts when there is a `total_weight` of 5. This will require 3 weight of Yes votes in order to pass. Later, the Proposal 2 starts but the `total_weight` of the group has increased to 9. That proposal will then automatically require 5 Yes of 9 to pass, rather than 3 yes of 9 as would be the case with `AbsoluteCount`.", + "type": "object", + "required": [ + "absolute_percentage" + ], + "properties": { + "absolute_percentage": { + "type": "object", + "required": [ + "percentage", + "total_weight" + ], + "properties": { + "percentage": { + "$ref": "#/definitions/Decimal" + }, + "total_weight": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "In addition to a `threshold`, declares a `quorum` of the total votes that must participate in the election in order for the vote to be considered at all. Within the votes that were cast, it requires `threshold` votes in favor. That is calculated by ignoring the Abstain votes (they count towards `quorum`, but do not influence `threshold`). That is, we calculate `Yes / (Yes + No + Veto)` and compare it with `threshold` to consider if the proposal was passed.\n\nIt is rather difficult for a proposal of this type to pass early. That can only happen if the required quorum has been already met, and there are already enough Yes votes for the proposal to pass.\n\n30% Yes votes, 10% No votes, and 20% Abstain would pass early if quorum <= 60% (who has cast votes) and if the threshold is <= 37.5% (the remaining 40% voting no => 30% yes + 50% no). Once the voting period has passed with no additional votes, that same proposal would be considered successful if quorum <= 60% and threshold <= 75% (percent in favor if we ignore abstain votes).\n\nThis type is more common in general elections, where participation is often expected to be low, and `AbsolutePercentage` would either be too high to pass anything, or allow low percentages to pass, independently of if there was high participation in the election or not.", + "type": "object", + "required": [ + "threshold_quorum" + ], + "properties": { + "threshold_quorum": { + "type": "object", + "required": [ + "quorum", + "threshold", + "total_weight" + ], + "properties": { + "quorum": { + "$ref": "#/definitions/Decimal" + }, + "threshold": { + "$ref": "#/definitions/Decimal" + }, + "total_weight": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + ], + "definitions": { + "Decimal": { + "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", + "type": "string" + } + } +} diff --git a/contracts/multisig/cw3-flex-multisig/schema/raw/response_to_vote.json b/contracts/multisig/cw3-flex-multisig/schema/raw/response_to_vote.json new file mode 100644 index 0000000000..ba779b7a0d --- /dev/null +++ b/contracts/multisig/cw3-flex-multisig/schema/raw/response_to_vote.json @@ -0,0 +1,81 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "VoteResponse", + "type": "object", + "properties": { + "vote": { + "anyOf": [ + { + "$ref": "#/definitions/VoteInfo" + }, + { + "type": "null" + } + ] + } + }, + "additionalProperties": false, + "definitions": { + "Vote": { + "oneOf": [ + { + "description": "Marks support for the proposal.", + "type": "string", + "enum": [ + "yes" + ] + }, + { + "description": "Marks opposition to the proposal.", + "type": "string", + "enum": [ + "no" + ] + }, + { + "description": "Marks participation but does not count towards the ratio of support / opposed", + "type": "string", + "enum": [ + "abstain" + ] + }, + { + "description": "Veto is generally to be treated as a No vote. Some implementations may allow certain voters to be able to Veto, or them to be counted stronger than No in some way.", + "type": "string", + "enum": [ + "veto" + ] + } + ] + }, + "VoteInfo": { + "description": "Returns the vote (opinion as well as weight counted) as well as the address of the voter who submitted it", + "type": "object", + "required": [ + "proposal_id", + "vote", + "voter", + "weight" + ], + "properties": { + "proposal_id": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "vote": { + "$ref": "#/definitions/Vote" + }, + "voter": { + "type": "string" + }, + "weight": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + } +} diff --git a/contracts/multisig/cw3-flex-multisig/schema/raw/response_to_voter.json b/contracts/multisig/cw3-flex-multisig/schema/raw/response_to_voter.json new file mode 100644 index 0000000000..125b4e1430 --- /dev/null +++ b/contracts/multisig/cw3-flex-multisig/schema/raw/response_to_voter.json @@ -0,0 +1,16 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "VoterResponse", + "type": "object", + "properties": { + "weight": { + "type": [ + "integer", + "null" + ], + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false +} diff --git a/contracts/multisig/cw3-flex-multisig/src/bin/schema.rs b/contracts/multisig/cw3-flex-multisig/src/bin/schema.rs new file mode 100644 index 0000000000..869f53b3a6 --- /dev/null +++ b/contracts/multisig/cw3-flex-multisig/src/bin/schema.rs @@ -0,0 +1,14 @@ +// Copyright 2023 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use cosmwasm_schema::write_api; +use nym_multisig_contract_common::msg::{ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg}; + +fn main() { + write_api! { + instantiate: InstantiateMsg, + query: QueryMsg, + execute: ExecuteMsg, + migrate: MigrateMsg, + } +} diff --git a/contracts/multisig/cw4-group/Cargo.toml b/contracts/multisig/cw4-group/Cargo.toml index 180ee66b9d..a2897779b9 100644 --- a/contracts/multisig/cw4-group/Cargo.toml +++ b/contracts/multisig/cw4-group/Cargo.toml @@ -16,6 +16,9 @@ exclude = [ # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html +[[bin]] +name = "schema" + [lib] crate-type = ["cdylib", "rlib"] @@ -25,6 +28,7 @@ backtraces = ["cosmwasm-std/backtraces"] # use library feature to disable all instantiate/execute/query exports library = [] + [dependencies] nym-group-contract-common = { path = "../../../common/cosmwasm-smart-contracts/group-contract" } @@ -38,3 +42,4 @@ cosmwasm-std = { workspace = true } schemars = "0.8.1" serde = { version = "1.0.103", default-features = false, features = ["derive"] } thiserror = { version = "1.0.23" } + diff --git a/contracts/multisig/cw4-group/Makefile b/contracts/multisig/cw4-group/Makefile new file mode 100644 index 0000000000..f138e2c879 --- /dev/null +++ b/contracts/multisig/cw4-group/Makefile @@ -0,0 +1,2 @@ +generate-schema: + cargo schema \ No newline at end of file diff --git a/contracts/multisig/cw4-group/schema/cw4-group.json b/contracts/multisig/cw4-group/schema/cw4-group.json new file mode 100644 index 0000000000..5e0f5af99b --- /dev/null +++ b/contracts/multisig/cw4-group/schema/cw4-group.json @@ -0,0 +1,395 @@ +{ + "contract_name": "cw4-group", + "contract_version": "1.0.0", + "idl_version": "1.0.0", + "instantiate": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "InstantiateMsg", + "type": "object", + "required": [ + "members" + ], + "properties": { + "admin": { + "description": "The admin is the only account that can update the group state. Omit it to make the group immutable.", + "type": [ + "string", + "null" + ] + }, + "members": { + "type": "array", + "items": { + "$ref": "#/definitions/Member" + } + } + }, + "additionalProperties": false, + "definitions": { + "Member": { + "description": "A group member has a weight associated with them. This may all be equal, or may have meaning in the app that makes use of the group (eg. voting power)", + "type": "object", + "required": [ + "addr", + "weight" + ], + "properties": { + "addr": { + "type": "string" + }, + "weight": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + } + }, + "execute": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ExecuteMsg", + "oneOf": [ + { + "description": "Change the admin", + "type": "object", + "required": [ + "update_admin" + ], + "properties": { + "update_admin": { + "type": "object", + "properties": { + "admin": { + "type": [ + "string", + "null" + ] + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "apply a diff to the existing members. remove is applied after add, so if an address is in both, it is removed", + "type": "object", + "required": [ + "update_members" + ], + "properties": { + "update_members": { + "type": "object", + "required": [ + "add", + "remove" + ], + "properties": { + "add": { + "type": "array", + "items": { + "$ref": "#/definitions/Member" + } + }, + "remove": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Add a new hook to be informed of all membership changes. Must be called by Admin", + "type": "object", + "required": [ + "add_hook" + ], + "properties": { + "add_hook": { + "type": "object", + "required": [ + "addr" + ], + "properties": { + "addr": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Remove a hook. Must be called by Admin", + "type": "object", + "required": [ + "remove_hook" + ], + "properties": { + "remove_hook": { + "type": "object", + "required": [ + "addr" + ], + "properties": { + "addr": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + ], + "definitions": { + "Member": { + "description": "A group member has a weight associated with them. This may all be equal, or may have meaning in the app that makes use of the group (eg. voting power)", + "type": "object", + "required": [ + "addr", + "weight" + ], + "properties": { + "addr": { + "type": "string" + }, + "weight": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + } + }, + "query": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "QueryMsg", + "oneOf": [ + { + "type": "object", + "required": [ + "admin" + ], + "properties": { + "admin": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "total_weight" + ], + "properties": { + "total_weight": { + "type": "object", + "properties": { + "at_height": { + "type": [ + "integer", + "null" + ], + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "list_members" + ], + "properties": { + "list_members": { + "type": "object", + "properties": { + "limit": { + "type": [ + "integer", + "null" + ], + "format": "uint32", + "minimum": 0.0 + }, + "start_after": { + "type": [ + "string", + "null" + ] + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "member" + ], + "properties": { + "member": { + "type": "object", + "required": [ + "addr" + ], + "properties": { + "addr": { + "type": "string" + }, + "at_height": { + "type": [ + "integer", + "null" + ], + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Shows all registered hooks.", + "type": "object", + "required": [ + "hooks" + ], + "properties": { + "hooks": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + } + ] + }, + "migrate": null, + "sudo": null, + "responses": { + "admin": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "AdminResponse", + "description": "Returned from Admin.query_admin()", + "type": "object", + "properties": { + "admin": { + "type": [ + "string", + "null" + ] + } + }, + "additionalProperties": false + }, + "hooks": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "HooksResponse", + "type": "object", + "required": [ + "hooks" + ], + "properties": { + "hooks": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "additionalProperties": false + }, + "list_members": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "MemberListResponse", + "type": "object", + "required": [ + "members" + ], + "properties": { + "members": { + "type": "array", + "items": { + "$ref": "#/definitions/Member" + } + } + }, + "additionalProperties": false, + "definitions": { + "Member": { + "description": "A group member has a weight associated with them. This may all be equal, or may have meaning in the app that makes use of the group (eg. voting power)", + "type": "object", + "required": [ + "addr", + "weight" + ], + "properties": { + "addr": { + "type": "string" + }, + "weight": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + } + }, + "member": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "MemberResponse", + "type": "object", + "properties": { + "weight": { + "type": [ + "integer", + "null" + ], + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + }, + "total_weight": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "TotalWeightResponse", + "type": "object", + "required": [ + "weight" + ], + "properties": { + "weight": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + } +} diff --git a/contracts/multisig/cw4-group/schema/raw/execute.json b/contracts/multisig/cw4-group/schema/raw/execute.json new file mode 100644 index 0000000000..526169f1bf --- /dev/null +++ b/contracts/multisig/cw4-group/schema/raw/execute.json @@ -0,0 +1,125 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ExecuteMsg", + "oneOf": [ + { + "description": "Change the admin", + "type": "object", + "required": [ + "update_admin" + ], + "properties": { + "update_admin": { + "type": "object", + "properties": { + "admin": { + "type": [ + "string", + "null" + ] + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "apply a diff to the existing members. remove is applied after add, so if an address is in both, it is removed", + "type": "object", + "required": [ + "update_members" + ], + "properties": { + "update_members": { + "type": "object", + "required": [ + "add", + "remove" + ], + "properties": { + "add": { + "type": "array", + "items": { + "$ref": "#/definitions/Member" + } + }, + "remove": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Add a new hook to be informed of all membership changes. Must be called by Admin", + "type": "object", + "required": [ + "add_hook" + ], + "properties": { + "add_hook": { + "type": "object", + "required": [ + "addr" + ], + "properties": { + "addr": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Remove a hook. Must be called by Admin", + "type": "object", + "required": [ + "remove_hook" + ], + "properties": { + "remove_hook": { + "type": "object", + "required": [ + "addr" + ], + "properties": { + "addr": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + ], + "definitions": { + "Member": { + "description": "A group member has a weight associated with them. This may all be equal, or may have meaning in the app that makes use of the group (eg. voting power)", + "type": "object", + "required": [ + "addr", + "weight" + ], + "properties": { + "addr": { + "type": "string" + }, + "weight": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + } +} diff --git a/contracts/multisig/cw4-group/schema/raw/instantiate.json b/contracts/multisig/cw4-group/schema/raw/instantiate.json new file mode 100644 index 0000000000..bba7b445a7 --- /dev/null +++ b/contracts/multisig/cw4-group/schema/raw/instantiate.json @@ -0,0 +1,45 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "InstantiateMsg", + "type": "object", + "required": [ + "members" + ], + "properties": { + "admin": { + "description": "The admin is the only account that can update the group state. Omit it to make the group immutable.", + "type": [ + "string", + "null" + ] + }, + "members": { + "type": "array", + "items": { + "$ref": "#/definitions/Member" + } + } + }, + "additionalProperties": false, + "definitions": { + "Member": { + "description": "A group member has a weight associated with them. This may all be equal, or may have meaning in the app that makes use of the group (eg. voting power)", + "type": "object", + "required": [ + "addr", + "weight" + ], + "properties": { + "addr": { + "type": "string" + }, + "weight": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + } +} diff --git a/contracts/multisig/cw4-group/schema/raw/query.json b/contracts/multisig/cw4-group/schema/raw/query.json new file mode 100644 index 0000000000..ec6abedb8d --- /dev/null +++ b/contracts/multisig/cw4-group/schema/raw/query.json @@ -0,0 +1,114 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "QueryMsg", + "oneOf": [ + { + "type": "object", + "required": [ + "admin" + ], + "properties": { + "admin": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "total_weight" + ], + "properties": { + "total_weight": { + "type": "object", + "properties": { + "at_height": { + "type": [ + "integer", + "null" + ], + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "list_members" + ], + "properties": { + "list_members": { + "type": "object", + "properties": { + "limit": { + "type": [ + "integer", + "null" + ], + "format": "uint32", + "minimum": 0.0 + }, + "start_after": { + "type": [ + "string", + "null" + ] + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "member" + ], + "properties": { + "member": { + "type": "object", + "required": [ + "addr" + ], + "properties": { + "addr": { + "type": "string" + }, + "at_height": { + "type": [ + "integer", + "null" + ], + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Shows all registered hooks.", + "type": "object", + "required": [ + "hooks" + ], + "properties": { + "hooks": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + } + ] +} diff --git a/contracts/multisig/cw4-group/schema/raw/response_to_admin.json b/contracts/multisig/cw4-group/schema/raw/response_to_admin.json new file mode 100644 index 0000000000..c73969ab04 --- /dev/null +++ b/contracts/multisig/cw4-group/schema/raw/response_to_admin.json @@ -0,0 +1,15 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "AdminResponse", + "description": "Returned from Admin.query_admin()", + "type": "object", + "properties": { + "admin": { + "type": [ + "string", + "null" + ] + } + }, + "additionalProperties": false +} diff --git a/contracts/multisig/cw4-group/schema/raw/response_to_hooks.json b/contracts/multisig/cw4-group/schema/raw/response_to_hooks.json new file mode 100644 index 0000000000..6d46524400 --- /dev/null +++ b/contracts/multisig/cw4-group/schema/raw/response_to_hooks.json @@ -0,0 +1,17 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "HooksResponse", + "type": "object", + "required": [ + "hooks" + ], + "properties": { + "hooks": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "additionalProperties": false +} diff --git a/contracts/multisig/cw4-group/schema/raw/response_to_list_members.json b/contracts/multisig/cw4-group/schema/raw/response_to_list_members.json new file mode 100644 index 0000000000..39617c0538 --- /dev/null +++ b/contracts/multisig/cw4-group/schema/raw/response_to_list_members.json @@ -0,0 +1,38 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "MemberListResponse", + "type": "object", + "required": [ + "members" + ], + "properties": { + "members": { + "type": "array", + "items": { + "$ref": "#/definitions/Member" + } + } + }, + "additionalProperties": false, + "definitions": { + "Member": { + "description": "A group member has a weight associated with them. This may all be equal, or may have meaning in the app that makes use of the group (eg. voting power)", + "type": "object", + "required": [ + "addr", + "weight" + ], + "properties": { + "addr": { + "type": "string" + }, + "weight": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + } +} diff --git a/contracts/multisig/cw4-group/schema/raw/response_to_member.json b/contracts/multisig/cw4-group/schema/raw/response_to_member.json new file mode 100644 index 0000000000..0de8fdcfde --- /dev/null +++ b/contracts/multisig/cw4-group/schema/raw/response_to_member.json @@ -0,0 +1,16 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "MemberResponse", + "type": "object", + "properties": { + "weight": { + "type": [ + "integer", + "null" + ], + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false +} diff --git a/contracts/multisig/cw4-group/schema/raw/response_to_total_weight.json b/contracts/multisig/cw4-group/schema/raw/response_to_total_weight.json new file mode 100644 index 0000000000..9b1f597629 --- /dev/null +++ b/contracts/multisig/cw4-group/schema/raw/response_to_total_weight.json @@ -0,0 +1,16 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "TotalWeightResponse", + "type": "object", + "required": [ + "weight" + ], + "properties": { + "weight": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false +} diff --git a/contracts/multisig/cw4-group/src/bin/schema.rs b/contracts/multisig/cw4-group/src/bin/schema.rs new file mode 100644 index 0000000000..a46ff6cf2e --- /dev/null +++ b/contracts/multisig/cw4-group/src/bin/schema.rs @@ -0,0 +1,13 @@ +// Copyright 2023 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use cosmwasm_schema::write_api; +use nym_group_contract_common::msg::{ExecuteMsg, InstantiateMsg, QueryMsg}; + +fn main() { + write_api! { + instantiate: InstantiateMsg, + query: QueryMsg, + execute: ExecuteMsg, + } +} diff --git a/contracts/name-service/.cargo/config b/contracts/name-service/.cargo/config new file mode 100644 index 0000000000..2fb2c1afdb --- /dev/null +++ b/contracts/name-service/.cargo/config @@ -0,0 +1,4 @@ +[alias] +wasm = "build --release --lib --target wasm32-unknown-unknown" +unit-test = "test --lib" +schema = "run --bin schema --features=schema-gen" \ No newline at end of file diff --git a/contracts/name-service/Cargo.toml b/contracts/name-service/Cargo.toml index 56fe0145cf..7bbacf8ba1 100644 --- a/contracts/name-service/Cargo.toml +++ b/contracts/name-service/Cargo.toml @@ -3,11 +3,16 @@ name = "nym-name-service" version = "0.1.0" edition = "2021" +[[bin]] +name = "schema" +required-features = ["schema-gen"] + [lib] crate-type = ["cdylib", "rlib"] [dependencies] bs58 = "0.4.0" +cosmwasm-schema = { workspace = true, optional = true } cosmwasm-std = { workspace = true } cw-controllers = { workspace = true } cw-storage-plus = { workspace = true } @@ -29,3 +34,6 @@ nym-crypto = { path = "../../common/crypto", features = ["asymmetric", "rand"] } rand = "0.8.5" rand_chacha = "0.2" rstest = "0.17.0" + +[features] +schema-gen = ["nym-name-service-common/schema", "cosmwasm-schema"] diff --git a/contracts/name-service/Makefile b/contracts/name-service/Makefile new file mode 100644 index 0000000000..f138e2c879 --- /dev/null +++ b/contracts/name-service/Makefile @@ -0,0 +1,2 @@ +generate-schema: + cargo schema \ No newline at end of file diff --git a/contracts/name-service/schema/nym-name-service.json b/contracts/name-service/schema/nym-name-service.json new file mode 100644 index 0000000000..f9da4f3cf8 --- /dev/null +++ b/contracts/name-service/schema/nym-name-service.json @@ -0,0 +1,1242 @@ +{ + "contract_name": "nym-name-service", + "contract_version": "0.1.0", + "idl_version": "1.0.0", + "instantiate": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "InstantiateMsg", + "type": "object", + "required": [ + "deposit_required" + ], + "properties": { + "deposit_required": { + "$ref": "#/definitions/Coin" + } + }, + "additionalProperties": false, + "definitions": { + "Coin": { + "type": "object", + "required": [ + "amount", + "denom" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "denom": { + "type": "string" + } + } + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } + }, + "execute": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ExecuteMsg", + "oneOf": [ + { + "description": "Announcing a name pointing to a nym-address", + "type": "object", + "required": [ + "register" + ], + "properties": { + "register": { + "type": "object", + "required": [ + "name", + "owner_signature" + ], + "properties": { + "name": { + "$ref": "#/definitions/NameDetails" + }, + "owner_signature": { + "$ref": "#/definitions/MessageSignature" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Delete a name entry by id", + "type": "object", + "required": [ + "delete_id" + ], + "properties": { + "delete_id": { + "type": "object", + "required": [ + "name_id" + ], + "properties": { + "name_id": { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Delete a name entry by name", + "type": "object", + "required": [ + "delete_name" + ], + "properties": { + "delete_name": { + "type": "object", + "required": [ + "name" + ], + "properties": { + "name": { + "$ref": "#/definitions/NymName" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Change the deposit required for announcing a name", + "type": "object", + "required": [ + "update_deposit_required" + ], + "properties": { + "update_deposit_required": { + "type": "object", + "required": [ + "deposit_required" + ], + "properties": { + "deposit_required": { + "$ref": "#/definitions/Coin" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + ], + "definitions": { + "Address": { + "description": "String representation of a nym address, which is of the form client_id.client_enc@gateway_id. NOTE: entirely unvalidated.", + "oneOf": [ + { + "type": "object", + "required": [ + "nym_address" + ], + "properties": { + "nym_address": { + "type": "string" + } + }, + "additionalProperties": false + } + ] + }, + "Coin": { + "type": "object", + "required": [ + "amount", + "denom" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "denom": { + "type": "string" + } + } + }, + "MessageSignature": { + "type": "array", + "items": { + "type": "integer", + "format": "uint8", + "minimum": 0.0 + } + }, + "NameDetails": { + "type": "object", + "required": [ + "address", + "identity_key", + "name" + ], + "properties": { + "address": { + "description": "The address of the name alias.", + "allOf": [ + { + "$ref": "#/definitions/Address" + } + ] + }, + "identity_key": { + "description": "The identity key of the registered name.", + "type": "string" + }, + "name": { + "description": "The name pointing to the nym address", + "allOf": [ + { + "$ref": "#/definitions/NymName" + } + ] + } + }, + "additionalProperties": false + }, + "NymName": { + "description": "Name stored and pointing a to a nym-address", + "type": "string" + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } + }, + "query": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "QueryMsg", + "oneOf": [ + { + "description": "Query the name by it's assigned id", + "type": "object", + "required": [ + "name_id" + ], + "properties": { + "name_id": { + "type": "object", + "required": [ + "name_id" + ], + "properties": { + "name_id": { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Query the names by the registrator", + "type": "object", + "required": [ + "by_owner" + ], + "properties": { + "by_owner": { + "type": "object", + "required": [ + "owner" + ], + "properties": { + "owner": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "by_name" + ], + "properties": { + "by_name": { + "type": "object", + "required": [ + "name" + ], + "properties": { + "name": { + "$ref": "#/definitions/NymName" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "by_address" + ], + "properties": { + "by_address": { + "type": "object", + "required": [ + "address" + ], + "properties": { + "address": { + "$ref": "#/definitions/Address" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "all" + ], + "properties": { + "all": { + "type": "object", + "properties": { + "limit": { + "type": [ + "integer", + "null" + ], + "format": "uint32", + "minimum": 0.0 + }, + "start_after": { + "type": [ + "integer", + "null" + ], + "format": "uint32", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "signing_nonce" + ], + "properties": { + "signing_nonce": { + "type": "object", + "required": [ + "address" + ], + "properties": { + "address": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "config" + ], + "properties": { + "config": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Gets build information of this contract, such as the commit hash used for the build or rustc version.", + "type": "object", + "required": [ + "get_contract_version" + ], + "properties": { + "get_contract_version": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Gets the stored contract version information that's required by the CW2 spec interface for migrations.", + "type": "object", + "required": [ + "get_cw2_contract_version" + ], + "properties": { + "get_cw2_contract_version": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + } + ], + "definitions": { + "Address": { + "description": "String representation of a nym address, which is of the form client_id.client_enc@gateway_id. NOTE: entirely unvalidated.", + "oneOf": [ + { + "type": "object", + "required": [ + "nym_address" + ], + "properties": { + "nym_address": { + "type": "string" + } + }, + "additionalProperties": false + } + ] + }, + "NymName": { + "description": "Name stored and pointing a to a nym-address", + "type": "string" + } + } + }, + "migrate": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "MigrateMsg", + "type": "object", + "additionalProperties": false + }, + "sudo": null, + "responses": { + "all": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "PagedNamesListResponse", + "type": "object", + "required": [ + "names", + "per_page" + ], + "properties": { + "names": { + "type": "array", + "items": { + "$ref": "#/definitions/RegisteredName" + } + }, + "per_page": { + "type": "integer", + "format": "uint", + "minimum": 0.0 + }, + "start_next_after": { + "description": "Field indicating paging information for the following queries if the caller wishes to get further entries.", + "type": [ + "integer", + "null" + ], + "format": "uint32", + "minimum": 0.0 + } + }, + "additionalProperties": false, + "definitions": { + "Addr": { + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + }, + "Address": { + "description": "String representation of a nym address, which is of the form client_id.client_enc@gateway_id. NOTE: entirely unvalidated.", + "oneOf": [ + { + "type": "object", + "required": [ + "nym_address" + ], + "properties": { + "nym_address": { + "type": "string" + } + }, + "additionalProperties": false + } + ] + }, + "Coin": { + "type": "object", + "required": [ + "amount", + "denom" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "denom": { + "type": "string" + } + } + }, + "NameDetails": { + "type": "object", + "required": [ + "address", + "identity_key", + "name" + ], + "properties": { + "address": { + "description": "The address of the name alias.", + "allOf": [ + { + "$ref": "#/definitions/Address" + } + ] + }, + "identity_key": { + "description": "The identity key of the registered name.", + "type": "string" + }, + "name": { + "description": "The name pointing to the nym address", + "allOf": [ + { + "$ref": "#/definitions/NymName" + } + ] + } + }, + "additionalProperties": false + }, + "NymName": { + "description": "Name stored and pointing a to a nym-address", + "type": "string" + }, + "RegisteredName": { + "type": "object", + "required": [ + "block_height", + "deposit", + "id", + "name", + "owner" + ], + "properties": { + "block_height": { + "description": "Block height at which the name was added.", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "deposit": { + "description": "The deposit used to announce the name.", + "allOf": [ + { + "$ref": "#/definitions/Coin" + } + ] + }, + "id": { + "description": "Unique id assigned to the registerd name.", + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "name": { + "description": "The registerd name details.", + "allOf": [ + { + "$ref": "#/definitions/NameDetails" + } + ] + }, + "owner": { + "description": "name owner.", + "allOf": [ + { + "$ref": "#/definitions/Addr" + } + ] + } + }, + "additionalProperties": false + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } + }, + "by_address": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "NamesListResponse", + "type": "object", + "required": [ + "names" + ], + "properties": { + "names": { + "type": "array", + "items": { + "$ref": "#/definitions/RegisteredName" + } + } + }, + "additionalProperties": false, + "definitions": { + "Addr": { + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + }, + "Address": { + "description": "String representation of a nym address, which is of the form client_id.client_enc@gateway_id. NOTE: entirely unvalidated.", + "oneOf": [ + { + "type": "object", + "required": [ + "nym_address" + ], + "properties": { + "nym_address": { + "type": "string" + } + }, + "additionalProperties": false + } + ] + }, + "Coin": { + "type": "object", + "required": [ + "amount", + "denom" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "denom": { + "type": "string" + } + } + }, + "NameDetails": { + "type": "object", + "required": [ + "address", + "identity_key", + "name" + ], + "properties": { + "address": { + "description": "The address of the name alias.", + "allOf": [ + { + "$ref": "#/definitions/Address" + } + ] + }, + "identity_key": { + "description": "The identity key of the registered name.", + "type": "string" + }, + "name": { + "description": "The name pointing to the nym address", + "allOf": [ + { + "$ref": "#/definitions/NymName" + } + ] + } + }, + "additionalProperties": false + }, + "NymName": { + "description": "Name stored and pointing a to a nym-address", + "type": "string" + }, + "RegisteredName": { + "type": "object", + "required": [ + "block_height", + "deposit", + "id", + "name", + "owner" + ], + "properties": { + "block_height": { + "description": "Block height at which the name was added.", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "deposit": { + "description": "The deposit used to announce the name.", + "allOf": [ + { + "$ref": "#/definitions/Coin" + } + ] + }, + "id": { + "description": "Unique id assigned to the registerd name.", + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "name": { + "description": "The registerd name details.", + "allOf": [ + { + "$ref": "#/definitions/NameDetails" + } + ] + }, + "owner": { + "description": "name owner.", + "allOf": [ + { + "$ref": "#/definitions/Addr" + } + ] + } + }, + "additionalProperties": false + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } + }, + "by_name": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "RegisteredName", + "type": "object", + "required": [ + "block_height", + "deposit", + "id", + "name", + "owner" + ], + "properties": { + "block_height": { + "description": "Block height at which the name was added.", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "deposit": { + "description": "The deposit used to announce the name.", + "allOf": [ + { + "$ref": "#/definitions/Coin" + } + ] + }, + "id": { + "description": "Unique id assigned to the registerd name.", + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "name": { + "description": "The registerd name details.", + "allOf": [ + { + "$ref": "#/definitions/NameDetails" + } + ] + }, + "owner": { + "description": "name owner.", + "allOf": [ + { + "$ref": "#/definitions/Addr" + } + ] + } + }, + "additionalProperties": false, + "definitions": { + "Addr": { + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + }, + "Address": { + "description": "String representation of a nym address, which is of the form client_id.client_enc@gateway_id. NOTE: entirely unvalidated.", + "oneOf": [ + { + "type": "object", + "required": [ + "nym_address" + ], + "properties": { + "nym_address": { + "type": "string" + } + }, + "additionalProperties": false + } + ] + }, + "Coin": { + "type": "object", + "required": [ + "amount", + "denom" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "denom": { + "type": "string" + } + } + }, + "NameDetails": { + "type": "object", + "required": [ + "address", + "identity_key", + "name" + ], + "properties": { + "address": { + "description": "The address of the name alias.", + "allOf": [ + { + "$ref": "#/definitions/Address" + } + ] + }, + "identity_key": { + "description": "The identity key of the registered name.", + "type": "string" + }, + "name": { + "description": "The name pointing to the nym address", + "allOf": [ + { + "$ref": "#/definitions/NymName" + } + ] + } + }, + "additionalProperties": false + }, + "NymName": { + "description": "Name stored and pointing a to a nym-address", + "type": "string" + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } + }, + "by_owner": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "NamesListResponse", + "type": "object", + "required": [ + "names" + ], + "properties": { + "names": { + "type": "array", + "items": { + "$ref": "#/definitions/RegisteredName" + } + } + }, + "additionalProperties": false, + "definitions": { + "Addr": { + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + }, + "Address": { + "description": "String representation of a nym address, which is of the form client_id.client_enc@gateway_id. NOTE: entirely unvalidated.", + "oneOf": [ + { + "type": "object", + "required": [ + "nym_address" + ], + "properties": { + "nym_address": { + "type": "string" + } + }, + "additionalProperties": false + } + ] + }, + "Coin": { + "type": "object", + "required": [ + "amount", + "denom" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "denom": { + "type": "string" + } + } + }, + "NameDetails": { + "type": "object", + "required": [ + "address", + "identity_key", + "name" + ], + "properties": { + "address": { + "description": "The address of the name alias.", + "allOf": [ + { + "$ref": "#/definitions/Address" + } + ] + }, + "identity_key": { + "description": "The identity key of the registered name.", + "type": "string" + }, + "name": { + "description": "The name pointing to the nym address", + "allOf": [ + { + "$ref": "#/definitions/NymName" + } + ] + } + }, + "additionalProperties": false + }, + "NymName": { + "description": "Name stored and pointing a to a nym-address", + "type": "string" + }, + "RegisteredName": { + "type": "object", + "required": [ + "block_height", + "deposit", + "id", + "name", + "owner" + ], + "properties": { + "block_height": { + "description": "Block height at which the name was added.", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "deposit": { + "description": "The deposit used to announce the name.", + "allOf": [ + { + "$ref": "#/definitions/Coin" + } + ] + }, + "id": { + "description": "Unique id assigned to the registerd name.", + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "name": { + "description": "The registerd name details.", + "allOf": [ + { + "$ref": "#/definitions/NameDetails" + } + ] + }, + "owner": { + "description": "name owner.", + "allOf": [ + { + "$ref": "#/definitions/Addr" + } + ] + } + }, + "additionalProperties": false + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } + }, + "config": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ConfigResponse", + "type": "object", + "required": [ + "deposit_required" + ], + "properties": { + "deposit_required": { + "$ref": "#/definitions/Coin" + } + }, + "additionalProperties": false, + "definitions": { + "Coin": { + "type": "object", + "required": [ + "amount", + "denom" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "denom": { + "type": "string" + } + } + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } + }, + "get_c_w2_contract_version": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ContractVersion", + "type": "object", + "required": [ + "contract", + "version" + ], + "properties": { + "contract": { + "description": "contract is the crate name of the implementing contract, eg. `crate:cw20-base` we will use other prefixes for other languages, and their standard global namespacing", + "type": "string" + }, + "version": { + "description": "version is any string that this implementation knows. It may be simple counter \"1\", \"2\". or semantic version on release tags \"v0.7.0\", or some custom feature flag list. the only code that needs to understand the version parsing is code that knows how to migrate from the given contract (and is tied to it's implementation somehow)", + "type": "string" + } + }, + "additionalProperties": false + }, + "get_contract_version": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ContractBuildInformation", + "type": "object", + "required": [ + "build_timestamp", + "build_version", + "commit_branch", + "commit_sha", + "commit_timestamp", + "rustc_version" + ], + "properties": { + "build_timestamp": { + "description": "Provides the build timestamp, for example `2021-02-23T20:14:46.558472672+00:00`.", + "type": "string" + }, + "build_version": { + "description": "Provides the build version, for example `0.1.0-9-g46f83e1`.", + "type": "string" + }, + "commit_branch": { + "description": "Provides the name of the git branch that was used for the build, for example `master`.", + "type": "string" + }, + "commit_sha": { + "description": "Provides the hash of the commit that was used for the build, for example `46f83e112520533338245862d366f6a02cef07d4`.", + "type": "string" + }, + "commit_timestamp": { + "description": "Provides the timestamp of the commit that was used for the build, for example `2021-02-23T08:08:02-05:00`.", + "type": "string" + }, + "rustc_version": { + "description": "Provides the rustc version that was used for the build, for example `1.52.0-nightly`.", + "type": "string" + } + }, + "additionalProperties": false + }, + "name_id": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "RegisteredName", + "type": "object", + "required": [ + "block_height", + "deposit", + "id", + "name", + "owner" + ], + "properties": { + "block_height": { + "description": "Block height at which the name was added.", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "deposit": { + "description": "The deposit used to announce the name.", + "allOf": [ + { + "$ref": "#/definitions/Coin" + } + ] + }, + "id": { + "description": "Unique id assigned to the registerd name.", + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "name": { + "description": "The registerd name details.", + "allOf": [ + { + "$ref": "#/definitions/NameDetails" + } + ] + }, + "owner": { + "description": "name owner.", + "allOf": [ + { + "$ref": "#/definitions/Addr" + } + ] + } + }, + "additionalProperties": false, + "definitions": { + "Addr": { + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + }, + "Address": { + "description": "String representation of a nym address, which is of the form client_id.client_enc@gateway_id. NOTE: entirely unvalidated.", + "oneOf": [ + { + "type": "object", + "required": [ + "nym_address" + ], + "properties": { + "nym_address": { + "type": "string" + } + }, + "additionalProperties": false + } + ] + }, + "Coin": { + "type": "object", + "required": [ + "amount", + "denom" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "denom": { + "type": "string" + } + } + }, + "NameDetails": { + "type": "object", + "required": [ + "address", + "identity_key", + "name" + ], + "properties": { + "address": { + "description": "The address of the name alias.", + "allOf": [ + { + "$ref": "#/definitions/Address" + } + ] + }, + "identity_key": { + "description": "The identity key of the registered name.", + "type": "string" + }, + "name": { + "description": "The name pointing to the nym address", + "allOf": [ + { + "$ref": "#/definitions/NymName" + } + ] + } + }, + "additionalProperties": false + }, + "NymName": { + "description": "Name stored and pointing a to a nym-address", + "type": "string" + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } + }, + "signing_nonce": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "uint32", + "type": "integer", + "format": "uint32", + "minimum": 0.0 + } + } +} diff --git a/contracts/name-service/schema/raw/execute.json b/contracts/name-service/schema/raw/execute.json new file mode 100644 index 0000000000..c902157b43 --- /dev/null +++ b/contracts/name-service/schema/raw/execute.json @@ -0,0 +1,181 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ExecuteMsg", + "oneOf": [ + { + "description": "Announcing a name pointing to a nym-address", + "type": "object", + "required": [ + "register" + ], + "properties": { + "register": { + "type": "object", + "required": [ + "name", + "owner_signature" + ], + "properties": { + "name": { + "$ref": "#/definitions/NameDetails" + }, + "owner_signature": { + "$ref": "#/definitions/MessageSignature" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Delete a name entry by id", + "type": "object", + "required": [ + "delete_id" + ], + "properties": { + "delete_id": { + "type": "object", + "required": [ + "name_id" + ], + "properties": { + "name_id": { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Delete a name entry by name", + "type": "object", + "required": [ + "delete_name" + ], + "properties": { + "delete_name": { + "type": "object", + "required": [ + "name" + ], + "properties": { + "name": { + "$ref": "#/definitions/NymName" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Change the deposit required for announcing a name", + "type": "object", + "required": [ + "update_deposit_required" + ], + "properties": { + "update_deposit_required": { + "type": "object", + "required": [ + "deposit_required" + ], + "properties": { + "deposit_required": { + "$ref": "#/definitions/Coin" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + ], + "definitions": { + "Address": { + "description": "String representation of a nym address, which is of the form client_id.client_enc@gateway_id. NOTE: entirely unvalidated.", + "oneOf": [ + { + "type": "object", + "required": [ + "nym_address" + ], + "properties": { + "nym_address": { + "type": "string" + } + }, + "additionalProperties": false + } + ] + }, + "Coin": { + "type": "object", + "required": [ + "amount", + "denom" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "denom": { + "type": "string" + } + } + }, + "MessageSignature": { + "type": "array", + "items": { + "type": "integer", + "format": "uint8", + "minimum": 0.0 + } + }, + "NameDetails": { + "type": "object", + "required": [ + "address", + "identity_key", + "name" + ], + "properties": { + "address": { + "description": "The address of the name alias.", + "allOf": [ + { + "$ref": "#/definitions/Address" + } + ] + }, + "identity_key": { + "description": "The identity key of the registered name.", + "type": "string" + }, + "name": { + "description": "The name pointing to the nym address", + "allOf": [ + { + "$ref": "#/definitions/NymName" + } + ] + } + }, + "additionalProperties": false + }, + "NymName": { + "description": "Name stored and pointing a to a nym-address", + "type": "string" + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } +} diff --git a/contracts/name-service/schema/raw/instantiate.json b/contracts/name-service/schema/raw/instantiate.json new file mode 100644 index 0000000000..73867fda63 --- /dev/null +++ b/contracts/name-service/schema/raw/instantiate.json @@ -0,0 +1,35 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "InstantiateMsg", + "type": "object", + "required": [ + "deposit_required" + ], + "properties": { + "deposit_required": { + "$ref": "#/definitions/Coin" + } + }, + "additionalProperties": false, + "definitions": { + "Coin": { + "type": "object", + "required": [ + "amount", + "denom" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "denom": { + "type": "string" + } + } + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } +} diff --git a/contracts/name-service/schema/raw/migrate.json b/contracts/name-service/schema/raw/migrate.json new file mode 100644 index 0000000000..7fbe8c5708 --- /dev/null +++ b/contracts/name-service/schema/raw/migrate.json @@ -0,0 +1,6 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "MigrateMsg", + "type": "object", + "additionalProperties": false +} diff --git a/contracts/name-service/schema/raw/query.json b/contracts/name-service/schema/raw/query.json new file mode 100644 index 0000000000..1c4efca8df --- /dev/null +++ b/contracts/name-service/schema/raw/query.json @@ -0,0 +1,210 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "QueryMsg", + "oneOf": [ + { + "description": "Query the name by it's assigned id", + "type": "object", + "required": [ + "name_id" + ], + "properties": { + "name_id": { + "type": "object", + "required": [ + "name_id" + ], + "properties": { + "name_id": { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Query the names by the registrator", + "type": "object", + "required": [ + "by_owner" + ], + "properties": { + "by_owner": { + "type": "object", + "required": [ + "owner" + ], + "properties": { + "owner": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "by_name" + ], + "properties": { + "by_name": { + "type": "object", + "required": [ + "name" + ], + "properties": { + "name": { + "$ref": "#/definitions/NymName" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "by_address" + ], + "properties": { + "by_address": { + "type": "object", + "required": [ + "address" + ], + "properties": { + "address": { + "$ref": "#/definitions/Address" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "all" + ], + "properties": { + "all": { + "type": "object", + "properties": { + "limit": { + "type": [ + "integer", + "null" + ], + "format": "uint32", + "minimum": 0.0 + }, + "start_after": { + "type": [ + "integer", + "null" + ], + "format": "uint32", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "signing_nonce" + ], + "properties": { + "signing_nonce": { + "type": "object", + "required": [ + "address" + ], + "properties": { + "address": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "config" + ], + "properties": { + "config": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Gets build information of this contract, such as the commit hash used for the build or rustc version.", + "type": "object", + "required": [ + "get_contract_version" + ], + "properties": { + "get_contract_version": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Gets the stored contract version information that's required by the CW2 spec interface for migrations.", + "type": "object", + "required": [ + "get_cw2_contract_version" + ], + "properties": { + "get_cw2_contract_version": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + } + ], + "definitions": { + "Address": { + "description": "String representation of a nym address, which is of the form client_id.client_enc@gateway_id. NOTE: entirely unvalidated.", + "oneOf": [ + { + "type": "object", + "required": [ + "nym_address" + ], + "properties": { + "nym_address": { + "type": "string" + } + }, + "additionalProperties": false + } + ] + }, + "NymName": { + "description": "Name stored and pointing a to a nym-address", + "type": "string" + } + } +} diff --git a/contracts/name-service/schema/raw/response_to_all.json b/contracts/name-service/schema/raw/response_to_all.json new file mode 100644 index 0000000000..5d4ab85d35 --- /dev/null +++ b/contracts/name-service/schema/raw/response_to_all.json @@ -0,0 +1,158 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "PagedNamesListResponse", + "type": "object", + "required": [ + "names", + "per_page" + ], + "properties": { + "names": { + "type": "array", + "items": { + "$ref": "#/definitions/RegisteredName" + } + }, + "per_page": { + "type": "integer", + "format": "uint", + "minimum": 0.0 + }, + "start_next_after": { + "description": "Field indicating paging information for the following queries if the caller wishes to get further entries.", + "type": [ + "integer", + "null" + ], + "format": "uint32", + "minimum": 0.0 + } + }, + "additionalProperties": false, + "definitions": { + "Addr": { + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + }, + "Address": { + "description": "String representation of a nym address, which is of the form client_id.client_enc@gateway_id. NOTE: entirely unvalidated.", + "oneOf": [ + { + "type": "object", + "required": [ + "nym_address" + ], + "properties": { + "nym_address": { + "type": "string" + } + }, + "additionalProperties": false + } + ] + }, + "Coin": { + "type": "object", + "required": [ + "amount", + "denom" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "denom": { + "type": "string" + } + } + }, + "NameDetails": { + "type": "object", + "required": [ + "address", + "identity_key", + "name" + ], + "properties": { + "address": { + "description": "The address of the name alias.", + "allOf": [ + { + "$ref": "#/definitions/Address" + } + ] + }, + "identity_key": { + "description": "The identity key of the registered name.", + "type": "string" + }, + "name": { + "description": "The name pointing to the nym address", + "allOf": [ + { + "$ref": "#/definitions/NymName" + } + ] + } + }, + "additionalProperties": false + }, + "NymName": { + "description": "Name stored and pointing a to a nym-address", + "type": "string" + }, + "RegisteredName": { + "type": "object", + "required": [ + "block_height", + "deposit", + "id", + "name", + "owner" + ], + "properties": { + "block_height": { + "description": "Block height at which the name was added.", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "deposit": { + "description": "The deposit used to announce the name.", + "allOf": [ + { + "$ref": "#/definitions/Coin" + } + ] + }, + "id": { + "description": "Unique id assigned to the registerd name.", + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "name": { + "description": "The registerd name details.", + "allOf": [ + { + "$ref": "#/definitions/NameDetails" + } + ] + }, + "owner": { + "description": "name owner.", + "allOf": [ + { + "$ref": "#/definitions/Addr" + } + ] + } + }, + "additionalProperties": false + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } +} diff --git a/contracts/name-service/schema/raw/response_to_by_address.json b/contracts/name-service/schema/raw/response_to_by_address.json new file mode 100644 index 0000000000..0e44754b87 --- /dev/null +++ b/contracts/name-service/schema/raw/response_to_by_address.json @@ -0,0 +1,143 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "NamesListResponse", + "type": "object", + "required": [ + "names" + ], + "properties": { + "names": { + "type": "array", + "items": { + "$ref": "#/definitions/RegisteredName" + } + } + }, + "additionalProperties": false, + "definitions": { + "Addr": { + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + }, + "Address": { + "description": "String representation of a nym address, which is of the form client_id.client_enc@gateway_id. NOTE: entirely unvalidated.", + "oneOf": [ + { + "type": "object", + "required": [ + "nym_address" + ], + "properties": { + "nym_address": { + "type": "string" + } + }, + "additionalProperties": false + } + ] + }, + "Coin": { + "type": "object", + "required": [ + "amount", + "denom" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "denom": { + "type": "string" + } + } + }, + "NameDetails": { + "type": "object", + "required": [ + "address", + "identity_key", + "name" + ], + "properties": { + "address": { + "description": "The address of the name alias.", + "allOf": [ + { + "$ref": "#/definitions/Address" + } + ] + }, + "identity_key": { + "description": "The identity key of the registered name.", + "type": "string" + }, + "name": { + "description": "The name pointing to the nym address", + "allOf": [ + { + "$ref": "#/definitions/NymName" + } + ] + } + }, + "additionalProperties": false + }, + "NymName": { + "description": "Name stored and pointing a to a nym-address", + "type": "string" + }, + "RegisteredName": { + "type": "object", + "required": [ + "block_height", + "deposit", + "id", + "name", + "owner" + ], + "properties": { + "block_height": { + "description": "Block height at which the name was added.", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "deposit": { + "description": "The deposit used to announce the name.", + "allOf": [ + { + "$ref": "#/definitions/Coin" + } + ] + }, + "id": { + "description": "Unique id assigned to the registerd name.", + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "name": { + "description": "The registerd name details.", + "allOf": [ + { + "$ref": "#/definitions/NameDetails" + } + ] + }, + "owner": { + "description": "name owner.", + "allOf": [ + { + "$ref": "#/definitions/Addr" + } + ] + } + }, + "additionalProperties": false + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } +} diff --git a/contracts/name-service/schema/raw/response_to_by_name.json b/contracts/name-service/schema/raw/response_to_by_name.json new file mode 100644 index 0000000000..4dfc8752ee --- /dev/null +++ b/contracts/name-service/schema/raw/response_to_by_name.json @@ -0,0 +1,128 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "RegisteredName", + "type": "object", + "required": [ + "block_height", + "deposit", + "id", + "name", + "owner" + ], + "properties": { + "block_height": { + "description": "Block height at which the name was added.", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "deposit": { + "description": "The deposit used to announce the name.", + "allOf": [ + { + "$ref": "#/definitions/Coin" + } + ] + }, + "id": { + "description": "Unique id assigned to the registerd name.", + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "name": { + "description": "The registerd name details.", + "allOf": [ + { + "$ref": "#/definitions/NameDetails" + } + ] + }, + "owner": { + "description": "name owner.", + "allOf": [ + { + "$ref": "#/definitions/Addr" + } + ] + } + }, + "additionalProperties": false, + "definitions": { + "Addr": { + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + }, + "Address": { + "description": "String representation of a nym address, which is of the form client_id.client_enc@gateway_id. NOTE: entirely unvalidated.", + "oneOf": [ + { + "type": "object", + "required": [ + "nym_address" + ], + "properties": { + "nym_address": { + "type": "string" + } + }, + "additionalProperties": false + } + ] + }, + "Coin": { + "type": "object", + "required": [ + "amount", + "denom" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "denom": { + "type": "string" + } + } + }, + "NameDetails": { + "type": "object", + "required": [ + "address", + "identity_key", + "name" + ], + "properties": { + "address": { + "description": "The address of the name alias.", + "allOf": [ + { + "$ref": "#/definitions/Address" + } + ] + }, + "identity_key": { + "description": "The identity key of the registered name.", + "type": "string" + }, + "name": { + "description": "The name pointing to the nym address", + "allOf": [ + { + "$ref": "#/definitions/NymName" + } + ] + } + }, + "additionalProperties": false + }, + "NymName": { + "description": "Name stored and pointing a to a nym-address", + "type": "string" + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } +} diff --git a/contracts/name-service/schema/raw/response_to_by_owner.json b/contracts/name-service/schema/raw/response_to_by_owner.json new file mode 100644 index 0000000000..0e44754b87 --- /dev/null +++ b/contracts/name-service/schema/raw/response_to_by_owner.json @@ -0,0 +1,143 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "NamesListResponse", + "type": "object", + "required": [ + "names" + ], + "properties": { + "names": { + "type": "array", + "items": { + "$ref": "#/definitions/RegisteredName" + } + } + }, + "additionalProperties": false, + "definitions": { + "Addr": { + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + }, + "Address": { + "description": "String representation of a nym address, which is of the form client_id.client_enc@gateway_id. NOTE: entirely unvalidated.", + "oneOf": [ + { + "type": "object", + "required": [ + "nym_address" + ], + "properties": { + "nym_address": { + "type": "string" + } + }, + "additionalProperties": false + } + ] + }, + "Coin": { + "type": "object", + "required": [ + "amount", + "denom" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "denom": { + "type": "string" + } + } + }, + "NameDetails": { + "type": "object", + "required": [ + "address", + "identity_key", + "name" + ], + "properties": { + "address": { + "description": "The address of the name alias.", + "allOf": [ + { + "$ref": "#/definitions/Address" + } + ] + }, + "identity_key": { + "description": "The identity key of the registered name.", + "type": "string" + }, + "name": { + "description": "The name pointing to the nym address", + "allOf": [ + { + "$ref": "#/definitions/NymName" + } + ] + } + }, + "additionalProperties": false + }, + "NymName": { + "description": "Name stored and pointing a to a nym-address", + "type": "string" + }, + "RegisteredName": { + "type": "object", + "required": [ + "block_height", + "deposit", + "id", + "name", + "owner" + ], + "properties": { + "block_height": { + "description": "Block height at which the name was added.", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "deposit": { + "description": "The deposit used to announce the name.", + "allOf": [ + { + "$ref": "#/definitions/Coin" + } + ] + }, + "id": { + "description": "Unique id assigned to the registerd name.", + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "name": { + "description": "The registerd name details.", + "allOf": [ + { + "$ref": "#/definitions/NameDetails" + } + ] + }, + "owner": { + "description": "name owner.", + "allOf": [ + { + "$ref": "#/definitions/Addr" + } + ] + } + }, + "additionalProperties": false + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } +} diff --git a/contracts/name-service/schema/raw/response_to_config.json b/contracts/name-service/schema/raw/response_to_config.json new file mode 100644 index 0000000000..16420c1130 --- /dev/null +++ b/contracts/name-service/schema/raw/response_to_config.json @@ -0,0 +1,35 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ConfigResponse", + "type": "object", + "required": [ + "deposit_required" + ], + "properties": { + "deposit_required": { + "$ref": "#/definitions/Coin" + } + }, + "additionalProperties": false, + "definitions": { + "Coin": { + "type": "object", + "required": [ + "amount", + "denom" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "denom": { + "type": "string" + } + } + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } +} diff --git a/contracts/name-service/schema/raw/response_to_get_c_w2_contract_version.json b/contracts/name-service/schema/raw/response_to_get_c_w2_contract_version.json new file mode 100644 index 0000000000..3ded8c84d9 --- /dev/null +++ b/contracts/name-service/schema/raw/response_to_get_c_w2_contract_version.json @@ -0,0 +1,20 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ContractVersion", + "type": "object", + "required": [ + "contract", + "version" + ], + "properties": { + "contract": { + "description": "contract is the crate name of the implementing contract, eg. `crate:cw20-base` we will use other prefixes for other languages, and their standard global namespacing", + "type": "string" + }, + "version": { + "description": "version is any string that this implementation knows. It may be simple counter \"1\", \"2\". or semantic version on release tags \"v0.7.0\", or some custom feature flag list. the only code that needs to understand the version parsing is code that knows how to migrate from the given contract (and is tied to it's implementation somehow)", + "type": "string" + } + }, + "additionalProperties": false +} diff --git a/contracts/name-service/schema/raw/response_to_get_contract_version.json b/contracts/name-service/schema/raw/response_to_get_contract_version.json new file mode 100644 index 0000000000..60ae65f3d1 --- /dev/null +++ b/contracts/name-service/schema/raw/response_to_get_contract_version.json @@ -0,0 +1,40 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ContractBuildInformation", + "type": "object", + "required": [ + "build_timestamp", + "build_version", + "commit_branch", + "commit_sha", + "commit_timestamp", + "rustc_version" + ], + "properties": { + "build_timestamp": { + "description": "Provides the build timestamp, for example `2021-02-23T20:14:46.558472672+00:00`.", + "type": "string" + }, + "build_version": { + "description": "Provides the build version, for example `0.1.0-9-g46f83e1`.", + "type": "string" + }, + "commit_branch": { + "description": "Provides the name of the git branch that was used for the build, for example `master`.", + "type": "string" + }, + "commit_sha": { + "description": "Provides the hash of the commit that was used for the build, for example `46f83e112520533338245862d366f6a02cef07d4`.", + "type": "string" + }, + "commit_timestamp": { + "description": "Provides the timestamp of the commit that was used for the build, for example `2021-02-23T08:08:02-05:00`.", + "type": "string" + }, + "rustc_version": { + "description": "Provides the rustc version that was used for the build, for example `1.52.0-nightly`.", + "type": "string" + } + }, + "additionalProperties": false +} diff --git a/contracts/name-service/schema/raw/response_to_name_id.json b/contracts/name-service/schema/raw/response_to_name_id.json new file mode 100644 index 0000000000..4dfc8752ee --- /dev/null +++ b/contracts/name-service/schema/raw/response_to_name_id.json @@ -0,0 +1,128 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "RegisteredName", + "type": "object", + "required": [ + "block_height", + "deposit", + "id", + "name", + "owner" + ], + "properties": { + "block_height": { + "description": "Block height at which the name was added.", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "deposit": { + "description": "The deposit used to announce the name.", + "allOf": [ + { + "$ref": "#/definitions/Coin" + } + ] + }, + "id": { + "description": "Unique id assigned to the registerd name.", + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "name": { + "description": "The registerd name details.", + "allOf": [ + { + "$ref": "#/definitions/NameDetails" + } + ] + }, + "owner": { + "description": "name owner.", + "allOf": [ + { + "$ref": "#/definitions/Addr" + } + ] + } + }, + "additionalProperties": false, + "definitions": { + "Addr": { + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + }, + "Address": { + "description": "String representation of a nym address, which is of the form client_id.client_enc@gateway_id. NOTE: entirely unvalidated.", + "oneOf": [ + { + "type": "object", + "required": [ + "nym_address" + ], + "properties": { + "nym_address": { + "type": "string" + } + }, + "additionalProperties": false + } + ] + }, + "Coin": { + "type": "object", + "required": [ + "amount", + "denom" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "denom": { + "type": "string" + } + } + }, + "NameDetails": { + "type": "object", + "required": [ + "address", + "identity_key", + "name" + ], + "properties": { + "address": { + "description": "The address of the name alias.", + "allOf": [ + { + "$ref": "#/definitions/Address" + } + ] + }, + "identity_key": { + "description": "The identity key of the registered name.", + "type": "string" + }, + "name": { + "description": "The name pointing to the nym address", + "allOf": [ + { + "$ref": "#/definitions/NymName" + } + ] + } + }, + "additionalProperties": false + }, + "NymName": { + "description": "Name stored and pointing a to a nym-address", + "type": "string" + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } +} diff --git a/contracts/name-service/schema/raw/response_to_signing_nonce.json b/contracts/name-service/schema/raw/response_to_signing_nonce.json new file mode 100644 index 0000000000..de85c3e56e --- /dev/null +++ b/contracts/name-service/schema/raw/response_to_signing_nonce.json @@ -0,0 +1,7 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "uint32", + "type": "integer", + "format": "uint32", + "minimum": 0.0 +} diff --git a/contracts/name-service/src/bin/schema.rs b/contracts/name-service/src/bin/schema.rs new file mode 100644 index 0000000000..92f8f3f6ab --- /dev/null +++ b/contracts/name-service/src/bin/schema.rs @@ -0,0 +1,14 @@ +// Copyright 2023 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use cosmwasm_schema::write_api; +use nym_name_service_common::msg::{ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg}; + +fn main() { + write_api! { + instantiate: InstantiateMsg, + query: QueryMsg, + execute: ExecuteMsg, + migrate: MigrateMsg, + } +} diff --git a/contracts/name-service/src/state/names.rs b/contracts/name-service/src/state/names.rs index 3b2b02f82d..41c68bf714 100644 --- a/contracts/name-service/src/state/names.rs +++ b/contracts/name-service/src/state/names.rs @@ -120,6 +120,8 @@ pub fn load_owner(store: &dyn Storage, owner: Addr) -> Result, pub limit: usize, + + /// Field indicating paging information for the following queries if the caller wishes to get further entries. pub start_next_after: Option, } diff --git a/contracts/service-provider-directory/.cargo/config b/contracts/service-provider-directory/.cargo/config index 4fa7f37d4a..2f698e22b3 100644 --- a/contracts/service-provider-directory/.cargo/config +++ b/contracts/service-provider-directory/.cargo/config @@ -2,4 +2,4 @@ wasm = "build --release --lib --target wasm32-unknown-unknown" wasm-debug = "build --target wasm32-unknown-unknown" unit-test = "test --lib" -schema = "run --bin schema" +schema = "run --bin schema --features=schema-gen" \ No newline at end of file diff --git a/contracts/service-provider-directory/Cargo.toml b/contracts/service-provider-directory/Cargo.toml index cadc17d708..39ffcaf6f7 100644 --- a/contracts/service-provider-directory/Cargo.toml +++ b/contracts/service-provider-directory/Cargo.toml @@ -3,11 +3,16 @@ name = "nym-service-provider-directory" version = "0.1.0" edition = "2021" +[[bin]] +name = "schema" +required-features = ["schema-gen"] + [lib] crate-type = ["cdylib", "rlib"] [dependencies] bs58 = "0.4.0" +cosmwasm-schema = { workspace = true, optional = true } cosmwasm-std = { workspace = true } cw-controllers = { workspace = true } cw-storage-plus = { workspace = true } @@ -28,3 +33,6 @@ cw-multi-test = { workspace = true } nym-crypto = { path = "../../common/crypto", features = ["asymmetric", "rand"] } rand_chacha = "0.2" rstest = "0.17.0" + +[features] +schema-gen = ["nym-service-provider-directory-common/schema", "cosmwasm-schema"] diff --git a/contracts/service-provider-directory/Makefile b/contracts/service-provider-directory/Makefile new file mode 100644 index 0000000000..f138e2c879 --- /dev/null +++ b/contracts/service-provider-directory/Makefile @@ -0,0 +1,2 @@ +generate-schema: + cargo schema \ No newline at end of file diff --git a/contracts/service-provider-directory/schema/nym-service-provider-directory.json b/contracts/service-provider-directory/schema/nym-service-provider-directory.json new file mode 100644 index 0000000000..d923ef3f99 --- /dev/null +++ b/contracts/service-provider-directory/schema/nym-service-provider-directory.json @@ -0,0 +1,1104 @@ +{ + "contract_name": "nym-service-provider-directory", + "contract_version": "0.1.0", + "idl_version": "1.0.0", + "instantiate": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "InstantiateMsg", + "type": "object", + "required": [ + "deposit_required" + ], + "properties": { + "deposit_required": { + "$ref": "#/definitions/Coin" + } + }, + "additionalProperties": false, + "definitions": { + "Coin": { + "type": "object", + "required": [ + "amount", + "denom" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "denom": { + "type": "string" + } + } + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } + }, + "execute": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ExecuteMsg", + "oneOf": [ + { + "type": "object", + "required": [ + "announce" + ], + "properties": { + "announce": { + "type": "object", + "required": [ + "owner_signature", + "service" + ], + "properties": { + "owner_signature": { + "$ref": "#/definitions/MessageSignature" + }, + "service": { + "$ref": "#/definitions/ServiceDetails" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "delete_id" + ], + "properties": { + "delete_id": { + "type": "object", + "required": [ + "service_id" + ], + "properties": { + "service_id": { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "delete_nym_address" + ], + "properties": { + "delete_nym_address": { + "type": "object", + "required": [ + "nym_address" + ], + "properties": { + "nym_address": { + "$ref": "#/definitions/NymAddress" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "update_deposit_required" + ], + "properties": { + "update_deposit_required": { + "type": "object", + "required": [ + "deposit_required" + ], + "properties": { + "deposit_required": { + "$ref": "#/definitions/Coin" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + ], + "definitions": { + "Coin": { + "type": "object", + "required": [ + "amount", + "denom" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "denom": { + "type": "string" + } + } + }, + "MessageSignature": { + "type": "array", + "items": { + "type": "integer", + "format": "uint8", + "minimum": 0.0 + } + }, + "NymAddress": { + "description": "The types of addresses supported.", + "oneOf": [ + { + "description": "String representation of a nym address, which is of the form client_id.client_enc@gateway_id.", + "type": "object", + "required": [ + "address" + ], + "properties": { + "address": { + "type": "string" + } + }, + "additionalProperties": false + } + ] + }, + "ServiceDetails": { + "type": "object", + "required": [ + "identity_key", + "nym_address", + "service_type" + ], + "properties": { + "identity_key": { + "description": "The identity key of the service.", + "type": "string" + }, + "nym_address": { + "description": "The address of the service.", + "allOf": [ + { + "$ref": "#/definitions/NymAddress" + } + ] + }, + "service_type": { + "description": "The service type.", + "allOf": [ + { + "$ref": "#/definitions/ServiceType" + } + ] + } + }, + "additionalProperties": false + }, + "ServiceType": { + "description": "The type of services provider supported", + "type": "string", + "enum": [ + "network_requester" + ] + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } + }, + "query": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "QueryMsg", + "oneOf": [ + { + "type": "object", + "required": [ + "service_id" + ], + "properties": { + "service_id": { + "type": "object", + "required": [ + "service_id" + ], + "properties": { + "service_id": { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "by_announcer" + ], + "properties": { + "by_announcer": { + "type": "object", + "required": [ + "announcer" + ], + "properties": { + "announcer": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "by_nym_address" + ], + "properties": { + "by_nym_address": { + "type": "object", + "required": [ + "nym_address" + ], + "properties": { + "nym_address": { + "$ref": "#/definitions/NymAddress" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "all" + ], + "properties": { + "all": { + "type": "object", + "properties": { + "limit": { + "type": [ + "integer", + "null" + ], + "format": "uint32", + "minimum": 0.0 + }, + "start_after": { + "type": [ + "integer", + "null" + ], + "format": "uint32", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "signing_nonce" + ], + "properties": { + "signing_nonce": { + "type": "object", + "required": [ + "address" + ], + "properties": { + "address": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "config" + ], + "properties": { + "config": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Gets build information of this contract, such as the commit hash used for the build or rustc version.", + "type": "object", + "required": [ + "get_contract_version" + ], + "properties": { + "get_contract_version": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Gets the stored contract version information that's required by the CW2 spec interface for migrations.", + "type": "object", + "required": [ + "get_cw2_contract_version" + ], + "properties": { + "get_cw2_contract_version": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + } + ], + "definitions": { + "NymAddress": { + "description": "The types of addresses supported.", + "oneOf": [ + { + "description": "String representation of a nym address, which is of the form client_id.client_enc@gateway_id.", + "type": "object", + "required": [ + "address" + ], + "properties": { + "address": { + "type": "string" + } + }, + "additionalProperties": false + } + ] + } + } + }, + "migrate": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "MigrateMsg", + "type": "object", + "additionalProperties": false + }, + "sudo": null, + "responses": { + "all": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "PagedServicesListResponse", + "type": "object", + "required": [ + "per_page", + "services" + ], + "properties": { + "per_page": { + "type": "integer", + "format": "uint", + "minimum": 0.0 + }, + "services": { + "type": "array", + "items": { + "$ref": "#/definitions/Service" + } + }, + "start_next_after": { + "description": "Field indicating paging information for the following queries if the caller wishes to get further entries.", + "type": [ + "integer", + "null" + ], + "format": "uint32", + "minimum": 0.0 + } + }, + "additionalProperties": false, + "definitions": { + "Addr": { + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + }, + "Coin": { + "type": "object", + "required": [ + "amount", + "denom" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "denom": { + "type": "string" + } + } + }, + "NymAddress": { + "description": "The types of addresses supported.", + "oneOf": [ + { + "description": "String representation of a nym address, which is of the form client_id.client_enc@gateway_id.", + "type": "object", + "required": [ + "address" + ], + "properties": { + "address": { + "type": "string" + } + }, + "additionalProperties": false + } + ] + }, + "Service": { + "type": "object", + "required": [ + "announcer", + "block_height", + "deposit", + "service", + "service_id" + ], + "properties": { + "announcer": { + "description": "Address of the service owner.", + "allOf": [ + { + "$ref": "#/definitions/Addr" + } + ] + }, + "block_height": { + "description": "Block height at which the service was added.", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "deposit": { + "description": "The deposit used to announce the service.", + "allOf": [ + { + "$ref": "#/definitions/Coin" + } + ] + }, + "service": { + "description": "The announced service.", + "allOf": [ + { + "$ref": "#/definitions/ServiceDetails" + } + ] + }, + "service_id": { + "description": "Unique id assigned to the anounced service.", + "type": "integer", + "format": "uint32", + "minimum": 0.0 + } + }, + "additionalProperties": false + }, + "ServiceDetails": { + "type": "object", + "required": [ + "identity_key", + "nym_address", + "service_type" + ], + "properties": { + "identity_key": { + "description": "The identity key of the service.", + "type": "string" + }, + "nym_address": { + "description": "The address of the service.", + "allOf": [ + { + "$ref": "#/definitions/NymAddress" + } + ] + }, + "service_type": { + "description": "The service type.", + "allOf": [ + { + "$ref": "#/definitions/ServiceType" + } + ] + } + }, + "additionalProperties": false + }, + "ServiceType": { + "description": "The type of services provider supported", + "type": "string", + "enum": [ + "network_requester" + ] + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } + }, + "by_announcer": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ServicesListResponse", + "type": "object", + "required": [ + "services" + ], + "properties": { + "services": { + "type": "array", + "items": { + "$ref": "#/definitions/Service" + } + } + }, + "additionalProperties": false, + "definitions": { + "Addr": { + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + }, + "Coin": { + "type": "object", + "required": [ + "amount", + "denom" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "denom": { + "type": "string" + } + } + }, + "NymAddress": { + "description": "The types of addresses supported.", + "oneOf": [ + { + "description": "String representation of a nym address, which is of the form client_id.client_enc@gateway_id.", + "type": "object", + "required": [ + "address" + ], + "properties": { + "address": { + "type": "string" + } + }, + "additionalProperties": false + } + ] + }, + "Service": { + "type": "object", + "required": [ + "announcer", + "block_height", + "deposit", + "service", + "service_id" + ], + "properties": { + "announcer": { + "description": "Address of the service owner.", + "allOf": [ + { + "$ref": "#/definitions/Addr" + } + ] + }, + "block_height": { + "description": "Block height at which the service was added.", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "deposit": { + "description": "The deposit used to announce the service.", + "allOf": [ + { + "$ref": "#/definitions/Coin" + } + ] + }, + "service": { + "description": "The announced service.", + "allOf": [ + { + "$ref": "#/definitions/ServiceDetails" + } + ] + }, + "service_id": { + "description": "Unique id assigned to the anounced service.", + "type": "integer", + "format": "uint32", + "minimum": 0.0 + } + }, + "additionalProperties": false + }, + "ServiceDetails": { + "type": "object", + "required": [ + "identity_key", + "nym_address", + "service_type" + ], + "properties": { + "identity_key": { + "description": "The identity key of the service.", + "type": "string" + }, + "nym_address": { + "description": "The address of the service.", + "allOf": [ + { + "$ref": "#/definitions/NymAddress" + } + ] + }, + "service_type": { + "description": "The service type.", + "allOf": [ + { + "$ref": "#/definitions/ServiceType" + } + ] + } + }, + "additionalProperties": false + }, + "ServiceType": { + "description": "The type of services provider supported", + "type": "string", + "enum": [ + "network_requester" + ] + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } + }, + "by_nym_address": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ServicesListResponse", + "type": "object", + "required": [ + "services" + ], + "properties": { + "services": { + "type": "array", + "items": { + "$ref": "#/definitions/Service" + } + } + }, + "additionalProperties": false, + "definitions": { + "Addr": { + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + }, + "Coin": { + "type": "object", + "required": [ + "amount", + "denom" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "denom": { + "type": "string" + } + } + }, + "NymAddress": { + "description": "The types of addresses supported.", + "oneOf": [ + { + "description": "String representation of a nym address, which is of the form client_id.client_enc@gateway_id.", + "type": "object", + "required": [ + "address" + ], + "properties": { + "address": { + "type": "string" + } + }, + "additionalProperties": false + } + ] + }, + "Service": { + "type": "object", + "required": [ + "announcer", + "block_height", + "deposit", + "service", + "service_id" + ], + "properties": { + "announcer": { + "description": "Address of the service owner.", + "allOf": [ + { + "$ref": "#/definitions/Addr" + } + ] + }, + "block_height": { + "description": "Block height at which the service was added.", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "deposit": { + "description": "The deposit used to announce the service.", + "allOf": [ + { + "$ref": "#/definitions/Coin" + } + ] + }, + "service": { + "description": "The announced service.", + "allOf": [ + { + "$ref": "#/definitions/ServiceDetails" + } + ] + }, + "service_id": { + "description": "Unique id assigned to the anounced service.", + "type": "integer", + "format": "uint32", + "minimum": 0.0 + } + }, + "additionalProperties": false + }, + "ServiceDetails": { + "type": "object", + "required": [ + "identity_key", + "nym_address", + "service_type" + ], + "properties": { + "identity_key": { + "description": "The identity key of the service.", + "type": "string" + }, + "nym_address": { + "description": "The address of the service.", + "allOf": [ + { + "$ref": "#/definitions/NymAddress" + } + ] + }, + "service_type": { + "description": "The service type.", + "allOf": [ + { + "$ref": "#/definitions/ServiceType" + } + ] + } + }, + "additionalProperties": false + }, + "ServiceType": { + "description": "The type of services provider supported", + "type": "string", + "enum": [ + "network_requester" + ] + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } + }, + "config": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ConfigResponse", + "type": "object", + "required": [ + "deposit_required" + ], + "properties": { + "deposit_required": { + "$ref": "#/definitions/Coin" + } + }, + "additionalProperties": false, + "definitions": { + "Coin": { + "type": "object", + "required": [ + "amount", + "denom" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "denom": { + "type": "string" + } + } + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } + }, + "get_c_w2_contract_version": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ContractVersion", + "type": "object", + "required": [ + "contract", + "version" + ], + "properties": { + "contract": { + "description": "contract is the crate name of the implementing contract, eg. `crate:cw20-base` we will use other prefixes for other languages, and their standard global namespacing", + "type": "string" + }, + "version": { + "description": "version is any string that this implementation knows. It may be simple counter \"1\", \"2\". or semantic version on release tags \"v0.7.0\", or some custom feature flag list. the only code that needs to understand the version parsing is code that knows how to migrate from the given contract (and is tied to it's implementation somehow)", + "type": "string" + } + }, + "additionalProperties": false + }, + "get_contract_version": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ContractBuildInformation", + "type": "object", + "required": [ + "build_timestamp", + "build_version", + "commit_branch", + "commit_sha", + "commit_timestamp", + "rustc_version" + ], + "properties": { + "build_timestamp": { + "description": "Provides the build timestamp, for example `2021-02-23T20:14:46.558472672+00:00`.", + "type": "string" + }, + "build_version": { + "description": "Provides the build version, for example `0.1.0-9-g46f83e1`.", + "type": "string" + }, + "commit_branch": { + "description": "Provides the name of the git branch that was used for the build, for example `master`.", + "type": "string" + }, + "commit_sha": { + "description": "Provides the hash of the commit that was used for the build, for example `46f83e112520533338245862d366f6a02cef07d4`.", + "type": "string" + }, + "commit_timestamp": { + "description": "Provides the timestamp of the commit that was used for the build, for example `2021-02-23T08:08:02-05:00`.", + "type": "string" + }, + "rustc_version": { + "description": "Provides the rustc version that was used for the build, for example `1.52.0-nightly`.", + "type": "string" + } + }, + "additionalProperties": false + }, + "service_id": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Service", + "type": "object", + "required": [ + "announcer", + "block_height", + "deposit", + "service", + "service_id" + ], + "properties": { + "announcer": { + "description": "Address of the service owner.", + "allOf": [ + { + "$ref": "#/definitions/Addr" + } + ] + }, + "block_height": { + "description": "Block height at which the service was added.", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "deposit": { + "description": "The deposit used to announce the service.", + "allOf": [ + { + "$ref": "#/definitions/Coin" + } + ] + }, + "service": { + "description": "The announced service.", + "allOf": [ + { + "$ref": "#/definitions/ServiceDetails" + } + ] + }, + "service_id": { + "description": "Unique id assigned to the anounced service.", + "type": "integer", + "format": "uint32", + "minimum": 0.0 + } + }, + "additionalProperties": false, + "definitions": { + "Addr": { + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + }, + "Coin": { + "type": "object", + "required": [ + "amount", + "denom" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "denom": { + "type": "string" + } + } + }, + "NymAddress": { + "description": "The types of addresses supported.", + "oneOf": [ + { + "description": "String representation of a nym address, which is of the form client_id.client_enc@gateway_id.", + "type": "object", + "required": [ + "address" + ], + "properties": { + "address": { + "type": "string" + } + }, + "additionalProperties": false + } + ] + }, + "ServiceDetails": { + "type": "object", + "required": [ + "identity_key", + "nym_address", + "service_type" + ], + "properties": { + "identity_key": { + "description": "The identity key of the service.", + "type": "string" + }, + "nym_address": { + "description": "The address of the service.", + "allOf": [ + { + "$ref": "#/definitions/NymAddress" + } + ] + }, + "service_type": { + "description": "The service type.", + "allOf": [ + { + "$ref": "#/definitions/ServiceType" + } + ] + } + }, + "additionalProperties": false + }, + "ServiceType": { + "description": "The type of services provider supported", + "type": "string", + "enum": [ + "network_requester" + ] + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } + }, + "signing_nonce": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "uint32", + "type": "integer", + "format": "uint32", + "minimum": 0.0 + } + } +} diff --git a/contracts/service-provider-directory/schema/raw/execute.json b/contracts/service-provider-directory/schema/raw/execute.json new file mode 100644 index 0000000000..995ede2a3a --- /dev/null +++ b/contracts/service-provider-directory/schema/raw/execute.json @@ -0,0 +1,181 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ExecuteMsg", + "oneOf": [ + { + "type": "object", + "required": [ + "announce" + ], + "properties": { + "announce": { + "type": "object", + "required": [ + "owner_signature", + "service" + ], + "properties": { + "owner_signature": { + "$ref": "#/definitions/MessageSignature" + }, + "service": { + "$ref": "#/definitions/ServiceDetails" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "delete_id" + ], + "properties": { + "delete_id": { + "type": "object", + "required": [ + "service_id" + ], + "properties": { + "service_id": { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "delete_nym_address" + ], + "properties": { + "delete_nym_address": { + "type": "object", + "required": [ + "nym_address" + ], + "properties": { + "nym_address": { + "$ref": "#/definitions/NymAddress" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "update_deposit_required" + ], + "properties": { + "update_deposit_required": { + "type": "object", + "required": [ + "deposit_required" + ], + "properties": { + "deposit_required": { + "$ref": "#/definitions/Coin" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + ], + "definitions": { + "Coin": { + "type": "object", + "required": [ + "amount", + "denom" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "denom": { + "type": "string" + } + } + }, + "MessageSignature": { + "type": "array", + "items": { + "type": "integer", + "format": "uint8", + "minimum": 0.0 + } + }, + "NymAddress": { + "description": "The types of addresses supported.", + "oneOf": [ + { + "description": "String representation of a nym address, which is of the form client_id.client_enc@gateway_id.", + "type": "object", + "required": [ + "address" + ], + "properties": { + "address": { + "type": "string" + } + }, + "additionalProperties": false + } + ] + }, + "ServiceDetails": { + "type": "object", + "required": [ + "identity_key", + "nym_address", + "service_type" + ], + "properties": { + "identity_key": { + "description": "The identity key of the service.", + "type": "string" + }, + "nym_address": { + "description": "The address of the service.", + "allOf": [ + { + "$ref": "#/definitions/NymAddress" + } + ] + }, + "service_type": { + "description": "The service type.", + "allOf": [ + { + "$ref": "#/definitions/ServiceType" + } + ] + } + }, + "additionalProperties": false + }, + "ServiceType": { + "description": "The type of services provider supported", + "type": "string", + "enum": [ + "network_requester" + ] + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } +} diff --git a/contracts/service-provider-directory/schema/raw/instantiate.json b/contracts/service-provider-directory/schema/raw/instantiate.json new file mode 100644 index 0000000000..73867fda63 --- /dev/null +++ b/contracts/service-provider-directory/schema/raw/instantiate.json @@ -0,0 +1,35 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "InstantiateMsg", + "type": "object", + "required": [ + "deposit_required" + ], + "properties": { + "deposit_required": { + "$ref": "#/definitions/Coin" + } + }, + "additionalProperties": false, + "definitions": { + "Coin": { + "type": "object", + "required": [ + "amount", + "denom" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "denom": { + "type": "string" + } + } + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } +} diff --git a/contracts/service-provider-directory/schema/raw/migrate.json b/contracts/service-provider-directory/schema/raw/migrate.json new file mode 100644 index 0000000000..7fbe8c5708 --- /dev/null +++ b/contracts/service-provider-directory/schema/raw/migrate.json @@ -0,0 +1,6 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "MigrateMsg", + "type": "object", + "additionalProperties": false +} diff --git a/contracts/service-provider-directory/schema/raw/query.json b/contracts/service-provider-directory/schema/raw/query.json new file mode 100644 index 0000000000..f6abe3d45e --- /dev/null +++ b/contracts/service-provider-directory/schema/raw/query.json @@ -0,0 +1,184 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "QueryMsg", + "oneOf": [ + { + "type": "object", + "required": [ + "service_id" + ], + "properties": { + "service_id": { + "type": "object", + "required": [ + "service_id" + ], + "properties": { + "service_id": { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "by_announcer" + ], + "properties": { + "by_announcer": { + "type": "object", + "required": [ + "announcer" + ], + "properties": { + "announcer": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "by_nym_address" + ], + "properties": { + "by_nym_address": { + "type": "object", + "required": [ + "nym_address" + ], + "properties": { + "nym_address": { + "$ref": "#/definitions/NymAddress" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "all" + ], + "properties": { + "all": { + "type": "object", + "properties": { + "limit": { + "type": [ + "integer", + "null" + ], + "format": "uint32", + "minimum": 0.0 + }, + "start_after": { + "type": [ + "integer", + "null" + ], + "format": "uint32", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "signing_nonce" + ], + "properties": { + "signing_nonce": { + "type": "object", + "required": [ + "address" + ], + "properties": { + "address": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "config" + ], + "properties": { + "config": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Gets build information of this contract, such as the commit hash used for the build or rustc version.", + "type": "object", + "required": [ + "get_contract_version" + ], + "properties": { + "get_contract_version": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Gets the stored contract version information that's required by the CW2 spec interface for migrations.", + "type": "object", + "required": [ + "get_cw2_contract_version" + ], + "properties": { + "get_cw2_contract_version": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + } + ], + "definitions": { + "NymAddress": { + "description": "The types of addresses supported.", + "oneOf": [ + { + "description": "String representation of a nym address, which is of the form client_id.client_enc@gateway_id.", + "type": "object", + "required": [ + "address" + ], + "properties": { + "address": { + "type": "string" + } + }, + "additionalProperties": false + } + ] + } + } +} diff --git a/contracts/service-provider-directory/schema/raw/response_to_all.json b/contracts/service-provider-directory/schema/raw/response_to_all.json new file mode 100644 index 0000000000..e9d7c842dd --- /dev/null +++ b/contracts/service-provider-directory/schema/raw/response_to_all.json @@ -0,0 +1,162 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "PagedServicesListResponse", + "type": "object", + "required": [ + "per_page", + "services" + ], + "properties": { + "per_page": { + "type": "integer", + "format": "uint", + "minimum": 0.0 + }, + "services": { + "type": "array", + "items": { + "$ref": "#/definitions/Service" + } + }, + "start_next_after": { + "description": "Field indicating paging information for the following queries if the caller wishes to get further entries.", + "type": [ + "integer", + "null" + ], + "format": "uint32", + "minimum": 0.0 + } + }, + "additionalProperties": false, + "definitions": { + "Addr": { + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + }, + "Coin": { + "type": "object", + "required": [ + "amount", + "denom" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "denom": { + "type": "string" + } + } + }, + "NymAddress": { + "description": "The types of addresses supported.", + "oneOf": [ + { + "description": "String representation of a nym address, which is of the form client_id.client_enc@gateway_id.", + "type": "object", + "required": [ + "address" + ], + "properties": { + "address": { + "type": "string" + } + }, + "additionalProperties": false + } + ] + }, + "Service": { + "type": "object", + "required": [ + "announcer", + "block_height", + "deposit", + "service", + "service_id" + ], + "properties": { + "announcer": { + "description": "Address of the service owner.", + "allOf": [ + { + "$ref": "#/definitions/Addr" + } + ] + }, + "block_height": { + "description": "Block height at which the service was added.", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "deposit": { + "description": "The deposit used to announce the service.", + "allOf": [ + { + "$ref": "#/definitions/Coin" + } + ] + }, + "service": { + "description": "The announced service.", + "allOf": [ + { + "$ref": "#/definitions/ServiceDetails" + } + ] + }, + "service_id": { + "description": "Unique id assigned to the anounced service.", + "type": "integer", + "format": "uint32", + "minimum": 0.0 + } + }, + "additionalProperties": false + }, + "ServiceDetails": { + "type": "object", + "required": [ + "identity_key", + "nym_address", + "service_type" + ], + "properties": { + "identity_key": { + "description": "The identity key of the service.", + "type": "string" + }, + "nym_address": { + "description": "The address of the service.", + "allOf": [ + { + "$ref": "#/definitions/NymAddress" + } + ] + }, + "service_type": { + "description": "The service type.", + "allOf": [ + { + "$ref": "#/definitions/ServiceType" + } + ] + } + }, + "additionalProperties": false + }, + "ServiceType": { + "description": "The type of services provider supported", + "type": "string", + "enum": [ + "network_requester" + ] + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } +} diff --git a/contracts/service-provider-directory/schema/raw/response_to_by_announcer.json b/contracts/service-provider-directory/schema/raw/response_to_by_announcer.json new file mode 100644 index 0000000000..a0092cef6b --- /dev/null +++ b/contracts/service-provider-directory/schema/raw/response_to_by_announcer.json @@ -0,0 +1,147 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ServicesListResponse", + "type": "object", + "required": [ + "services" + ], + "properties": { + "services": { + "type": "array", + "items": { + "$ref": "#/definitions/Service" + } + } + }, + "additionalProperties": false, + "definitions": { + "Addr": { + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + }, + "Coin": { + "type": "object", + "required": [ + "amount", + "denom" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "denom": { + "type": "string" + } + } + }, + "NymAddress": { + "description": "The types of addresses supported.", + "oneOf": [ + { + "description": "String representation of a nym address, which is of the form client_id.client_enc@gateway_id.", + "type": "object", + "required": [ + "address" + ], + "properties": { + "address": { + "type": "string" + } + }, + "additionalProperties": false + } + ] + }, + "Service": { + "type": "object", + "required": [ + "announcer", + "block_height", + "deposit", + "service", + "service_id" + ], + "properties": { + "announcer": { + "description": "Address of the service owner.", + "allOf": [ + { + "$ref": "#/definitions/Addr" + } + ] + }, + "block_height": { + "description": "Block height at which the service was added.", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "deposit": { + "description": "The deposit used to announce the service.", + "allOf": [ + { + "$ref": "#/definitions/Coin" + } + ] + }, + "service": { + "description": "The announced service.", + "allOf": [ + { + "$ref": "#/definitions/ServiceDetails" + } + ] + }, + "service_id": { + "description": "Unique id assigned to the anounced service.", + "type": "integer", + "format": "uint32", + "minimum": 0.0 + } + }, + "additionalProperties": false + }, + "ServiceDetails": { + "type": "object", + "required": [ + "identity_key", + "nym_address", + "service_type" + ], + "properties": { + "identity_key": { + "description": "The identity key of the service.", + "type": "string" + }, + "nym_address": { + "description": "The address of the service.", + "allOf": [ + { + "$ref": "#/definitions/NymAddress" + } + ] + }, + "service_type": { + "description": "The service type.", + "allOf": [ + { + "$ref": "#/definitions/ServiceType" + } + ] + } + }, + "additionalProperties": false + }, + "ServiceType": { + "description": "The type of services provider supported", + "type": "string", + "enum": [ + "network_requester" + ] + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } +} diff --git a/contracts/service-provider-directory/schema/raw/response_to_by_nym_address.json b/contracts/service-provider-directory/schema/raw/response_to_by_nym_address.json new file mode 100644 index 0000000000..a0092cef6b --- /dev/null +++ b/contracts/service-provider-directory/schema/raw/response_to_by_nym_address.json @@ -0,0 +1,147 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ServicesListResponse", + "type": "object", + "required": [ + "services" + ], + "properties": { + "services": { + "type": "array", + "items": { + "$ref": "#/definitions/Service" + } + } + }, + "additionalProperties": false, + "definitions": { + "Addr": { + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + }, + "Coin": { + "type": "object", + "required": [ + "amount", + "denom" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "denom": { + "type": "string" + } + } + }, + "NymAddress": { + "description": "The types of addresses supported.", + "oneOf": [ + { + "description": "String representation of a nym address, which is of the form client_id.client_enc@gateway_id.", + "type": "object", + "required": [ + "address" + ], + "properties": { + "address": { + "type": "string" + } + }, + "additionalProperties": false + } + ] + }, + "Service": { + "type": "object", + "required": [ + "announcer", + "block_height", + "deposit", + "service", + "service_id" + ], + "properties": { + "announcer": { + "description": "Address of the service owner.", + "allOf": [ + { + "$ref": "#/definitions/Addr" + } + ] + }, + "block_height": { + "description": "Block height at which the service was added.", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "deposit": { + "description": "The deposit used to announce the service.", + "allOf": [ + { + "$ref": "#/definitions/Coin" + } + ] + }, + "service": { + "description": "The announced service.", + "allOf": [ + { + "$ref": "#/definitions/ServiceDetails" + } + ] + }, + "service_id": { + "description": "Unique id assigned to the anounced service.", + "type": "integer", + "format": "uint32", + "minimum": 0.0 + } + }, + "additionalProperties": false + }, + "ServiceDetails": { + "type": "object", + "required": [ + "identity_key", + "nym_address", + "service_type" + ], + "properties": { + "identity_key": { + "description": "The identity key of the service.", + "type": "string" + }, + "nym_address": { + "description": "The address of the service.", + "allOf": [ + { + "$ref": "#/definitions/NymAddress" + } + ] + }, + "service_type": { + "description": "The service type.", + "allOf": [ + { + "$ref": "#/definitions/ServiceType" + } + ] + } + }, + "additionalProperties": false + }, + "ServiceType": { + "description": "The type of services provider supported", + "type": "string", + "enum": [ + "network_requester" + ] + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } +} diff --git a/contracts/service-provider-directory/schema/raw/response_to_config.json b/contracts/service-provider-directory/schema/raw/response_to_config.json new file mode 100644 index 0000000000..16420c1130 --- /dev/null +++ b/contracts/service-provider-directory/schema/raw/response_to_config.json @@ -0,0 +1,35 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ConfigResponse", + "type": "object", + "required": [ + "deposit_required" + ], + "properties": { + "deposit_required": { + "$ref": "#/definitions/Coin" + } + }, + "additionalProperties": false, + "definitions": { + "Coin": { + "type": "object", + "required": [ + "amount", + "denom" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "denom": { + "type": "string" + } + } + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } +} diff --git a/contracts/service-provider-directory/schema/raw/response_to_get_c_w2_contract_version.json b/contracts/service-provider-directory/schema/raw/response_to_get_c_w2_contract_version.json new file mode 100644 index 0000000000..3ded8c84d9 --- /dev/null +++ b/contracts/service-provider-directory/schema/raw/response_to_get_c_w2_contract_version.json @@ -0,0 +1,20 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ContractVersion", + "type": "object", + "required": [ + "contract", + "version" + ], + "properties": { + "contract": { + "description": "contract is the crate name of the implementing contract, eg. `crate:cw20-base` we will use other prefixes for other languages, and their standard global namespacing", + "type": "string" + }, + "version": { + "description": "version is any string that this implementation knows. It may be simple counter \"1\", \"2\". or semantic version on release tags \"v0.7.0\", or some custom feature flag list. the only code that needs to understand the version parsing is code that knows how to migrate from the given contract (and is tied to it's implementation somehow)", + "type": "string" + } + }, + "additionalProperties": false +} diff --git a/contracts/service-provider-directory/schema/raw/response_to_get_contract_version.json b/contracts/service-provider-directory/schema/raw/response_to_get_contract_version.json new file mode 100644 index 0000000000..60ae65f3d1 --- /dev/null +++ b/contracts/service-provider-directory/schema/raw/response_to_get_contract_version.json @@ -0,0 +1,40 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ContractBuildInformation", + "type": "object", + "required": [ + "build_timestamp", + "build_version", + "commit_branch", + "commit_sha", + "commit_timestamp", + "rustc_version" + ], + "properties": { + "build_timestamp": { + "description": "Provides the build timestamp, for example `2021-02-23T20:14:46.558472672+00:00`.", + "type": "string" + }, + "build_version": { + "description": "Provides the build version, for example `0.1.0-9-g46f83e1`.", + "type": "string" + }, + "commit_branch": { + "description": "Provides the name of the git branch that was used for the build, for example `master`.", + "type": "string" + }, + "commit_sha": { + "description": "Provides the hash of the commit that was used for the build, for example `46f83e112520533338245862d366f6a02cef07d4`.", + "type": "string" + }, + "commit_timestamp": { + "description": "Provides the timestamp of the commit that was used for the build, for example `2021-02-23T08:08:02-05:00`.", + "type": "string" + }, + "rustc_version": { + "description": "Provides the rustc version that was used for the build, for example `1.52.0-nightly`.", + "type": "string" + } + }, + "additionalProperties": false +} diff --git a/contracts/service-provider-directory/schema/raw/response_to_service_id.json b/contracts/service-provider-directory/schema/raw/response_to_service_id.json new file mode 100644 index 0000000000..68f5ff143b --- /dev/null +++ b/contracts/service-provider-directory/schema/raw/response_to_service_id.json @@ -0,0 +1,132 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Service", + "type": "object", + "required": [ + "announcer", + "block_height", + "deposit", + "service", + "service_id" + ], + "properties": { + "announcer": { + "description": "Address of the service owner.", + "allOf": [ + { + "$ref": "#/definitions/Addr" + } + ] + }, + "block_height": { + "description": "Block height at which the service was added.", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "deposit": { + "description": "The deposit used to announce the service.", + "allOf": [ + { + "$ref": "#/definitions/Coin" + } + ] + }, + "service": { + "description": "The announced service.", + "allOf": [ + { + "$ref": "#/definitions/ServiceDetails" + } + ] + }, + "service_id": { + "description": "Unique id assigned to the anounced service.", + "type": "integer", + "format": "uint32", + "minimum": 0.0 + } + }, + "additionalProperties": false, + "definitions": { + "Addr": { + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + }, + "Coin": { + "type": "object", + "required": [ + "amount", + "denom" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "denom": { + "type": "string" + } + } + }, + "NymAddress": { + "description": "The types of addresses supported.", + "oneOf": [ + { + "description": "String representation of a nym address, which is of the form client_id.client_enc@gateway_id.", + "type": "object", + "required": [ + "address" + ], + "properties": { + "address": { + "type": "string" + } + }, + "additionalProperties": false + } + ] + }, + "ServiceDetails": { + "type": "object", + "required": [ + "identity_key", + "nym_address", + "service_type" + ], + "properties": { + "identity_key": { + "description": "The identity key of the service.", + "type": "string" + }, + "nym_address": { + "description": "The address of the service.", + "allOf": [ + { + "$ref": "#/definitions/NymAddress" + } + ] + }, + "service_type": { + "description": "The service type.", + "allOf": [ + { + "$ref": "#/definitions/ServiceType" + } + ] + } + }, + "additionalProperties": false + }, + "ServiceType": { + "description": "The type of services provider supported", + "type": "string", + "enum": [ + "network_requester" + ] + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } +} diff --git a/contracts/service-provider-directory/schema/raw/response_to_signing_nonce.json b/contracts/service-provider-directory/schema/raw/response_to_signing_nonce.json new file mode 100644 index 0000000000..de85c3e56e --- /dev/null +++ b/contracts/service-provider-directory/schema/raw/response_to_signing_nonce.json @@ -0,0 +1,7 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "uint32", + "type": "integer", + "format": "uint32", + "minimum": 0.0 +} diff --git a/contracts/service-provider-directory/src/bin/schema.rs b/contracts/service-provider-directory/src/bin/schema.rs new file mode 100644 index 0000000000..0adf4b8827 --- /dev/null +++ b/contracts/service-provider-directory/src/bin/schema.rs @@ -0,0 +1,16 @@ +// Copyright 2023 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use cosmwasm_schema::write_api; +use nym_service_provider_directory_common::msg::{ + ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg, +}; + +fn main() { + write_api! { + instantiate: InstantiateMsg, + query: QueryMsg, + execute: ExecuteMsg, + migrate: MigrateMsg, + } +} diff --git a/contracts/service-provider-directory/src/state/services.rs b/contracts/service-provider-directory/src/state/services.rs index e5e68a21ff..d88ebf6722 100644 --- a/contracts/service-provider-directory/src/state/services.rs +++ b/contracts/service-provider-directory/src/state/services.rs @@ -88,6 +88,8 @@ pub fn load_nym_address(store: &dyn Storage, nym_address: NymAddress) -> Result< pub struct PagedLoad { pub services: Vec, pub limit: usize, + + /// Field indicating paging information for the following queries if the caller wishes to get further entries. pub start_next_after: Option, } diff --git a/contracts/vesting/.cargo/config b/contracts/vesting/.cargo/config new file mode 100644 index 0000000000..2c25b8a87e --- /dev/null +++ b/contracts/vesting/.cargo/config @@ -0,0 +1,4 @@ +[alias] +wasm = "build --release --target wasm32-unknown-unknown" +unit-test = "test --lib" +schema = "run --bin schema --features=schema-gen" \ No newline at end of file diff --git a/contracts/vesting/Cargo.toml b/contracts/vesting/Cargo.toml index 5f988b6431..5998e5d773 100644 --- a/contracts/vesting/Cargo.toml +++ b/contracts/vesting/Cargo.toml @@ -15,6 +15,10 @@ exclude = [ "artifacts", ] +[[bin]] +name = "schema" +required-features = ["schema-gen"] + [lib] name = "vesting_contract" crate-type = ["cdylib", "rlib"] @@ -24,12 +28,12 @@ mixnet-contract-common = { path = "../../common/cosmwasm-smart-contracts/mixnet- contracts-common = { path = "../../common/cosmwasm-smart-contracts/contracts-common", package = "nym-contracts-common", version = "0.5.0" } vesting-contract-common = { path = "../../common/cosmwasm-smart-contracts/vesting-contract", package = "nym-vesting-contract-common", version = "0.7.0" } +cosmwasm-schema = { workspace = true, optional = true } cosmwasm-std = { workspace = true } cosmwasm-derive = { workspace = true } cw2 = { workspace = true } cw-storage-plus = { workspace = true, features = ["iterator"] } -schemars = "0.8" serde = { version = "1.0", default-features = false, features = ["derive"] } thiserror = { version = "1.0" } semver = { version = "1.0.16", default-features = false } @@ -46,3 +50,6 @@ vergen = { version = "=7.4.3", default-features = false, features = ["build", "g [profile.release] overflow-checks = true + +[features] +schema-gen = ["vesting-contract-common/schema", "cosmwasm-schema"] \ No newline at end of file diff --git a/contracts/vesting/Makefile b/contracts/vesting/Makefile index a00097bc65..8ca651ccec 100644 --- a/contracts/vesting/Makefile +++ b/contracts/vesting/Makefile @@ -1,2 +1,5 @@ wasm: - RUSTFLAGS='-C link-arg=-s' cargo build --release --target wasm32-unknown-unknown \ No newline at end of file + RUSTFLAGS='-C link-arg=-s' cargo build --release --target wasm32-unknown-unknown + +generate-schema: + cargo schema \ No newline at end of file diff --git a/contracts/vesting/schema/nym-vesting-contract.json b/contracts/vesting/schema/nym-vesting-contract.json new file mode 100644 index 0000000000..87967fb410 --- /dev/null +++ b/contracts/vesting/schema/nym-vesting-contract.json @@ -0,0 +1,2841 @@ +{ + "contract_name": "nym-vesting-contract", + "contract_version": "1.4.0", + "idl_version": "1.0.0", + "instantiate": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "InstantiateMsg", + "type": "object", + "required": [ + "mix_denom", + "mixnet_contract_address" + ], + "properties": { + "mix_denom": { + "type": "string" + }, + "mixnet_contract_address": { + "type": "string" + } + }, + "additionalProperties": false + }, + "execute": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ExecuteMsg", + "oneOf": [ + { + "description": "Only owner of the node can crate the family with node as head", + "type": "object", + "required": [ + "create_family" + ], + "properties": { + "create_family": { + "type": "object", + "required": [ + "label" + ], + "properties": { + "label": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Family head needs to sign the joining node IdentityKey, the Node provides its signature signaling consent to join the family", + "type": "object", + "required": [ + "join_family" + ], + "properties": { + "join_family": { + "type": "object", + "required": [ + "family_head", + "join_permit" + ], + "properties": { + "family_head": { + "$ref": "#/definitions/FamilyHead" + }, + "join_permit": { + "$ref": "#/definitions/MessageSignature" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "leave_family" + ], + "properties": { + "leave_family": { + "type": "object", + "required": [ + "family_head" + ], + "properties": { + "family_head": { + "$ref": "#/definitions/FamilyHead" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "kick_family_member" + ], + "properties": { + "kick_family_member": { + "type": "object", + "required": [ + "member" + ], + "properties": { + "member": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "track_reward" + ], + "properties": { + "track_reward": { + "type": "object", + "required": [ + "address", + "amount" + ], + "properties": { + "address": { + "type": "string" + }, + "amount": { + "$ref": "#/definitions/Coin" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "claim_operator_reward" + ], + "properties": { + "claim_operator_reward": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "claim_delegator_reward" + ], + "properties": { + "claim_delegator_reward": { + "type": "object", + "required": [ + "mix_id" + ], + "properties": { + "mix_id": { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "update_mixnode_cost_params" + ], + "properties": { + "update_mixnode_cost_params": { + "type": "object", + "required": [ + "new_costs" + ], + "properties": { + "new_costs": { + "$ref": "#/definitions/MixNodeCostParams" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "update_mixnode_config" + ], + "properties": { + "update_mixnode_config": { + "type": "object", + "required": [ + "new_config" + ], + "properties": { + "new_config": { + "$ref": "#/definitions/MixNodeConfigUpdate" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "update_mixnet_address" + ], + "properties": { + "update_mixnet_address": { + "type": "object", + "required": [ + "address" + ], + "properties": { + "address": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "delegate_to_mixnode" + ], + "properties": { + "delegate_to_mixnode": { + "type": "object", + "required": [ + "amount", + "mix_id" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Coin" + }, + "mix_id": { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "on_behalf_of": { + "type": [ + "string", + "null" + ] + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "undelegate_from_mixnode" + ], + "properties": { + "undelegate_from_mixnode": { + "type": "object", + "required": [ + "mix_id" + ], + "properties": { + "mix_id": { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "on_behalf_of": { + "type": [ + "string", + "null" + ] + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "create_account" + ], + "properties": { + "create_account": { + "type": "object", + "required": [ + "owner_address" + ], + "properties": { + "cap": { + "anyOf": [ + { + "$ref": "#/definitions/PledgeCap" + }, + { + "type": "null" + } + ] + }, + "owner_address": { + "type": "string" + }, + "staking_address": { + "type": [ + "string", + "null" + ] + }, + "vesting_spec": { + "anyOf": [ + { + "$ref": "#/definitions/VestingSpecification" + }, + { + "type": "null" + } + ] + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "withdraw_vested_coins" + ], + "properties": { + "withdraw_vested_coins": { + "type": "object", + "required": [ + "amount" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Coin" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "track_undelegation" + ], + "properties": { + "track_undelegation": { + "type": "object", + "required": [ + "amount", + "mix_id", + "owner" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Coin" + }, + "mix_id": { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "owner": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "bond_mixnode" + ], + "properties": { + "bond_mixnode": { + "type": "object", + "required": [ + "amount", + "cost_params", + "mix_node", + "owner_signature" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Coin" + }, + "cost_params": { + "$ref": "#/definitions/MixNodeCostParams" + }, + "mix_node": { + "$ref": "#/definitions/MixNode" + }, + "owner_signature": { + "$ref": "#/definitions/MessageSignature" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "pledge_more" + ], + "properties": { + "pledge_more": { + "type": "object", + "required": [ + "amount" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Coin" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "decrease_pledge" + ], + "properties": { + "decrease_pledge": { + "type": "object", + "required": [ + "amount" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Coin" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "unbond_mixnode" + ], + "properties": { + "unbond_mixnode": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "track_unbond_mixnode" + ], + "properties": { + "track_unbond_mixnode": { + "type": "object", + "required": [ + "amount", + "owner" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Coin" + }, + "owner": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "track_decrease_pledge" + ], + "properties": { + "track_decrease_pledge": { + "type": "object", + "required": [ + "amount", + "owner" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Coin" + }, + "owner": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "bond_gateway" + ], + "properties": { + "bond_gateway": { + "type": "object", + "required": [ + "amount", + "gateway", + "owner_signature" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Coin" + }, + "gateway": { + "$ref": "#/definitions/Gateway" + }, + "owner_signature": { + "$ref": "#/definitions/MessageSignature" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "unbond_gateway" + ], + "properties": { + "unbond_gateway": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "track_unbond_gateway" + ], + "properties": { + "track_unbond_gateway": { + "type": "object", + "required": [ + "amount", + "owner" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Coin" + }, + "owner": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "update_gateway_config" + ], + "properties": { + "update_gateway_config": { + "type": "object", + "required": [ + "new_config" + ], + "properties": { + "new_config": { + "$ref": "#/definitions/GatewayConfigUpdate" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "transfer_ownership" + ], + "properties": { + "transfer_ownership": { + "type": "object", + "required": [ + "to_address" + ], + "properties": { + "to_address": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "update_staking_address" + ], + "properties": { + "update_staking_address": { + "type": "object", + "properties": { + "to_address": { + "type": [ + "string", + "null" + ] + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "update_locked_pledge_cap" + ], + "properties": { + "update_locked_pledge_cap": { + "type": "object", + "required": [ + "address", + "cap" + ], + "properties": { + "address": { + "type": "string" + }, + "cap": { + "$ref": "#/definitions/PledgeCap" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + ], + "definitions": { + "Coin": { + "type": "object", + "required": [ + "amount", + "denom" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "denom": { + "type": "string" + } + } + }, + "Decimal": { + "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", + "type": "string" + }, + "FamilyHead": { + "description": "Head of particular family as identified by its identity key (i.e. public component of its ed25519 keypair stringified into base58).", + "type": "string" + }, + "Gateway": { + "description": "Information provided by the node operator during bonding that are used to allow other entities to use the services of this node.", + "type": "object", + "required": [ + "clients_port", + "host", + "identity_key", + "location", + "mix_port", + "sphinx_key", + "version" + ], + "properties": { + "clients_port": { + "description": "Port used by this gateway for listening for client requests.", + "type": "integer", + "format": "uint16", + "minimum": 0.0 + }, + "host": { + "description": "Network address of this gateway, for example 1.1.1.1 or foo.gateway.com", + "type": "string" + }, + "identity_key": { + "description": "Base58 encoded ed25519 EdDSA public key of the gateway used to derive shared keys with clients", + "type": "string" + }, + "location": { + "description": "The physical, self-reported, location of this gateway.", + "type": "string" + }, + "mix_port": { + "description": "Port used by this gateway for listening for mix packets.", + "type": "integer", + "format": "uint16", + "minimum": 0.0 + }, + "sphinx_key": { + "description": "Base58-encoded x25519 public key used for sphinx key derivation.", + "type": "string" + }, + "version": { + "description": "The self-reported semver version of this gateway.", + "type": "string" + } + }, + "additionalProperties": false + }, + "GatewayConfigUpdate": { + "type": "object", + "required": [ + "clients_port", + "host", + "location", + "mix_port", + "version" + ], + "properties": { + "clients_port": { + "type": "integer", + "format": "uint16", + "minimum": 0.0 + }, + "host": { + "type": "string" + }, + "location": { + "type": "string" + }, + "mix_port": { + "type": "integer", + "format": "uint16", + "minimum": 0.0 + }, + "version": { + "type": "string" + } + }, + "additionalProperties": false + }, + "MessageSignature": { + "type": "array", + "items": { + "type": "integer", + "format": "uint8", + "minimum": 0.0 + } + }, + "MixNode": { + "description": "Information provided by the node operator during bonding that are used to allow other entities to use the services of this node.", + "type": "object", + "required": [ + "host", + "http_api_port", + "identity_key", + "mix_port", + "sphinx_key", + "verloc_port", + "version" + ], + "properties": { + "host": { + "description": "Network address of this mixnode, for example 1.1.1.1 or foo.mixnode.com", + "type": "string" + }, + "http_api_port": { + "description": "Port used by this mixnode for its http(s) API", + "type": "integer", + "format": "uint16", + "minimum": 0.0 + }, + "identity_key": { + "description": "Base58-encoded ed25519 EdDSA public key.", + "type": "string" + }, + "mix_port": { + "description": "Port used by this mixnode for listening for mix packets.", + "type": "integer", + "format": "uint16", + "minimum": 0.0 + }, + "sphinx_key": { + "description": "Base58-encoded x25519 public key used for sphinx key derivation.", + "type": "string" + }, + "verloc_port": { + "description": "Port used by this mixnode for listening for verloc requests.", + "type": "integer", + "format": "uint16", + "minimum": 0.0 + }, + "version": { + "description": "The self-reported semver version of this mixnode.", + "type": "string" + } + }, + "additionalProperties": false + }, + "MixNodeConfigUpdate": { + "type": "object", + "required": [ + "host", + "http_api_port", + "mix_port", + "verloc_port", + "version" + ], + "properties": { + "host": { + "type": "string" + }, + "http_api_port": { + "type": "integer", + "format": "uint16", + "minimum": 0.0 + }, + "mix_port": { + "type": "integer", + "format": "uint16", + "minimum": 0.0 + }, + "verloc_port": { + "type": "integer", + "format": "uint16", + "minimum": 0.0 + }, + "version": { + "type": "string" + } + }, + "additionalProperties": false + }, + "MixNodeCostParams": { + "description": "The cost parameters, or the cost function, defined for the particular mixnode that influences how the rewards should be split between the node operator and its delegators.", + "type": "object", + "required": [ + "interval_operating_cost", + "profit_margin_percent" + ], + "properties": { + "interval_operating_cost": { + "description": "Operating cost of the associated mixnode per the entire interval.", + "allOf": [ + { + "$ref": "#/definitions/Coin" + } + ] + }, + "profit_margin_percent": { + "description": "The profit margin of the associated mixnode, i.e. the desired percent of the reward to be distributed to the operator.", + "allOf": [ + { + "$ref": "#/definitions/Percent" + } + ] + } + }, + "additionalProperties": false + }, + "Percent": { + "description": "Percent represents a value between 0 and 100% (i.e. between 0.0 and 1.0)", + "allOf": [ + { + "$ref": "#/definitions/Decimal" + } + ] + }, + "PledgeCap": { + "description": "Defines cap for pleding/staking tokens.", + "oneOf": [ + { + "description": "Specifies a percent-based pledge cap, i.e. only given % of tokens could be pledged/staked.", + "type": "object", + "required": [ + "percent" + ], + "properties": { + "percent": { + "$ref": "#/definitions/Percent" + } + }, + "additionalProperties": false + }, + { + "description": "Specifies an absolute pledge cap, i.e. an explicit value that could be pledged/staked.", + "type": "object", + "required": [ + "absolute" + ], + "properties": { + "absolute": { + "$ref": "#/definitions/Uint128" + } + }, + "additionalProperties": false + } + ] + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + }, + "VestingSpecification": { + "type": "object", + "properties": { + "num_periods": { + "type": [ + "integer", + "null" + ], + "format": "uint64", + "minimum": 0.0 + }, + "period_seconds": { + "type": [ + "integer", + "null" + ], + "format": "uint64", + "minimum": 0.0 + }, + "start_time": { + "type": [ + "integer", + "null" + ], + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + } + }, + "query": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "QueryMsg", + "description": "Queries exposed by this contract.", + "oneOf": [ + { + "description": "Gets build information of this contract, such as the commit hash used for the build or rustc version.", + "type": "object", + "required": [ + "get_contract_version" + ], + "properties": { + "get_contract_version": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Gets the stored contract version information that's required by the CW2 spec interface for migrations.", + "type": "object", + "required": [ + "get_cw2_contract_version" + ], + "properties": { + "get_cw2_contract_version": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Gets the list of vesting accounts held in this contract.", + "type": "object", + "required": [ + "get_accounts_paged" + ], + "properties": { + "get_accounts_paged": { + "type": "object", + "properties": { + "limit": { + "description": "Controls the maximum number of entries returned by the query. Note that too large values will be overwritten by a saner default.", + "type": [ + "integer", + "null" + ], + "format": "uint32", + "minimum": 0.0 + }, + "start_next_after": { + "description": "Pagination control for the values returned by the query. Note that the provided value itself will **not** be used for the response.", + "type": [ + "string", + "null" + ] + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Gets the list of coins that are still vesting for each account held in this contract.", + "type": "object", + "required": [ + "get_accounts_vesting_coins_paged" + ], + "properties": { + "get_accounts_vesting_coins_paged": { + "type": "object", + "properties": { + "limit": { + "description": "Controls the maximum number of entries returned by the query. Note that too large values will be overwritten by a saner default.", + "type": [ + "integer", + "null" + ], + "format": "uint32", + "minimum": 0.0 + }, + "start_next_after": { + "description": "Pagination control for the values returned by the query. Note that the provided value itself will **not** be used for the response.", + "type": [ + "string", + "null" + ] + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Returns the amount of locked coins for the provided vesting account, i.e. coins that are still vesting but have not been staked. `locked_coins = vesting_coins - staked_coins`", + "type": "object", + "required": [ + "locked_coins" + ], + "properties": { + "locked_coins": { + "type": "object", + "required": [ + "vesting_account_address" + ], + "properties": { + "block_time": { + "description": "(deprecated) Optional argument specifying that the query should be performed against non-current block.", + "anyOf": [ + { + "$ref": "#/definitions/Timestamp" + }, + { + "type": "null" + } + ] + }, + "vesting_account_address": { + "description": "Address of the vesting account in question.", + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Returns the amount of spendable coins for the provided vesting account, i.e. coins that could be withdrawn. `spendable_coins = account_balance - locked_coins` note: `account_balance` is the amount of coins still physically present in this contract, i.e. not withdrawn or staked.", + "type": "object", + "required": [ + "spendable_coins" + ], + "properties": { + "spendable_coins": { + "type": "object", + "required": [ + "vesting_account_address" + ], + "properties": { + "block_time": { + "description": "(deprecated) Optional argument specifying that the query should be performed against non-current block.", + "anyOf": [ + { + "$ref": "#/definitions/Timestamp" + }, + { + "type": "null" + } + ] + }, + "vesting_account_address": { + "description": "Address of the vesting account in question.", + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Returns the amount of coins that have already vested for the provided vesting account address.", + "type": "object", + "required": [ + "get_vested_coins" + ], + "properties": { + "get_vested_coins": { + "type": "object", + "required": [ + "vesting_account_address" + ], + "properties": { + "block_time": { + "description": "(deprecated) Optional argument specifying that the query should be performed against non-current block.", + "anyOf": [ + { + "$ref": "#/definitions/Timestamp" + }, + { + "type": "null" + } + ] + }, + "vesting_account_address": { + "description": "Address of the vesting account in question.", + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Returns the amount of coins that are still vesting for the provided vesting account address.", + "type": "object", + "required": [ + "get_vesting_coins" + ], + "properties": { + "get_vesting_coins": { + "type": "object", + "required": [ + "vesting_account_address" + ], + "properties": { + "block_time": { + "description": "(deprecated) Optional argument specifying that the query should be performed against non-current block.", + "anyOf": [ + { + "$ref": "#/definitions/Timestamp" + }, + { + "type": "null" + } + ] + }, + "vesting_account_address": { + "description": "Address of the vesting account in question.", + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Returns the starting vesting time for the provided vesting account, i.e. the beginning of the first vesting period.", + "type": "object", + "required": [ + "get_start_time" + ], + "properties": { + "get_start_time": { + "type": "object", + "required": [ + "vesting_account_address" + ], + "properties": { + "vesting_account_address": { + "description": "Address of the vesting account in question.", + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Returns the ending vesting time for the provided vesting account, i.e. the end of the last vesting period.", + "type": "object", + "required": [ + "get_end_time" + ], + "properties": { + "get_end_time": { + "type": "object", + "required": [ + "vesting_account_address" + ], + "properties": { + "vesting_account_address": { + "description": "Address of the vesting account in question.", + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Returns the initial vesting specification used for the provided vesting account address.", + "type": "object", + "required": [ + "get_original_vesting" + ], + "properties": { + "get_original_vesting": { + "type": "object", + "required": [ + "vesting_account_address" + ], + "properties": { + "vesting_account_address": { + "description": "Address of the vesting account in question.", + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Returns the total amount of coins accrued through claimed staking rewards by the provided vesting account.", + "type": "object", + "required": [ + "get_historical_vesting_staking_reward" + ], + "properties": { + "get_historical_vesting_staking_reward": { + "type": "object", + "required": [ + "vesting_account_address" + ], + "properties": { + "vesting_account_address": { + "description": "Address of the vesting account in question.", + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Returns the amount of spendable vesting coins for the provided vesting account, i.e. coins that could be withdrawn that originated from the vesting specification.", + "type": "object", + "required": [ + "get_spendable_vested_coins" + ], + "properties": { + "get_spendable_vested_coins": { + "type": "object", + "required": [ + "vesting_account_address" + ], + "properties": { + "vesting_account_address": { + "description": "Address of the vesting account in question.", + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Returns the amount of spendable reward coins for the provided vesting account, i.e. coins that could be withdrawn that originated from the claimed staking rewards.", + "type": "object", + "required": [ + "get_spendable_reward_coins" + ], + "properties": { + "get_spendable_reward_coins": { + "type": "object", + "required": [ + "vesting_account_address" + ], + "properties": { + "vesting_account_address": { + "description": "Address of the vesting account in question.", + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Returns the amount of coins that are currently delegated for the provided vesting account address.", + "type": "object", + "required": [ + "get_delegated_coins" + ], + "properties": { + "get_delegated_coins": { + "type": "object", + "required": [ + "vesting_account_address" + ], + "properties": { + "vesting_account_address": { + "description": "Address of the vesting account in question.", + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Returns the amount of coins that are currently pledged for the provided vesting account address.", + "type": "object", + "required": [ + "get_pledged_coins" + ], + "properties": { + "get_pledged_coins": { + "type": "object", + "required": [ + "vesting_account_address" + ], + "properties": { + "vesting_account_address": { + "description": "Address of the vesting account in question.", + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Returns the amount of coins that are currently staked (i.e. delegations + pledges) for the provided vesting account address.", + "type": "object", + "required": [ + "get_staked_coins" + ], + "properties": { + "get_staked_coins": { + "type": "object", + "required": [ + "vesting_account_address" + ], + "properties": { + "vesting_account_address": { + "description": "Address of the vesting account in question.", + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Returns the amount of coins that got withdrawn for the provided vesting account address.", + "type": "object", + "required": [ + "get_withdrawn_coins" + ], + "properties": { + "get_withdrawn_coins": { + "type": "object", + "required": [ + "vesting_account_address" + ], + "properties": { + "vesting_account_address": { + "description": "Address of the vesting account in question.", + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Returns detailed information associated with the account for the provided vesting account address.", + "type": "object", + "required": [ + "get_account" + ], + "properties": { + "get_account": { + "type": "object", + "required": [ + "address" + ], + "properties": { + "address": { + "description": "Address of the vesting account in question.", + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Returns pledge information (if applicable) for bonded mixnode for the provided vesting account address.", + "type": "object", + "required": [ + "get_mixnode" + ], + "properties": { + "get_mixnode": { + "type": "object", + "required": [ + "address" + ], + "properties": { + "address": { + "description": "Address of the vesting account in question.", + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Returns pledge information (if applicable) for bonded gateway for the provided vesting account address.", + "type": "object", + "required": [ + "get_gateway" + ], + "properties": { + "get_gateway": { + "type": "object", + "required": [ + "address" + ], + "properties": { + "address": { + "description": "Address of the vesting account in question.", + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Returns the current vesting period for the provided vesting account address.", + "type": "object", + "required": [ + "get_current_vesting_period" + ], + "properties": { + "get_current_vesting_period": { + "type": "object", + "required": [ + "address" + ], + "properties": { + "address": { + "description": "Address of the vesting account in question.", + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Returns the information about particular vesting delegation.", + "type": "object", + "required": [ + "get_delegation" + ], + "properties": { + "get_delegation": { + "type": "object", + "required": [ + "address", + "block_timestamp_secs", + "mix_id" + ], + "properties": { + "address": { + "description": "Address of the vesting account in question.", + "type": "string" + }, + "block_timestamp_secs": { + "description": "Block timestamp of the delegation.", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "mix_id": { + "description": "Id of the mixnode towards which the delegation has been made.", + "type": "integer", + "format": "uint32", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Returns the total amount of coins delegated towards particular mixnode by the provided vesting account address.", + "type": "object", + "required": [ + "get_total_delegation_amount" + ], + "properties": { + "get_total_delegation_amount": { + "type": "object", + "required": [ + "address", + "mix_id" + ], + "properties": { + "address": { + "description": "Address of the vesting account in question.", + "type": "string" + }, + "mix_id": { + "description": "Id of the mixnode towards which the delegations have been made.", + "type": "integer", + "format": "uint32", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Returns timestamps of delegations made towards particular mixnode by the provided vesting account address.", + "type": "object", + "required": [ + "get_delegation_times" + ], + "properties": { + "get_delegation_times": { + "type": "object", + "required": [ + "address", + "mix_id" + ], + "properties": { + "address": { + "description": "Address of the vesting account in question.", + "type": "string" + }, + "mix_id": { + "description": "Id of the mixnode towards which the delegations have been made.", + "type": "integer", + "format": "uint32", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Returns all active delegations made with vesting tokens stored in this contract.", + "type": "object", + "required": [ + "get_all_delegations" + ], + "properties": { + "get_all_delegations": { + "type": "object", + "properties": { + "limit": { + "description": "Controls the maximum number of entries returned by the query. Note that too large values will be overwritten by a saner default.", + "type": [ + "integer", + "null" + ], + "format": "uint32", + "minimum": 0.0 + }, + "start_after": { + "description": "Pagination control for the values returned by the query. Note that the provided value itself will **not** be used for the response.", + "type": [ + "array", + "null" + ], + "items": [ + { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + ], + "maxItems": 3, + "minItems": 3 + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + ], + "definitions": { + "Timestamp": { + "description": "A point in time in nanosecond precision.\n\nThis type can represent times from 1970-01-01T00:00:00Z to 2554-07-21T23:34:33Z.\n\n## Examples\n\n``` # use cosmwasm_std::Timestamp; let ts = Timestamp::from_nanos(1_000_000_202); assert_eq!(ts.nanos(), 1_000_000_202); assert_eq!(ts.seconds(), 1); assert_eq!(ts.subsec_nanos(), 202);\n\nlet ts = ts.plus_seconds(2); assert_eq!(ts.nanos(), 3_000_000_202); assert_eq!(ts.seconds(), 3); assert_eq!(ts.subsec_nanos(), 202); ```", + "allOf": [ + { + "$ref": "#/definitions/Uint64" + } + ] + }, + "Uint64": { + "description": "A thin wrapper around u64 that is using strings for JSON encoding/decoding, such that the full u64 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u64` to get the value out:\n\n``` # use cosmwasm_std::Uint64; let a = Uint64::from(42u64); assert_eq!(a.u64(), 42);\n\nlet b = Uint64::from(70u32); assert_eq!(b.u64(), 70); ```", + "type": "string" + } + } + }, + "migrate": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "MigrateMsg", + "type": "object", + "additionalProperties": false + }, + "sudo": null, + "responses": { + "get_account": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Account", + "description": "Vesting account information.", + "type": "object", + "required": [ + "coin", + "owner_address", + "periods", + "start_time", + "storage_key" + ], + "properties": { + "coin": { + "description": "The initial amount of coins used creation of this account.", + "allOf": [ + { + "$ref": "#/definitions/Coin" + } + ] + }, + "owner_address": { + "description": "Address of the owner of the vesting account.", + "allOf": [ + { + "$ref": "#/definitions/Addr" + } + ] + }, + "periods": { + "description": "All vesting periods for this account.", + "type": "array", + "items": { + "$ref": "#/definitions/VestingPeriod" + } + }, + "pledge_cap": { + "description": "Optional custom pledge cap of this vesting account.", + "default": null, + "anyOf": [ + { + "$ref": "#/definitions/PledgeCap" + }, + { + "type": "null" + } + ] + }, + "staking_address": { + "description": "Optional address of an account allowed to perform staking on behalf of the owner.", + "anyOf": [ + { + "$ref": "#/definitions/Addr" + }, + { + "type": "null" + } + ] + }, + "start_time": { + "description": "The starting vesting time.", + "allOf": [ + { + "$ref": "#/definitions/Timestamp" + } + ] + }, + "storage_key": { + "description": "The id/storage_key of this vesting account.", + "type": "integer", + "format": "uint32", + "minimum": 0.0 + } + }, + "additionalProperties": false, + "definitions": { + "Addr": { + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + }, + "Coin": { + "type": "object", + "required": [ + "amount", + "denom" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "denom": { + "type": "string" + } + } + }, + "Decimal": { + "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", + "type": "string" + }, + "Percent": { + "description": "Percent represents a value between 0 and 100% (i.e. between 0.0 and 1.0)", + "allOf": [ + { + "$ref": "#/definitions/Decimal" + } + ] + }, + "PledgeCap": { + "description": "Defines cap for pleding/staking tokens.", + "oneOf": [ + { + "description": "Specifies a percent-based pledge cap, i.e. only given % of tokens could be pledged/staked.", + "type": "object", + "required": [ + "percent" + ], + "properties": { + "percent": { + "$ref": "#/definitions/Percent" + } + }, + "additionalProperties": false + }, + { + "description": "Specifies an absolute pledge cap, i.e. an explicit value that could be pledged/staked.", + "type": "object", + "required": [ + "absolute" + ], + "properties": { + "absolute": { + "$ref": "#/definitions/Uint128" + } + }, + "additionalProperties": false + } + ] + }, + "Timestamp": { + "description": "A point in time in nanosecond precision.\n\nThis type can represent times from 1970-01-01T00:00:00Z to 2554-07-21T23:34:33Z.\n\n## Examples\n\n``` # use cosmwasm_std::Timestamp; let ts = Timestamp::from_nanos(1_000_000_202); assert_eq!(ts.nanos(), 1_000_000_202); assert_eq!(ts.seconds(), 1); assert_eq!(ts.subsec_nanos(), 202);\n\nlet ts = ts.plus_seconds(2); assert_eq!(ts.nanos(), 3_000_000_202); assert_eq!(ts.seconds(), 3); assert_eq!(ts.subsec_nanos(), 202); ```", + "allOf": [ + { + "$ref": "#/definitions/Uint64" + } + ] + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + }, + "Uint64": { + "description": "A thin wrapper around u64 that is using strings for JSON encoding/decoding, such that the full u64 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u64` to get the value out:\n\n``` # use cosmwasm_std::Uint64; let a = Uint64::from(42u64); assert_eq!(a.u64(), 42);\n\nlet b = Uint64::from(70u32); assert_eq!(b.u64(), 70); ```", + "type": "string" + }, + "VestingPeriod": { + "description": "Vesting period details.", + "type": "object", + "required": [ + "period_seconds", + "start_time" + ], + "properties": { + "period_seconds": { + "description": "The duration (in seconds) of the vesting period.", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "start_time": { + "description": "The start time of this vesting period, as unix timestamp.", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + } + }, + "get_accounts_paged": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "AccountsResponse", + "description": "Response containing basic vesting account information", + "type": "object", + "required": [ + "accounts" + ], + "properties": { + "accounts": { + "description": "The actual accounts returned by the query.", + "type": "array", + "items": { + "$ref": "#/definitions/BaseVestingAccountInfo" + } + }, + "start_next_after": { + "description": "Field indicating paging information for the following queries if the caller wishes to get further entries.", + "anyOf": [ + { + "$ref": "#/definitions/Addr" + }, + { + "type": "null" + } + ] + } + }, + "additionalProperties": false, + "definitions": { + "Addr": { + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + }, + "BaseVestingAccountInfo": { + "description": "Basic information regarding particular vesting account", + "type": "object", + "required": [ + "account_id", + "owner" + ], + "properties": { + "account_id": { + "description": "Id associated with this account", + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "owner": { + "description": "Address of this account's owner", + "allOf": [ + { + "$ref": "#/definitions/Addr" + } + ] + } + }, + "additionalProperties": false + } + } + }, + "get_accounts_vesting_coins_paged": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "VestingCoinsResponse", + "description": "Response containing vesting coins held in this contract", + "type": "object", + "required": [ + "accounts" + ], + "properties": { + "accounts": { + "description": "The actual accounts, and their vesting coins, returned by the query.", + "type": "array", + "items": { + "$ref": "#/definitions/AccountVestingCoins" + } + }, + "start_next_after": { + "description": "Field indicating paging information for the following queries if the caller wishes to get further entries.", + "anyOf": [ + { + "$ref": "#/definitions/Addr" + }, + { + "type": "null" + } + ] + } + }, + "additionalProperties": false, + "definitions": { + "AccountVestingCoins": { + "description": "Basic information regarding particular vesting account alongside the amount of vesting coins.", + "type": "object", + "required": [ + "account_id", + "owner", + "still_vesting" + ], + "properties": { + "account_id": { + "description": "Id associated with this account", + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "owner": { + "description": "Address of this account's owner", + "allOf": [ + { + "$ref": "#/definitions/Addr" + } + ] + }, + "still_vesting": { + "description": "Coins that are still vesting belonging to this account.", + "allOf": [ + { + "$ref": "#/definitions/Coin" + } + ] + } + }, + "additionalProperties": false + }, + "Addr": { + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + }, + "Coin": { + "type": "object", + "required": [ + "amount", + "denom" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "denom": { + "type": "string" + } + } + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } + }, + "get_all_delegations": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "AllDelegationsResponse", + "description": "Response containing paged list of all vesting delegations made using vesting coins.", + "type": "object", + "required": [ + "delegations" + ], + "properties": { + "delegations": { + "description": "The actual vesting delegations made.", + "type": "array", + "items": { + "$ref": "#/definitions/VestingDelegation" + } + }, + "start_next_after": { + "description": "Field indicating paging information for the following queries if the caller wishes to get further entries.", + "type": [ + "array", + "null" + ], + "items": [ + { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + ], + "maxItems": 3, + "minItems": 3 + } + }, + "additionalProperties": false, + "definitions": { + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + }, + "VestingDelegation": { + "description": "Details about particular vesting delegation.", + "type": "object", + "required": [ + "account_id", + "amount", + "block_timestamp", + "mix_id" + ], + "properties": { + "account_id": { + "description": "The id of the vesting account that has made the delegation.", + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "amount": { + "description": "The raw amount delegated (interpreted to be in the same denom as the underlying vesting specification)", + "allOf": [ + { + "$ref": "#/definitions/Uint128" + } + ] + }, + "block_timestamp": { + "description": "The block timestamp when the delegation has been made.", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "mix_id": { + "description": "The id of the mixnode towards which the delegation has been made.", + "type": "integer", + "format": "uint32", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + } + }, + "get_c_w2_contract_version": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ContractVersion", + "type": "object", + "required": [ + "contract", + "version" + ], + "properties": { + "contract": { + "description": "contract is the crate name of the implementing contract, eg. `crate:cw20-base` we will use other prefixes for other languages, and their standard global namespacing", + "type": "string" + }, + "version": { + "description": "version is any string that this implementation knows. It may be simple counter \"1\", \"2\". or semantic version on release tags \"v0.7.0\", or some custom feature flag list. the only code that needs to understand the version parsing is code that knows how to migrate from the given contract (and is tied to it's implementation somehow)", + "type": "string" + } + }, + "additionalProperties": false + }, + "get_contract_version": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ContractBuildInformation", + "type": "object", + "required": [ + "build_timestamp", + "build_version", + "commit_branch", + "commit_sha", + "commit_timestamp", + "rustc_version" + ], + "properties": { + "build_timestamp": { + "description": "Provides the build timestamp, for example `2021-02-23T20:14:46.558472672+00:00`.", + "type": "string" + }, + "build_version": { + "description": "Provides the build version, for example `0.1.0-9-g46f83e1`.", + "type": "string" + }, + "commit_branch": { + "description": "Provides the name of the git branch that was used for the build, for example `master`.", + "type": "string" + }, + "commit_sha": { + "description": "Provides the hash of the commit that was used for the build, for example `46f83e112520533338245862d366f6a02cef07d4`.", + "type": "string" + }, + "commit_timestamp": { + "description": "Provides the timestamp of the commit that was used for the build, for example `2021-02-23T08:08:02-05:00`.", + "type": "string" + }, + "rustc_version": { + "description": "Provides the rustc version that was used for the build, for example `1.52.0-nightly`.", + "type": "string" + } + }, + "additionalProperties": false + }, + "get_current_vesting_period": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Period", + "description": "The vesting period.", + "oneOf": [ + { + "description": "Defines a pre-vesting period.", + "type": "string", + "enum": [ + "before" + ] + }, + { + "description": "Defines currently active vesting period.", + "type": "object", + "required": [ + "in" + ], + "properties": { + "in": { + "type": "integer", + "format": "uint", + "minimum": 0.0 + } + }, + "additionalProperties": false + }, + { + "description": "Defines a post-vesting period.", + "type": "string", + "enum": [ + "after" + ] + } + ] + }, + "get_delegated_coins": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Coin", + "type": "object", + "required": [ + "amount", + "denom" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "denom": { + "type": "string" + } + }, + "definitions": { + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } + }, + "get_delegation": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "VestingDelegation", + "description": "Details about particular vesting delegation.", + "type": "object", + "required": [ + "account_id", + "amount", + "block_timestamp", + "mix_id" + ], + "properties": { + "account_id": { + "description": "The id of the vesting account that has made the delegation.", + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "amount": { + "description": "The raw amount delegated (interpreted to be in the same denom as the underlying vesting specification)", + "allOf": [ + { + "$ref": "#/definitions/Uint128" + } + ] + }, + "block_timestamp": { + "description": "The block timestamp when the delegation has been made.", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "mix_id": { + "description": "The id of the mixnode towards which the delegation has been made.", + "type": "integer", + "format": "uint32", + "minimum": 0.0 + } + }, + "additionalProperties": false, + "definitions": { + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } + }, + "get_delegation_times": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "DelegationTimesResponse", + "description": "Response containing timestamps of all delegations made towards particular mixnode by given vesting account.", + "type": "object", + "required": [ + "account_id", + "delegation_timestamps", + "mix_id", + "owner" + ], + "properties": { + "account_id": { + "description": "Id associated with this account", + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "delegation_timestamps": { + "description": "All timestamps where a delegation was made", + "type": "array", + "items": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "mix_id": { + "description": "Id of the mixnode towards which the delegation was made", + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "owner": { + "description": "Address of this account's owner", + "allOf": [ + { + "$ref": "#/definitions/Addr" + } + ] + } + }, + "additionalProperties": false, + "definitions": { + "Addr": { + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + } + } + }, + "get_end_time": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Timestamp", + "description": "A point in time in nanosecond precision.\n\nThis type can represent times from 1970-01-01T00:00:00Z to 2554-07-21T23:34:33Z.\n\n## Examples\n\n``` # use cosmwasm_std::Timestamp; let ts = Timestamp::from_nanos(1_000_000_202); assert_eq!(ts.nanos(), 1_000_000_202); assert_eq!(ts.seconds(), 1); assert_eq!(ts.subsec_nanos(), 202);\n\nlet ts = ts.plus_seconds(2); assert_eq!(ts.nanos(), 3_000_000_202); assert_eq!(ts.seconds(), 3); assert_eq!(ts.subsec_nanos(), 202); ```", + "allOf": [ + { + "$ref": "#/definitions/Uint64" + } + ], + "definitions": { + "Uint64": { + "description": "A thin wrapper around u64 that is using strings for JSON encoding/decoding, such that the full u64 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u64` to get the value out:\n\n``` # use cosmwasm_std::Uint64; let a = Uint64::from(42u64); assert_eq!(a.u64(), 42);\n\nlet b = Uint64::from(70u32); assert_eq!(b.u64(), 70); ```", + "type": "string" + } + } + }, + "get_gateway": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Nullable_PledgeData", + "anyOf": [ + { + "$ref": "#/definitions/PledgeData" + }, + { + "type": "null" + } + ], + "definitions": { + "Coin": { + "type": "object", + "required": [ + "amount", + "denom" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "denom": { + "type": "string" + } + } + }, + "PledgeData": { + "description": "Information regarding pledge (i.e. mixnode or gateway bonding) made with vesting tokens.", + "type": "object", + "required": [ + "amount", + "block_time" + ], + "properties": { + "amount": { + "description": "The amount pledged.", + "allOf": [ + { + "$ref": "#/definitions/Coin" + } + ] + }, + "block_time": { + "description": "The block timestamp where the pledge occurred.", + "allOf": [ + { + "$ref": "#/definitions/Timestamp" + } + ] + } + }, + "additionalProperties": false + }, + "Timestamp": { + "description": "A point in time in nanosecond precision.\n\nThis type can represent times from 1970-01-01T00:00:00Z to 2554-07-21T23:34:33Z.\n\n## Examples\n\n``` # use cosmwasm_std::Timestamp; let ts = Timestamp::from_nanos(1_000_000_202); assert_eq!(ts.nanos(), 1_000_000_202); assert_eq!(ts.seconds(), 1); assert_eq!(ts.subsec_nanos(), 202);\n\nlet ts = ts.plus_seconds(2); assert_eq!(ts.nanos(), 3_000_000_202); assert_eq!(ts.seconds(), 3); assert_eq!(ts.subsec_nanos(), 202); ```", + "allOf": [ + { + "$ref": "#/definitions/Uint64" + } + ] + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + }, + "Uint64": { + "description": "A thin wrapper around u64 that is using strings for JSON encoding/decoding, such that the full u64 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u64` to get the value out:\n\n``` # use cosmwasm_std::Uint64; let a = Uint64::from(42u64); assert_eq!(a.u64(), 42);\n\nlet b = Uint64::from(70u32); assert_eq!(b.u64(), 70); ```", + "type": "string" + } + } + }, + "get_historical_vesting_staking_reward": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Coin", + "type": "object", + "required": [ + "amount", + "denom" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "denom": { + "type": "string" + } + }, + "definitions": { + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } + }, + "get_mixnode": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Nullable_PledgeData", + "anyOf": [ + { + "$ref": "#/definitions/PledgeData" + }, + { + "type": "null" + } + ], + "definitions": { + "Coin": { + "type": "object", + "required": [ + "amount", + "denom" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "denom": { + "type": "string" + } + } + }, + "PledgeData": { + "description": "Information regarding pledge (i.e. mixnode or gateway bonding) made with vesting tokens.", + "type": "object", + "required": [ + "amount", + "block_time" + ], + "properties": { + "amount": { + "description": "The amount pledged.", + "allOf": [ + { + "$ref": "#/definitions/Coin" + } + ] + }, + "block_time": { + "description": "The block timestamp where the pledge occurred.", + "allOf": [ + { + "$ref": "#/definitions/Timestamp" + } + ] + } + }, + "additionalProperties": false + }, + "Timestamp": { + "description": "A point in time in nanosecond precision.\n\nThis type can represent times from 1970-01-01T00:00:00Z to 2554-07-21T23:34:33Z.\n\n## Examples\n\n``` # use cosmwasm_std::Timestamp; let ts = Timestamp::from_nanos(1_000_000_202); assert_eq!(ts.nanos(), 1_000_000_202); assert_eq!(ts.seconds(), 1); assert_eq!(ts.subsec_nanos(), 202);\n\nlet ts = ts.plus_seconds(2); assert_eq!(ts.nanos(), 3_000_000_202); assert_eq!(ts.seconds(), 3); assert_eq!(ts.subsec_nanos(), 202); ```", + "allOf": [ + { + "$ref": "#/definitions/Uint64" + } + ] + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + }, + "Uint64": { + "description": "A thin wrapper around u64 that is using strings for JSON encoding/decoding, such that the full u64 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u64` to get the value out:\n\n``` # use cosmwasm_std::Uint64; let a = Uint64::from(42u64); assert_eq!(a.u64(), 42);\n\nlet b = Uint64::from(70u32); assert_eq!(b.u64(), 70); ```", + "type": "string" + } + } + }, + "get_original_vesting": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "OriginalVestingResponse", + "description": "Details about the original vesting specification used when the account was created.", + "type": "object", + "required": [ + "amount", + "number_of_periods", + "period_duration" + ], + "properties": { + "amount": { + "description": "The original amount that was used for the creation of this vesting account", + "allOf": [ + { + "$ref": "#/definitions/Coin" + } + ] + }, + "number_of_periods": { + "description": "The number of vesting periods that the account was created with", + "type": "integer", + "format": "uint", + "minimum": 0.0 + }, + "period_duration": { + "description": "Duration of each vesting period in seconds", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false, + "definitions": { + "Coin": { + "type": "object", + "required": [ + "amount", + "denom" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "denom": { + "type": "string" + } + } + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } + }, + "get_pledged_coins": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Coin", + "type": "object", + "required": [ + "amount", + "denom" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "denom": { + "type": "string" + } + }, + "definitions": { + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } + }, + "get_spendable_reward_coins": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Coin", + "type": "object", + "required": [ + "amount", + "denom" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "denom": { + "type": "string" + } + }, + "definitions": { + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } + }, + "get_spendable_vested_coins": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Coin", + "type": "object", + "required": [ + "amount", + "denom" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "denom": { + "type": "string" + } + }, + "definitions": { + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } + }, + "get_staked_coins": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Coin", + "type": "object", + "required": [ + "amount", + "denom" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "denom": { + "type": "string" + } + }, + "definitions": { + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } + }, + "get_start_time": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Timestamp", + "description": "A point in time in nanosecond precision.\n\nThis type can represent times from 1970-01-01T00:00:00Z to 2554-07-21T23:34:33Z.\n\n## Examples\n\n``` # use cosmwasm_std::Timestamp; let ts = Timestamp::from_nanos(1_000_000_202); assert_eq!(ts.nanos(), 1_000_000_202); assert_eq!(ts.seconds(), 1); assert_eq!(ts.subsec_nanos(), 202);\n\nlet ts = ts.plus_seconds(2); assert_eq!(ts.nanos(), 3_000_000_202); assert_eq!(ts.seconds(), 3); assert_eq!(ts.subsec_nanos(), 202); ```", + "allOf": [ + { + "$ref": "#/definitions/Uint64" + } + ], + "definitions": { + "Uint64": { + "description": "A thin wrapper around u64 that is using strings for JSON encoding/decoding, such that the full u64 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u64` to get the value out:\n\n``` # use cosmwasm_std::Uint64; let a = Uint64::from(42u64); assert_eq!(a.u64(), 42);\n\nlet b = Uint64::from(70u32); assert_eq!(b.u64(), 70); ```", + "type": "string" + } + } + }, + "get_total_delegation_amount": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Coin", + "type": "object", + "required": [ + "amount", + "denom" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "denom": { + "type": "string" + } + }, + "definitions": { + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } + }, + "get_vested_coins": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Coin", + "type": "object", + "required": [ + "amount", + "denom" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "denom": { + "type": "string" + } + }, + "definitions": { + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } + }, + "get_vesting_coins": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Coin", + "type": "object", + "required": [ + "amount", + "denom" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "denom": { + "type": "string" + } + }, + "definitions": { + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } + }, + "get_withdrawn_coins": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Coin", + "type": "object", + "required": [ + "amount", + "denom" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "denom": { + "type": "string" + } + }, + "definitions": { + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } + }, + "locked_coins": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Coin", + "type": "object", + "required": [ + "amount", + "denom" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "denom": { + "type": "string" + } + }, + "definitions": { + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } + }, + "spendable_coins": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Coin", + "type": "object", + "required": [ + "amount", + "denom" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "denom": { + "type": "string" + } + }, + "definitions": { + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } + } + } +} diff --git a/contracts/vesting/schema/raw/execute.json b/contracts/vesting/schema/raw/execute.json new file mode 100644 index 0000000000..be6d6a73ff --- /dev/null +++ b/contracts/vesting/schema/raw/execute.json @@ -0,0 +1,971 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ExecuteMsg", + "oneOf": [ + { + "description": "Only owner of the node can crate the family with node as head", + "type": "object", + "required": [ + "create_family" + ], + "properties": { + "create_family": { + "type": "object", + "required": [ + "label" + ], + "properties": { + "label": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Family head needs to sign the joining node IdentityKey, the Node provides its signature signaling consent to join the family", + "type": "object", + "required": [ + "join_family" + ], + "properties": { + "join_family": { + "type": "object", + "required": [ + "family_head", + "join_permit" + ], + "properties": { + "family_head": { + "$ref": "#/definitions/FamilyHead" + }, + "join_permit": { + "$ref": "#/definitions/MessageSignature" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "leave_family" + ], + "properties": { + "leave_family": { + "type": "object", + "required": [ + "family_head" + ], + "properties": { + "family_head": { + "$ref": "#/definitions/FamilyHead" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "kick_family_member" + ], + "properties": { + "kick_family_member": { + "type": "object", + "required": [ + "member" + ], + "properties": { + "member": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "track_reward" + ], + "properties": { + "track_reward": { + "type": "object", + "required": [ + "address", + "amount" + ], + "properties": { + "address": { + "type": "string" + }, + "amount": { + "$ref": "#/definitions/Coin" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "claim_operator_reward" + ], + "properties": { + "claim_operator_reward": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "claim_delegator_reward" + ], + "properties": { + "claim_delegator_reward": { + "type": "object", + "required": [ + "mix_id" + ], + "properties": { + "mix_id": { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "update_mixnode_cost_params" + ], + "properties": { + "update_mixnode_cost_params": { + "type": "object", + "required": [ + "new_costs" + ], + "properties": { + "new_costs": { + "$ref": "#/definitions/MixNodeCostParams" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "update_mixnode_config" + ], + "properties": { + "update_mixnode_config": { + "type": "object", + "required": [ + "new_config" + ], + "properties": { + "new_config": { + "$ref": "#/definitions/MixNodeConfigUpdate" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "update_mixnet_address" + ], + "properties": { + "update_mixnet_address": { + "type": "object", + "required": [ + "address" + ], + "properties": { + "address": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "delegate_to_mixnode" + ], + "properties": { + "delegate_to_mixnode": { + "type": "object", + "required": [ + "amount", + "mix_id" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Coin" + }, + "mix_id": { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "on_behalf_of": { + "type": [ + "string", + "null" + ] + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "undelegate_from_mixnode" + ], + "properties": { + "undelegate_from_mixnode": { + "type": "object", + "required": [ + "mix_id" + ], + "properties": { + "mix_id": { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "on_behalf_of": { + "type": [ + "string", + "null" + ] + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "create_account" + ], + "properties": { + "create_account": { + "type": "object", + "required": [ + "owner_address" + ], + "properties": { + "cap": { + "anyOf": [ + { + "$ref": "#/definitions/PledgeCap" + }, + { + "type": "null" + } + ] + }, + "owner_address": { + "type": "string" + }, + "staking_address": { + "type": [ + "string", + "null" + ] + }, + "vesting_spec": { + "anyOf": [ + { + "$ref": "#/definitions/VestingSpecification" + }, + { + "type": "null" + } + ] + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "withdraw_vested_coins" + ], + "properties": { + "withdraw_vested_coins": { + "type": "object", + "required": [ + "amount" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Coin" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "track_undelegation" + ], + "properties": { + "track_undelegation": { + "type": "object", + "required": [ + "amount", + "mix_id", + "owner" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Coin" + }, + "mix_id": { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "owner": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "bond_mixnode" + ], + "properties": { + "bond_mixnode": { + "type": "object", + "required": [ + "amount", + "cost_params", + "mix_node", + "owner_signature" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Coin" + }, + "cost_params": { + "$ref": "#/definitions/MixNodeCostParams" + }, + "mix_node": { + "$ref": "#/definitions/MixNode" + }, + "owner_signature": { + "$ref": "#/definitions/MessageSignature" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "pledge_more" + ], + "properties": { + "pledge_more": { + "type": "object", + "required": [ + "amount" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Coin" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "decrease_pledge" + ], + "properties": { + "decrease_pledge": { + "type": "object", + "required": [ + "amount" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Coin" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "unbond_mixnode" + ], + "properties": { + "unbond_mixnode": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "track_unbond_mixnode" + ], + "properties": { + "track_unbond_mixnode": { + "type": "object", + "required": [ + "amount", + "owner" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Coin" + }, + "owner": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "track_decrease_pledge" + ], + "properties": { + "track_decrease_pledge": { + "type": "object", + "required": [ + "amount", + "owner" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Coin" + }, + "owner": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "bond_gateway" + ], + "properties": { + "bond_gateway": { + "type": "object", + "required": [ + "amount", + "gateway", + "owner_signature" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Coin" + }, + "gateway": { + "$ref": "#/definitions/Gateway" + }, + "owner_signature": { + "$ref": "#/definitions/MessageSignature" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "unbond_gateway" + ], + "properties": { + "unbond_gateway": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "track_unbond_gateway" + ], + "properties": { + "track_unbond_gateway": { + "type": "object", + "required": [ + "amount", + "owner" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Coin" + }, + "owner": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "update_gateway_config" + ], + "properties": { + "update_gateway_config": { + "type": "object", + "required": [ + "new_config" + ], + "properties": { + "new_config": { + "$ref": "#/definitions/GatewayConfigUpdate" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "transfer_ownership" + ], + "properties": { + "transfer_ownership": { + "type": "object", + "required": [ + "to_address" + ], + "properties": { + "to_address": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "update_staking_address" + ], + "properties": { + "update_staking_address": { + "type": "object", + "properties": { + "to_address": { + "type": [ + "string", + "null" + ] + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "update_locked_pledge_cap" + ], + "properties": { + "update_locked_pledge_cap": { + "type": "object", + "required": [ + "address", + "cap" + ], + "properties": { + "address": { + "type": "string" + }, + "cap": { + "$ref": "#/definitions/PledgeCap" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + ], + "definitions": { + "Coin": { + "type": "object", + "required": [ + "amount", + "denom" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "denom": { + "type": "string" + } + } + }, + "Decimal": { + "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", + "type": "string" + }, + "FamilyHead": { + "description": "Head of particular family as identified by its identity key (i.e. public component of its ed25519 keypair stringified into base58).", + "type": "string" + }, + "Gateway": { + "description": "Information provided by the node operator during bonding that are used to allow other entities to use the services of this node.", + "type": "object", + "required": [ + "clients_port", + "host", + "identity_key", + "location", + "mix_port", + "sphinx_key", + "version" + ], + "properties": { + "clients_port": { + "description": "Port used by this gateway for listening for client requests.", + "type": "integer", + "format": "uint16", + "minimum": 0.0 + }, + "host": { + "description": "Network address of this gateway, for example 1.1.1.1 or foo.gateway.com", + "type": "string" + }, + "identity_key": { + "description": "Base58 encoded ed25519 EdDSA public key of the gateway used to derive shared keys with clients", + "type": "string" + }, + "location": { + "description": "The physical, self-reported, location of this gateway.", + "type": "string" + }, + "mix_port": { + "description": "Port used by this gateway for listening for mix packets.", + "type": "integer", + "format": "uint16", + "minimum": 0.0 + }, + "sphinx_key": { + "description": "Base58-encoded x25519 public key used for sphinx key derivation.", + "type": "string" + }, + "version": { + "description": "The self-reported semver version of this gateway.", + "type": "string" + } + }, + "additionalProperties": false + }, + "GatewayConfigUpdate": { + "type": "object", + "required": [ + "clients_port", + "host", + "location", + "mix_port", + "version" + ], + "properties": { + "clients_port": { + "type": "integer", + "format": "uint16", + "minimum": 0.0 + }, + "host": { + "type": "string" + }, + "location": { + "type": "string" + }, + "mix_port": { + "type": "integer", + "format": "uint16", + "minimum": 0.0 + }, + "version": { + "type": "string" + } + }, + "additionalProperties": false + }, + "MessageSignature": { + "type": "array", + "items": { + "type": "integer", + "format": "uint8", + "minimum": 0.0 + } + }, + "MixNode": { + "description": "Information provided by the node operator during bonding that are used to allow other entities to use the services of this node.", + "type": "object", + "required": [ + "host", + "http_api_port", + "identity_key", + "mix_port", + "sphinx_key", + "verloc_port", + "version" + ], + "properties": { + "host": { + "description": "Network address of this mixnode, for example 1.1.1.1 or foo.mixnode.com", + "type": "string" + }, + "http_api_port": { + "description": "Port used by this mixnode for its http(s) API", + "type": "integer", + "format": "uint16", + "minimum": 0.0 + }, + "identity_key": { + "description": "Base58-encoded ed25519 EdDSA public key.", + "type": "string" + }, + "mix_port": { + "description": "Port used by this mixnode for listening for mix packets.", + "type": "integer", + "format": "uint16", + "minimum": 0.0 + }, + "sphinx_key": { + "description": "Base58-encoded x25519 public key used for sphinx key derivation.", + "type": "string" + }, + "verloc_port": { + "description": "Port used by this mixnode for listening for verloc requests.", + "type": "integer", + "format": "uint16", + "minimum": 0.0 + }, + "version": { + "description": "The self-reported semver version of this mixnode.", + "type": "string" + } + }, + "additionalProperties": false + }, + "MixNodeConfigUpdate": { + "type": "object", + "required": [ + "host", + "http_api_port", + "mix_port", + "verloc_port", + "version" + ], + "properties": { + "host": { + "type": "string" + }, + "http_api_port": { + "type": "integer", + "format": "uint16", + "minimum": 0.0 + }, + "mix_port": { + "type": "integer", + "format": "uint16", + "minimum": 0.0 + }, + "verloc_port": { + "type": "integer", + "format": "uint16", + "minimum": 0.0 + }, + "version": { + "type": "string" + } + }, + "additionalProperties": false + }, + "MixNodeCostParams": { + "description": "The cost parameters, or the cost function, defined for the particular mixnode that influences how the rewards should be split between the node operator and its delegators.", + "type": "object", + "required": [ + "interval_operating_cost", + "profit_margin_percent" + ], + "properties": { + "interval_operating_cost": { + "description": "Operating cost of the associated mixnode per the entire interval.", + "allOf": [ + { + "$ref": "#/definitions/Coin" + } + ] + }, + "profit_margin_percent": { + "description": "The profit margin of the associated mixnode, i.e. the desired percent of the reward to be distributed to the operator.", + "allOf": [ + { + "$ref": "#/definitions/Percent" + } + ] + } + }, + "additionalProperties": false + }, + "Percent": { + "description": "Percent represents a value between 0 and 100% (i.e. between 0.0 and 1.0)", + "allOf": [ + { + "$ref": "#/definitions/Decimal" + } + ] + }, + "PledgeCap": { + "description": "Defines cap for pleding/staking tokens.", + "oneOf": [ + { + "description": "Specifies a percent-based pledge cap, i.e. only given % of tokens could be pledged/staked.", + "type": "object", + "required": [ + "percent" + ], + "properties": { + "percent": { + "$ref": "#/definitions/Percent" + } + }, + "additionalProperties": false + }, + { + "description": "Specifies an absolute pledge cap, i.e. an explicit value that could be pledged/staked.", + "type": "object", + "required": [ + "absolute" + ], + "properties": { + "absolute": { + "$ref": "#/definitions/Uint128" + } + }, + "additionalProperties": false + } + ] + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + }, + "VestingSpecification": { + "type": "object", + "properties": { + "num_periods": { + "type": [ + "integer", + "null" + ], + "format": "uint64", + "minimum": 0.0 + }, + "period_seconds": { + "type": [ + "integer", + "null" + ], + "format": "uint64", + "minimum": 0.0 + }, + "start_time": { + "type": [ + "integer", + "null" + ], + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + } +} diff --git a/contracts/vesting/schema/raw/instantiate.json b/contracts/vesting/schema/raw/instantiate.json new file mode 100644 index 0000000000..09b7526278 --- /dev/null +++ b/contracts/vesting/schema/raw/instantiate.json @@ -0,0 +1,18 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "InstantiateMsg", + "type": "object", + "required": [ + "mix_denom", + "mixnet_contract_address" + ], + "properties": { + "mix_denom": { + "type": "string" + }, + "mixnet_contract_address": { + "type": "string" + } + }, + "additionalProperties": false +} diff --git a/contracts/vesting/schema/raw/migrate.json b/contracts/vesting/schema/raw/migrate.json new file mode 100644 index 0000000000..7fbe8c5708 --- /dev/null +++ b/contracts/vesting/schema/raw/migrate.json @@ -0,0 +1,6 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "MigrateMsg", + "type": "object", + "additionalProperties": false +} diff --git a/contracts/vesting/schema/raw/query.json b/contracts/vesting/schema/raw/query.json new file mode 100644 index 0000000000..42b1cd94ea --- /dev/null +++ b/contracts/vesting/schema/raw/query.json @@ -0,0 +1,719 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "QueryMsg", + "description": "Queries exposed by this contract.", + "oneOf": [ + { + "description": "Gets build information of this contract, such as the commit hash used for the build or rustc version.", + "type": "object", + "required": [ + "get_contract_version" + ], + "properties": { + "get_contract_version": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Gets the stored contract version information that's required by the CW2 spec interface for migrations.", + "type": "object", + "required": [ + "get_cw2_contract_version" + ], + "properties": { + "get_cw2_contract_version": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Gets the list of vesting accounts held in this contract.", + "type": "object", + "required": [ + "get_accounts_paged" + ], + "properties": { + "get_accounts_paged": { + "type": "object", + "properties": { + "limit": { + "description": "Controls the maximum number of entries returned by the query. Note that too large values will be overwritten by a saner default.", + "type": [ + "integer", + "null" + ], + "format": "uint32", + "minimum": 0.0 + }, + "start_next_after": { + "description": "Pagination control for the values returned by the query. Note that the provided value itself will **not** be used for the response.", + "type": [ + "string", + "null" + ] + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Gets the list of coins that are still vesting for each account held in this contract.", + "type": "object", + "required": [ + "get_accounts_vesting_coins_paged" + ], + "properties": { + "get_accounts_vesting_coins_paged": { + "type": "object", + "properties": { + "limit": { + "description": "Controls the maximum number of entries returned by the query. Note that too large values will be overwritten by a saner default.", + "type": [ + "integer", + "null" + ], + "format": "uint32", + "minimum": 0.0 + }, + "start_next_after": { + "description": "Pagination control for the values returned by the query. Note that the provided value itself will **not** be used for the response.", + "type": [ + "string", + "null" + ] + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Returns the amount of locked coins for the provided vesting account, i.e. coins that are still vesting but have not been staked. `locked_coins = vesting_coins - staked_coins`", + "type": "object", + "required": [ + "locked_coins" + ], + "properties": { + "locked_coins": { + "type": "object", + "required": [ + "vesting_account_address" + ], + "properties": { + "block_time": { + "description": "(deprecated) Optional argument specifying that the query should be performed against non-current block.", + "anyOf": [ + { + "$ref": "#/definitions/Timestamp" + }, + { + "type": "null" + } + ] + }, + "vesting_account_address": { + "description": "Address of the vesting account in question.", + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Returns the amount of spendable coins for the provided vesting account, i.e. coins that could be withdrawn. `spendable_coins = account_balance - locked_coins` note: `account_balance` is the amount of coins still physically present in this contract, i.e. not withdrawn or staked.", + "type": "object", + "required": [ + "spendable_coins" + ], + "properties": { + "spendable_coins": { + "type": "object", + "required": [ + "vesting_account_address" + ], + "properties": { + "block_time": { + "description": "(deprecated) Optional argument specifying that the query should be performed against non-current block.", + "anyOf": [ + { + "$ref": "#/definitions/Timestamp" + }, + { + "type": "null" + } + ] + }, + "vesting_account_address": { + "description": "Address of the vesting account in question.", + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Returns the amount of coins that have already vested for the provided vesting account address.", + "type": "object", + "required": [ + "get_vested_coins" + ], + "properties": { + "get_vested_coins": { + "type": "object", + "required": [ + "vesting_account_address" + ], + "properties": { + "block_time": { + "description": "(deprecated) Optional argument specifying that the query should be performed against non-current block.", + "anyOf": [ + { + "$ref": "#/definitions/Timestamp" + }, + { + "type": "null" + } + ] + }, + "vesting_account_address": { + "description": "Address of the vesting account in question.", + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Returns the amount of coins that are still vesting for the provided vesting account address.", + "type": "object", + "required": [ + "get_vesting_coins" + ], + "properties": { + "get_vesting_coins": { + "type": "object", + "required": [ + "vesting_account_address" + ], + "properties": { + "block_time": { + "description": "(deprecated) Optional argument specifying that the query should be performed against non-current block.", + "anyOf": [ + { + "$ref": "#/definitions/Timestamp" + }, + { + "type": "null" + } + ] + }, + "vesting_account_address": { + "description": "Address of the vesting account in question.", + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Returns the starting vesting time for the provided vesting account, i.e. the beginning of the first vesting period.", + "type": "object", + "required": [ + "get_start_time" + ], + "properties": { + "get_start_time": { + "type": "object", + "required": [ + "vesting_account_address" + ], + "properties": { + "vesting_account_address": { + "description": "Address of the vesting account in question.", + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Returns the ending vesting time for the provided vesting account, i.e. the end of the last vesting period.", + "type": "object", + "required": [ + "get_end_time" + ], + "properties": { + "get_end_time": { + "type": "object", + "required": [ + "vesting_account_address" + ], + "properties": { + "vesting_account_address": { + "description": "Address of the vesting account in question.", + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Returns the initial vesting specification used for the provided vesting account address.", + "type": "object", + "required": [ + "get_original_vesting" + ], + "properties": { + "get_original_vesting": { + "type": "object", + "required": [ + "vesting_account_address" + ], + "properties": { + "vesting_account_address": { + "description": "Address of the vesting account in question.", + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Returns the total amount of coins accrued through claimed staking rewards by the provided vesting account.", + "type": "object", + "required": [ + "get_historical_vesting_staking_reward" + ], + "properties": { + "get_historical_vesting_staking_reward": { + "type": "object", + "required": [ + "vesting_account_address" + ], + "properties": { + "vesting_account_address": { + "description": "Address of the vesting account in question.", + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Returns the amount of spendable vesting coins for the provided vesting account, i.e. coins that could be withdrawn that originated from the vesting specification.", + "type": "object", + "required": [ + "get_spendable_vested_coins" + ], + "properties": { + "get_spendable_vested_coins": { + "type": "object", + "required": [ + "vesting_account_address" + ], + "properties": { + "vesting_account_address": { + "description": "Address of the vesting account in question.", + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Returns the amount of spendable reward coins for the provided vesting account, i.e. coins that could be withdrawn that originated from the claimed staking rewards.", + "type": "object", + "required": [ + "get_spendable_reward_coins" + ], + "properties": { + "get_spendable_reward_coins": { + "type": "object", + "required": [ + "vesting_account_address" + ], + "properties": { + "vesting_account_address": { + "description": "Address of the vesting account in question.", + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Returns the amount of coins that are currently delegated for the provided vesting account address.", + "type": "object", + "required": [ + "get_delegated_coins" + ], + "properties": { + "get_delegated_coins": { + "type": "object", + "required": [ + "vesting_account_address" + ], + "properties": { + "vesting_account_address": { + "description": "Address of the vesting account in question.", + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Returns the amount of coins that are currently pledged for the provided vesting account address.", + "type": "object", + "required": [ + "get_pledged_coins" + ], + "properties": { + "get_pledged_coins": { + "type": "object", + "required": [ + "vesting_account_address" + ], + "properties": { + "vesting_account_address": { + "description": "Address of the vesting account in question.", + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Returns the amount of coins that are currently staked (i.e. delegations + pledges) for the provided vesting account address.", + "type": "object", + "required": [ + "get_staked_coins" + ], + "properties": { + "get_staked_coins": { + "type": "object", + "required": [ + "vesting_account_address" + ], + "properties": { + "vesting_account_address": { + "description": "Address of the vesting account in question.", + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Returns the amount of coins that got withdrawn for the provided vesting account address.", + "type": "object", + "required": [ + "get_withdrawn_coins" + ], + "properties": { + "get_withdrawn_coins": { + "type": "object", + "required": [ + "vesting_account_address" + ], + "properties": { + "vesting_account_address": { + "description": "Address of the vesting account in question.", + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Returns detailed information associated with the account for the provided vesting account address.", + "type": "object", + "required": [ + "get_account" + ], + "properties": { + "get_account": { + "type": "object", + "required": [ + "address" + ], + "properties": { + "address": { + "description": "Address of the vesting account in question.", + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Returns pledge information (if applicable) for bonded mixnode for the provided vesting account address.", + "type": "object", + "required": [ + "get_mixnode" + ], + "properties": { + "get_mixnode": { + "type": "object", + "required": [ + "address" + ], + "properties": { + "address": { + "description": "Address of the vesting account in question.", + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Returns pledge information (if applicable) for bonded gateway for the provided vesting account address.", + "type": "object", + "required": [ + "get_gateway" + ], + "properties": { + "get_gateway": { + "type": "object", + "required": [ + "address" + ], + "properties": { + "address": { + "description": "Address of the vesting account in question.", + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Returns the current vesting period for the provided vesting account address.", + "type": "object", + "required": [ + "get_current_vesting_period" + ], + "properties": { + "get_current_vesting_period": { + "type": "object", + "required": [ + "address" + ], + "properties": { + "address": { + "description": "Address of the vesting account in question.", + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Returns the information about particular vesting delegation.", + "type": "object", + "required": [ + "get_delegation" + ], + "properties": { + "get_delegation": { + "type": "object", + "required": [ + "address", + "block_timestamp_secs", + "mix_id" + ], + "properties": { + "address": { + "description": "Address of the vesting account in question.", + "type": "string" + }, + "block_timestamp_secs": { + "description": "Block timestamp of the delegation.", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "mix_id": { + "description": "Id of the mixnode towards which the delegation has been made.", + "type": "integer", + "format": "uint32", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Returns the total amount of coins delegated towards particular mixnode by the provided vesting account address.", + "type": "object", + "required": [ + "get_total_delegation_amount" + ], + "properties": { + "get_total_delegation_amount": { + "type": "object", + "required": [ + "address", + "mix_id" + ], + "properties": { + "address": { + "description": "Address of the vesting account in question.", + "type": "string" + }, + "mix_id": { + "description": "Id of the mixnode towards which the delegations have been made.", + "type": "integer", + "format": "uint32", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Returns timestamps of delegations made towards particular mixnode by the provided vesting account address.", + "type": "object", + "required": [ + "get_delegation_times" + ], + "properties": { + "get_delegation_times": { + "type": "object", + "required": [ + "address", + "mix_id" + ], + "properties": { + "address": { + "description": "Address of the vesting account in question.", + "type": "string" + }, + "mix_id": { + "description": "Id of the mixnode towards which the delegations have been made.", + "type": "integer", + "format": "uint32", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Returns all active delegations made with vesting tokens stored in this contract.", + "type": "object", + "required": [ + "get_all_delegations" + ], + "properties": { + "get_all_delegations": { + "type": "object", + "properties": { + "limit": { + "description": "Controls the maximum number of entries returned by the query. Note that too large values will be overwritten by a saner default.", + "type": [ + "integer", + "null" + ], + "format": "uint32", + "minimum": 0.0 + }, + "start_after": { + "description": "Pagination control for the values returned by the query. Note that the provided value itself will **not** be used for the response.", + "type": [ + "array", + "null" + ], + "items": [ + { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + ], + "maxItems": 3, + "minItems": 3 + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + ], + "definitions": { + "Timestamp": { + "description": "A point in time in nanosecond precision.\n\nThis type can represent times from 1970-01-01T00:00:00Z to 2554-07-21T23:34:33Z.\n\n## Examples\n\n``` # use cosmwasm_std::Timestamp; let ts = Timestamp::from_nanos(1_000_000_202); assert_eq!(ts.nanos(), 1_000_000_202); assert_eq!(ts.seconds(), 1); assert_eq!(ts.subsec_nanos(), 202);\n\nlet ts = ts.plus_seconds(2); assert_eq!(ts.nanos(), 3_000_000_202); assert_eq!(ts.seconds(), 3); assert_eq!(ts.subsec_nanos(), 202); ```", + "allOf": [ + { + "$ref": "#/definitions/Uint64" + } + ] + }, + "Uint64": { + "description": "A thin wrapper around u64 that is using strings for JSON encoding/decoding, such that the full u64 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u64` to get the value out:\n\n``` # use cosmwasm_std::Uint64; let a = Uint64::from(42u64); assert_eq!(a.u64(), 42);\n\nlet b = Uint64::from(70u32); assert_eq!(b.u64(), 70); ```", + "type": "string" + } + } +} diff --git a/contracts/vesting/schema/raw/response_to_get_account.json b/contracts/vesting/schema/raw/response_to_get_account.json new file mode 100644 index 0000000000..7d8544539b --- /dev/null +++ b/contracts/vesting/schema/raw/response_to_get_account.json @@ -0,0 +1,179 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Account", + "description": "Vesting account information.", + "type": "object", + "required": [ + "coin", + "owner_address", + "periods", + "start_time", + "storage_key" + ], + "properties": { + "coin": { + "description": "The initial amount of coins used creation of this account.", + "allOf": [ + { + "$ref": "#/definitions/Coin" + } + ] + }, + "owner_address": { + "description": "Address of the owner of the vesting account.", + "allOf": [ + { + "$ref": "#/definitions/Addr" + } + ] + }, + "periods": { + "description": "All vesting periods for this account.", + "type": "array", + "items": { + "$ref": "#/definitions/VestingPeriod" + } + }, + "pledge_cap": { + "description": "Optional custom pledge cap of this vesting account.", + "default": null, + "anyOf": [ + { + "$ref": "#/definitions/PledgeCap" + }, + { + "type": "null" + } + ] + }, + "staking_address": { + "description": "Optional address of an account allowed to perform staking on behalf of the owner.", + "anyOf": [ + { + "$ref": "#/definitions/Addr" + }, + { + "type": "null" + } + ] + }, + "start_time": { + "description": "The starting vesting time.", + "allOf": [ + { + "$ref": "#/definitions/Timestamp" + } + ] + }, + "storage_key": { + "description": "The id/storage_key of this vesting account.", + "type": "integer", + "format": "uint32", + "minimum": 0.0 + } + }, + "additionalProperties": false, + "definitions": { + "Addr": { + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + }, + "Coin": { + "type": "object", + "required": [ + "amount", + "denom" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "denom": { + "type": "string" + } + } + }, + "Decimal": { + "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", + "type": "string" + }, + "Percent": { + "description": "Percent represents a value between 0 and 100% (i.e. between 0.0 and 1.0)", + "allOf": [ + { + "$ref": "#/definitions/Decimal" + } + ] + }, + "PledgeCap": { + "description": "Defines cap for pleding/staking tokens.", + "oneOf": [ + { + "description": "Specifies a percent-based pledge cap, i.e. only given % of tokens could be pledged/staked.", + "type": "object", + "required": [ + "percent" + ], + "properties": { + "percent": { + "$ref": "#/definitions/Percent" + } + }, + "additionalProperties": false + }, + { + "description": "Specifies an absolute pledge cap, i.e. an explicit value that could be pledged/staked.", + "type": "object", + "required": [ + "absolute" + ], + "properties": { + "absolute": { + "$ref": "#/definitions/Uint128" + } + }, + "additionalProperties": false + } + ] + }, + "Timestamp": { + "description": "A point in time in nanosecond precision.\n\nThis type can represent times from 1970-01-01T00:00:00Z to 2554-07-21T23:34:33Z.\n\n## Examples\n\n``` # use cosmwasm_std::Timestamp; let ts = Timestamp::from_nanos(1_000_000_202); assert_eq!(ts.nanos(), 1_000_000_202); assert_eq!(ts.seconds(), 1); assert_eq!(ts.subsec_nanos(), 202);\n\nlet ts = ts.plus_seconds(2); assert_eq!(ts.nanos(), 3_000_000_202); assert_eq!(ts.seconds(), 3); assert_eq!(ts.subsec_nanos(), 202); ```", + "allOf": [ + { + "$ref": "#/definitions/Uint64" + } + ] + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + }, + "Uint64": { + "description": "A thin wrapper around u64 that is using strings for JSON encoding/decoding, such that the full u64 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u64` to get the value out:\n\n``` # use cosmwasm_std::Uint64; let a = Uint64::from(42u64); assert_eq!(a.u64(), 42);\n\nlet b = Uint64::from(70u32); assert_eq!(b.u64(), 70); ```", + "type": "string" + }, + "VestingPeriod": { + "description": "Vesting period details.", + "type": "object", + "required": [ + "period_seconds", + "start_time" + ], + "properties": { + "period_seconds": { + "description": "The duration (in seconds) of the vesting period.", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "start_time": { + "description": "The start time of this vesting period, as unix timestamp.", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + } +} diff --git a/contracts/vesting/schema/raw/response_to_get_accounts_paged.json b/contracts/vesting/schema/raw/response_to_get_accounts_paged.json new file mode 100644 index 0000000000..f84ed729b8 --- /dev/null +++ b/contracts/vesting/schema/raw/response_to_get_accounts_paged.json @@ -0,0 +1,61 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "AccountsResponse", + "description": "Response containing basic vesting account information", + "type": "object", + "required": [ + "accounts" + ], + "properties": { + "accounts": { + "description": "The actual accounts returned by the query.", + "type": "array", + "items": { + "$ref": "#/definitions/BaseVestingAccountInfo" + } + }, + "start_next_after": { + "description": "Field indicating paging information for the following queries if the caller wishes to get further entries.", + "anyOf": [ + { + "$ref": "#/definitions/Addr" + }, + { + "type": "null" + } + ] + } + }, + "additionalProperties": false, + "definitions": { + "Addr": { + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + }, + "BaseVestingAccountInfo": { + "description": "Basic information regarding particular vesting account", + "type": "object", + "required": [ + "account_id", + "owner" + ], + "properties": { + "account_id": { + "description": "Id associated with this account", + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "owner": { + "description": "Address of this account's owner", + "allOf": [ + { + "$ref": "#/definitions/Addr" + } + ] + } + }, + "additionalProperties": false + } + } +} diff --git a/contracts/vesting/schema/raw/response_to_get_accounts_vesting_coins_paged.json b/contracts/vesting/schema/raw/response_to_get_accounts_vesting_coins_paged.json new file mode 100644 index 0000000000..6647e14194 --- /dev/null +++ b/contracts/vesting/schema/raw/response_to_get_accounts_vesting_coins_paged.json @@ -0,0 +1,89 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "VestingCoinsResponse", + "description": "Response containing vesting coins held in this contract", + "type": "object", + "required": [ + "accounts" + ], + "properties": { + "accounts": { + "description": "The actual accounts, and their vesting coins, returned by the query.", + "type": "array", + "items": { + "$ref": "#/definitions/AccountVestingCoins" + } + }, + "start_next_after": { + "description": "Field indicating paging information for the following queries if the caller wishes to get further entries.", + "anyOf": [ + { + "$ref": "#/definitions/Addr" + }, + { + "type": "null" + } + ] + } + }, + "additionalProperties": false, + "definitions": { + "AccountVestingCoins": { + "description": "Basic information regarding particular vesting account alongside the amount of vesting coins.", + "type": "object", + "required": [ + "account_id", + "owner", + "still_vesting" + ], + "properties": { + "account_id": { + "description": "Id associated with this account", + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "owner": { + "description": "Address of this account's owner", + "allOf": [ + { + "$ref": "#/definitions/Addr" + } + ] + }, + "still_vesting": { + "description": "Coins that are still vesting belonging to this account.", + "allOf": [ + { + "$ref": "#/definitions/Coin" + } + ] + } + }, + "additionalProperties": false + }, + "Addr": { + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + }, + "Coin": { + "type": "object", + "required": [ + "amount", + "denom" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "denom": { + "type": "string" + } + } + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } +} diff --git a/contracts/vesting/schema/raw/response_to_get_all_delegations.json b/contracts/vesting/schema/raw/response_to_get_all_delegations.json new file mode 100644 index 0000000000..89de917311 --- /dev/null +++ b/contracts/vesting/schema/raw/response_to_get_all_delegations.json @@ -0,0 +1,90 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "AllDelegationsResponse", + "description": "Response containing paged list of all vesting delegations made using vesting coins.", + "type": "object", + "required": [ + "delegations" + ], + "properties": { + "delegations": { + "description": "The actual vesting delegations made.", + "type": "array", + "items": { + "$ref": "#/definitions/VestingDelegation" + } + }, + "start_next_after": { + "description": "Field indicating paging information for the following queries if the caller wishes to get further entries.", + "type": [ + "array", + "null" + ], + "items": [ + { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + ], + "maxItems": 3, + "minItems": 3 + } + }, + "additionalProperties": false, + "definitions": { + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + }, + "VestingDelegation": { + "description": "Details about particular vesting delegation.", + "type": "object", + "required": [ + "account_id", + "amount", + "block_timestamp", + "mix_id" + ], + "properties": { + "account_id": { + "description": "The id of the vesting account that has made the delegation.", + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "amount": { + "description": "The raw amount delegated (interpreted to be in the same denom as the underlying vesting specification)", + "allOf": [ + { + "$ref": "#/definitions/Uint128" + } + ] + }, + "block_timestamp": { + "description": "The block timestamp when the delegation has been made.", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "mix_id": { + "description": "The id of the mixnode towards which the delegation has been made.", + "type": "integer", + "format": "uint32", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + } +} diff --git a/contracts/vesting/schema/raw/response_to_get_c_w2_contract_version.json b/contracts/vesting/schema/raw/response_to_get_c_w2_contract_version.json new file mode 100644 index 0000000000..3ded8c84d9 --- /dev/null +++ b/contracts/vesting/schema/raw/response_to_get_c_w2_contract_version.json @@ -0,0 +1,20 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ContractVersion", + "type": "object", + "required": [ + "contract", + "version" + ], + "properties": { + "contract": { + "description": "contract is the crate name of the implementing contract, eg. `crate:cw20-base` we will use other prefixes for other languages, and their standard global namespacing", + "type": "string" + }, + "version": { + "description": "version is any string that this implementation knows. It may be simple counter \"1\", \"2\". or semantic version on release tags \"v0.7.0\", or some custom feature flag list. the only code that needs to understand the version parsing is code that knows how to migrate from the given contract (and is tied to it's implementation somehow)", + "type": "string" + } + }, + "additionalProperties": false +} diff --git a/contracts/vesting/schema/raw/response_to_get_contract_version.json b/contracts/vesting/schema/raw/response_to_get_contract_version.json new file mode 100644 index 0000000000..60ae65f3d1 --- /dev/null +++ b/contracts/vesting/schema/raw/response_to_get_contract_version.json @@ -0,0 +1,40 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ContractBuildInformation", + "type": "object", + "required": [ + "build_timestamp", + "build_version", + "commit_branch", + "commit_sha", + "commit_timestamp", + "rustc_version" + ], + "properties": { + "build_timestamp": { + "description": "Provides the build timestamp, for example `2021-02-23T20:14:46.558472672+00:00`.", + "type": "string" + }, + "build_version": { + "description": "Provides the build version, for example `0.1.0-9-g46f83e1`.", + "type": "string" + }, + "commit_branch": { + "description": "Provides the name of the git branch that was used for the build, for example `master`.", + "type": "string" + }, + "commit_sha": { + "description": "Provides the hash of the commit that was used for the build, for example `46f83e112520533338245862d366f6a02cef07d4`.", + "type": "string" + }, + "commit_timestamp": { + "description": "Provides the timestamp of the commit that was used for the build, for example `2021-02-23T08:08:02-05:00`.", + "type": "string" + }, + "rustc_version": { + "description": "Provides the rustc version that was used for the build, for example `1.52.0-nightly`.", + "type": "string" + } + }, + "additionalProperties": false +} diff --git a/contracts/vesting/schema/raw/response_to_get_current_vesting_period.json b/contracts/vesting/schema/raw/response_to_get_current_vesting_period.json new file mode 100644 index 0000000000..e001a46a2c --- /dev/null +++ b/contracts/vesting/schema/raw/response_to_get_current_vesting_period.json @@ -0,0 +1,36 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Period", + "description": "The vesting period.", + "oneOf": [ + { + "description": "Defines a pre-vesting period.", + "type": "string", + "enum": [ + "before" + ] + }, + { + "description": "Defines currently active vesting period.", + "type": "object", + "required": [ + "in" + ], + "properties": { + "in": { + "type": "integer", + "format": "uint", + "minimum": 0.0 + } + }, + "additionalProperties": false + }, + { + "description": "Defines a post-vesting period.", + "type": "string", + "enum": [ + "after" + ] + } + ] +} diff --git a/contracts/vesting/schema/raw/response_to_get_delegated_coins.json b/contracts/vesting/schema/raw/response_to_get_delegated_coins.json new file mode 100644 index 0000000000..6e18ef9a9b --- /dev/null +++ b/contracts/vesting/schema/raw/response_to_get_delegated_coins.json @@ -0,0 +1,23 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Coin", + "type": "object", + "required": [ + "amount", + "denom" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "denom": { + "type": "string" + } + }, + "definitions": { + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } +} diff --git a/contracts/vesting/schema/raw/response_to_get_delegation.json b/contracts/vesting/schema/raw/response_to_get_delegation.json new file mode 100644 index 0000000000..75c770ef66 --- /dev/null +++ b/contracts/vesting/schema/raw/response_to_get_delegation.json @@ -0,0 +1,47 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "VestingDelegation", + "description": "Details about particular vesting delegation.", + "type": "object", + "required": [ + "account_id", + "amount", + "block_timestamp", + "mix_id" + ], + "properties": { + "account_id": { + "description": "The id of the vesting account that has made the delegation.", + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "amount": { + "description": "The raw amount delegated (interpreted to be in the same denom as the underlying vesting specification)", + "allOf": [ + { + "$ref": "#/definitions/Uint128" + } + ] + }, + "block_timestamp": { + "description": "The block timestamp when the delegation has been made.", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "mix_id": { + "description": "The id of the mixnode towards which the delegation has been made.", + "type": "integer", + "format": "uint32", + "minimum": 0.0 + } + }, + "additionalProperties": false, + "definitions": { + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } +} diff --git a/contracts/vesting/schema/raw/response_to_get_delegation_times.json b/contracts/vesting/schema/raw/response_to_get_delegation_times.json new file mode 100644 index 0000000000..0ac43e6513 --- /dev/null +++ b/contracts/vesting/schema/raw/response_to_get_delegation_times.json @@ -0,0 +1,50 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "DelegationTimesResponse", + "description": "Response containing timestamps of all delegations made towards particular mixnode by given vesting account.", + "type": "object", + "required": [ + "account_id", + "delegation_timestamps", + "mix_id", + "owner" + ], + "properties": { + "account_id": { + "description": "Id associated with this account", + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "delegation_timestamps": { + "description": "All timestamps where a delegation was made", + "type": "array", + "items": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "mix_id": { + "description": "Id of the mixnode towards which the delegation was made", + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "owner": { + "description": "Address of this account's owner", + "allOf": [ + { + "$ref": "#/definitions/Addr" + } + ] + } + }, + "additionalProperties": false, + "definitions": { + "Addr": { + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + } + } +} diff --git a/contracts/vesting/schema/raw/response_to_get_end_time.json b/contracts/vesting/schema/raw/response_to_get_end_time.json new file mode 100644 index 0000000000..6dd9971a14 --- /dev/null +++ b/contracts/vesting/schema/raw/response_to_get_end_time.json @@ -0,0 +1,16 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Timestamp", + "description": "A point in time in nanosecond precision.\n\nThis type can represent times from 1970-01-01T00:00:00Z to 2554-07-21T23:34:33Z.\n\n## Examples\n\n``` # use cosmwasm_std::Timestamp; let ts = Timestamp::from_nanos(1_000_000_202); assert_eq!(ts.nanos(), 1_000_000_202); assert_eq!(ts.seconds(), 1); assert_eq!(ts.subsec_nanos(), 202);\n\nlet ts = ts.plus_seconds(2); assert_eq!(ts.nanos(), 3_000_000_202); assert_eq!(ts.seconds(), 3); assert_eq!(ts.subsec_nanos(), 202); ```", + "allOf": [ + { + "$ref": "#/definitions/Uint64" + } + ], + "definitions": { + "Uint64": { + "description": "A thin wrapper around u64 that is using strings for JSON encoding/decoding, such that the full u64 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u64` to get the value out:\n\n``` # use cosmwasm_std::Uint64; let a = Uint64::from(42u64); assert_eq!(a.u64(), 42);\n\nlet b = Uint64::from(70u32); assert_eq!(b.u64(), 70); ```", + "type": "string" + } + } +} diff --git a/contracts/vesting/schema/raw/response_to_get_gateway.json b/contracts/vesting/schema/raw/response_to_get_gateway.json new file mode 100644 index 0000000000..1a18cd9f84 --- /dev/null +++ b/contracts/vesting/schema/raw/response_to_get_gateway.json @@ -0,0 +1,72 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Nullable_PledgeData", + "anyOf": [ + { + "$ref": "#/definitions/PledgeData" + }, + { + "type": "null" + } + ], + "definitions": { + "Coin": { + "type": "object", + "required": [ + "amount", + "denom" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "denom": { + "type": "string" + } + } + }, + "PledgeData": { + "description": "Information regarding pledge (i.e. mixnode or gateway bonding) made with vesting tokens.", + "type": "object", + "required": [ + "amount", + "block_time" + ], + "properties": { + "amount": { + "description": "The amount pledged.", + "allOf": [ + { + "$ref": "#/definitions/Coin" + } + ] + }, + "block_time": { + "description": "The block timestamp where the pledge occurred.", + "allOf": [ + { + "$ref": "#/definitions/Timestamp" + } + ] + } + }, + "additionalProperties": false + }, + "Timestamp": { + "description": "A point in time in nanosecond precision.\n\nThis type can represent times from 1970-01-01T00:00:00Z to 2554-07-21T23:34:33Z.\n\n## Examples\n\n``` # use cosmwasm_std::Timestamp; let ts = Timestamp::from_nanos(1_000_000_202); assert_eq!(ts.nanos(), 1_000_000_202); assert_eq!(ts.seconds(), 1); assert_eq!(ts.subsec_nanos(), 202);\n\nlet ts = ts.plus_seconds(2); assert_eq!(ts.nanos(), 3_000_000_202); assert_eq!(ts.seconds(), 3); assert_eq!(ts.subsec_nanos(), 202); ```", + "allOf": [ + { + "$ref": "#/definitions/Uint64" + } + ] + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + }, + "Uint64": { + "description": "A thin wrapper around u64 that is using strings for JSON encoding/decoding, such that the full u64 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u64` to get the value out:\n\n``` # use cosmwasm_std::Uint64; let a = Uint64::from(42u64); assert_eq!(a.u64(), 42);\n\nlet b = Uint64::from(70u32); assert_eq!(b.u64(), 70); ```", + "type": "string" + } + } +} diff --git a/contracts/vesting/schema/raw/response_to_get_historical_vesting_staking_reward.json b/contracts/vesting/schema/raw/response_to_get_historical_vesting_staking_reward.json new file mode 100644 index 0000000000..6e18ef9a9b --- /dev/null +++ b/contracts/vesting/schema/raw/response_to_get_historical_vesting_staking_reward.json @@ -0,0 +1,23 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Coin", + "type": "object", + "required": [ + "amount", + "denom" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "denom": { + "type": "string" + } + }, + "definitions": { + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } +} diff --git a/contracts/vesting/schema/raw/response_to_get_mixnode.json b/contracts/vesting/schema/raw/response_to_get_mixnode.json new file mode 100644 index 0000000000..1a18cd9f84 --- /dev/null +++ b/contracts/vesting/schema/raw/response_to_get_mixnode.json @@ -0,0 +1,72 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Nullable_PledgeData", + "anyOf": [ + { + "$ref": "#/definitions/PledgeData" + }, + { + "type": "null" + } + ], + "definitions": { + "Coin": { + "type": "object", + "required": [ + "amount", + "denom" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "denom": { + "type": "string" + } + } + }, + "PledgeData": { + "description": "Information regarding pledge (i.e. mixnode or gateway bonding) made with vesting tokens.", + "type": "object", + "required": [ + "amount", + "block_time" + ], + "properties": { + "amount": { + "description": "The amount pledged.", + "allOf": [ + { + "$ref": "#/definitions/Coin" + } + ] + }, + "block_time": { + "description": "The block timestamp where the pledge occurred.", + "allOf": [ + { + "$ref": "#/definitions/Timestamp" + } + ] + } + }, + "additionalProperties": false + }, + "Timestamp": { + "description": "A point in time in nanosecond precision.\n\nThis type can represent times from 1970-01-01T00:00:00Z to 2554-07-21T23:34:33Z.\n\n## Examples\n\n``` # use cosmwasm_std::Timestamp; let ts = Timestamp::from_nanos(1_000_000_202); assert_eq!(ts.nanos(), 1_000_000_202); assert_eq!(ts.seconds(), 1); assert_eq!(ts.subsec_nanos(), 202);\n\nlet ts = ts.plus_seconds(2); assert_eq!(ts.nanos(), 3_000_000_202); assert_eq!(ts.seconds(), 3); assert_eq!(ts.subsec_nanos(), 202); ```", + "allOf": [ + { + "$ref": "#/definitions/Uint64" + } + ] + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + }, + "Uint64": { + "description": "A thin wrapper around u64 that is using strings for JSON encoding/decoding, such that the full u64 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u64` to get the value out:\n\n``` # use cosmwasm_std::Uint64; let a = Uint64::from(42u64); assert_eq!(a.u64(), 42);\n\nlet b = Uint64::from(70u32); assert_eq!(b.u64(), 70); ```", + "type": "string" + } + } +} diff --git a/contracts/vesting/schema/raw/response_to_get_original_vesting.json b/contracts/vesting/schema/raw/response_to_get_original_vesting.json new file mode 100644 index 0000000000..0b0ced2dec --- /dev/null +++ b/contracts/vesting/schema/raw/response_to_get_original_vesting.json @@ -0,0 +1,55 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "OriginalVestingResponse", + "description": "Details about the original vesting specification used when the account was created.", + "type": "object", + "required": [ + "amount", + "number_of_periods", + "period_duration" + ], + "properties": { + "amount": { + "description": "The original amount that was used for the creation of this vesting account", + "allOf": [ + { + "$ref": "#/definitions/Coin" + } + ] + }, + "number_of_periods": { + "description": "The number of vesting periods that the account was created with", + "type": "integer", + "format": "uint", + "minimum": 0.0 + }, + "period_duration": { + "description": "Duration of each vesting period in seconds", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false, + "definitions": { + "Coin": { + "type": "object", + "required": [ + "amount", + "denom" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "denom": { + "type": "string" + } + } + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } +} diff --git a/contracts/vesting/schema/raw/response_to_get_pledged_coins.json b/contracts/vesting/schema/raw/response_to_get_pledged_coins.json new file mode 100644 index 0000000000..6e18ef9a9b --- /dev/null +++ b/contracts/vesting/schema/raw/response_to_get_pledged_coins.json @@ -0,0 +1,23 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Coin", + "type": "object", + "required": [ + "amount", + "denom" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "denom": { + "type": "string" + } + }, + "definitions": { + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } +} diff --git a/contracts/vesting/schema/raw/response_to_get_spendable_reward_coins.json b/contracts/vesting/schema/raw/response_to_get_spendable_reward_coins.json new file mode 100644 index 0000000000..6e18ef9a9b --- /dev/null +++ b/contracts/vesting/schema/raw/response_to_get_spendable_reward_coins.json @@ -0,0 +1,23 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Coin", + "type": "object", + "required": [ + "amount", + "denom" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "denom": { + "type": "string" + } + }, + "definitions": { + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } +} diff --git a/contracts/vesting/schema/raw/response_to_get_spendable_vested_coins.json b/contracts/vesting/schema/raw/response_to_get_spendable_vested_coins.json new file mode 100644 index 0000000000..6e18ef9a9b --- /dev/null +++ b/contracts/vesting/schema/raw/response_to_get_spendable_vested_coins.json @@ -0,0 +1,23 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Coin", + "type": "object", + "required": [ + "amount", + "denom" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "denom": { + "type": "string" + } + }, + "definitions": { + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } +} diff --git a/contracts/vesting/schema/raw/response_to_get_staked_coins.json b/contracts/vesting/schema/raw/response_to_get_staked_coins.json new file mode 100644 index 0000000000..6e18ef9a9b --- /dev/null +++ b/contracts/vesting/schema/raw/response_to_get_staked_coins.json @@ -0,0 +1,23 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Coin", + "type": "object", + "required": [ + "amount", + "denom" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "denom": { + "type": "string" + } + }, + "definitions": { + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } +} diff --git a/contracts/vesting/schema/raw/response_to_get_start_time.json b/contracts/vesting/schema/raw/response_to_get_start_time.json new file mode 100644 index 0000000000..6dd9971a14 --- /dev/null +++ b/contracts/vesting/schema/raw/response_to_get_start_time.json @@ -0,0 +1,16 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Timestamp", + "description": "A point in time in nanosecond precision.\n\nThis type can represent times from 1970-01-01T00:00:00Z to 2554-07-21T23:34:33Z.\n\n## Examples\n\n``` # use cosmwasm_std::Timestamp; let ts = Timestamp::from_nanos(1_000_000_202); assert_eq!(ts.nanos(), 1_000_000_202); assert_eq!(ts.seconds(), 1); assert_eq!(ts.subsec_nanos(), 202);\n\nlet ts = ts.plus_seconds(2); assert_eq!(ts.nanos(), 3_000_000_202); assert_eq!(ts.seconds(), 3); assert_eq!(ts.subsec_nanos(), 202); ```", + "allOf": [ + { + "$ref": "#/definitions/Uint64" + } + ], + "definitions": { + "Uint64": { + "description": "A thin wrapper around u64 that is using strings for JSON encoding/decoding, such that the full u64 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u64` to get the value out:\n\n``` # use cosmwasm_std::Uint64; let a = Uint64::from(42u64); assert_eq!(a.u64(), 42);\n\nlet b = Uint64::from(70u32); assert_eq!(b.u64(), 70); ```", + "type": "string" + } + } +} diff --git a/contracts/vesting/schema/raw/response_to_get_total_delegation_amount.json b/contracts/vesting/schema/raw/response_to_get_total_delegation_amount.json new file mode 100644 index 0000000000..6e18ef9a9b --- /dev/null +++ b/contracts/vesting/schema/raw/response_to_get_total_delegation_amount.json @@ -0,0 +1,23 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Coin", + "type": "object", + "required": [ + "amount", + "denom" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "denom": { + "type": "string" + } + }, + "definitions": { + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } +} diff --git a/contracts/vesting/schema/raw/response_to_get_vested_coins.json b/contracts/vesting/schema/raw/response_to_get_vested_coins.json new file mode 100644 index 0000000000..6e18ef9a9b --- /dev/null +++ b/contracts/vesting/schema/raw/response_to_get_vested_coins.json @@ -0,0 +1,23 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Coin", + "type": "object", + "required": [ + "amount", + "denom" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "denom": { + "type": "string" + } + }, + "definitions": { + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } +} diff --git a/contracts/vesting/schema/raw/response_to_get_vesting_coins.json b/contracts/vesting/schema/raw/response_to_get_vesting_coins.json new file mode 100644 index 0000000000..6e18ef9a9b --- /dev/null +++ b/contracts/vesting/schema/raw/response_to_get_vesting_coins.json @@ -0,0 +1,23 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Coin", + "type": "object", + "required": [ + "amount", + "denom" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "denom": { + "type": "string" + } + }, + "definitions": { + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } +} diff --git a/contracts/vesting/schema/raw/response_to_get_withdrawn_coins.json b/contracts/vesting/schema/raw/response_to_get_withdrawn_coins.json new file mode 100644 index 0000000000..6e18ef9a9b --- /dev/null +++ b/contracts/vesting/schema/raw/response_to_get_withdrawn_coins.json @@ -0,0 +1,23 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Coin", + "type": "object", + "required": [ + "amount", + "denom" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "denom": { + "type": "string" + } + }, + "definitions": { + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } +} diff --git a/contracts/vesting/schema/raw/response_to_locked_coins.json b/contracts/vesting/schema/raw/response_to_locked_coins.json new file mode 100644 index 0000000000..6e18ef9a9b --- /dev/null +++ b/contracts/vesting/schema/raw/response_to_locked_coins.json @@ -0,0 +1,23 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Coin", + "type": "object", + "required": [ + "amount", + "denom" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "denom": { + "type": "string" + } + }, + "definitions": { + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } +} diff --git a/contracts/vesting/schema/raw/response_to_spendable_coins.json b/contracts/vesting/schema/raw/response_to_spendable_coins.json new file mode 100644 index 0000000000..6e18ef9a9b --- /dev/null +++ b/contracts/vesting/schema/raw/response_to_spendable_coins.json @@ -0,0 +1,23 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Coin", + "type": "object", + "required": [ + "amount", + "denom" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "denom": { + "type": "string" + } + }, + "definitions": { + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } +} diff --git a/contracts/vesting/src/bin/schema.rs b/contracts/vesting/src/bin/schema.rs new file mode 100644 index 0000000000..fc95866f6d --- /dev/null +++ b/contracts/vesting/src/bin/schema.rs @@ -0,0 +1,14 @@ +// Copyright 2023 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use cosmwasm_schema::write_api; +use vesting_contract_common::{ExecuteMsg, InitMsg, MigrateMsg, QueryMsg}; + +fn main() { + write_api! { + instantiate: InitMsg, + query: QueryMsg, + execute: ExecuteMsg, + migrate: MigrateMsg, + } +} diff --git a/contracts/vesting/src/contract.rs b/contracts/vesting/src/contract.rs index 54db2d9464..31d3c21b06 100644 --- a/contracts/vesting/src/contract.rs +++ b/contracts/vesting/src/contract.rs @@ -1,14 +1,13 @@ -use crate::errors::ContractError; pub use crate::queries::*; use crate::storage::{ADMIN, MIXNET_CONTRACT_ADDRESS, MIX_DENOM}; pub use crate::transactions::*; -use crate::vesting::Account; use cosmwasm_std::{ entry_point, to_binary, Addr, Coin, Deps, DepsMut, Env, MessageInfo, QueryResponse, Response, Uint128, }; use semver::Version; use vesting_contract_common::messages::{ExecuteMsg, InitMsg, MigrateMsg, QueryMsg}; +use vesting_contract_common::{Account, VestingContractError}; // version info for migration info const CONTRACT_NAME: &str = "crate:nym-vesting-contract"; @@ -36,7 +35,7 @@ pub fn instantiate( _env: Env, info: MessageInfo, msg: InitMsg, -) -> Result { +) -> Result { // validate the received mixnet contract address let mixnet_contract_address = deps.api.addr_validate(&msg.mixnet_contract_address)?; @@ -51,21 +50,24 @@ pub fn instantiate( } #[entry_point] -pub fn migrate(deps: DepsMut<'_>, _env: Env, _msg: MigrateMsg) -> Result { +pub fn migrate( + deps: DepsMut<'_>, + _env: Env, + _msg: MigrateMsg, +) -> Result { // note: don't remove this particular bit of code as we have to ALWAYS check whether we have to update the stored version - let version: Version = - CONTRACT_VERSION - .parse() - .map_err(|error: semver::Error| ContractError::SemVerFailure { - value: CONTRACT_VERSION.to_string(), - error_message: error.to_string(), - })?; + let version: Version = CONTRACT_VERSION.parse().map_err(|error: semver::Error| { + VestingContractError::SemVerFailure { + value: CONTRACT_VERSION.to_string(), + error_message: error.to_string(), + } + })?; let storage_version_raw = cw2::get_contract_version(deps.storage)?.version; let storage_version: Version = storage_version_raw .parse() - .map_err(|error: semver::Error| ContractError::SemVerFailure { + .map_err(|error: semver::Error| VestingContractError::SemVerFailure { value: storage_version_raw, error_message: error.to_string(), })?; @@ -86,7 +88,7 @@ pub fn execute( env: Env, info: MessageInfo, msg: ExecuteMsg, -) -> Result { +) -> Result { match msg { ExecuteMsg::CreateFamily { label } => try_create_family(info, deps, label), ExecuteMsg::JoinFamily { @@ -190,7 +192,11 @@ pub fn execute( } #[entry_point] -pub fn query(deps: Deps<'_>, env: Env, msg: QueryMsg) -> Result { +pub fn query( + deps: Deps<'_>, + env: Env, + msg: QueryMsg, +) -> Result { let query_res = match msg { QueryMsg::GetContractVersion {} => to_binary(&get_contract_version()), QueryMsg::GetCW2ContractVersion {} => to_binary(&cw2::get_contract_version(deps.storage)?), @@ -314,17 +320,23 @@ pub fn query(deps: Deps<'_>, env: Env, msg: QueryMsg) -> Result Result { +pub(crate) fn validate_funds( + funds: &[Coin], + mix_denom: String, +) -> Result { if funds.is_empty() || funds[0].amount.is_zero() { - return Err(ContractError::EmptyFunds); + return Err(VestingContractError::EmptyFunds); } if funds.len() > 1 { - return Err(ContractError::MultipleDenoms); + return Err(VestingContractError::MultipleDenoms); } if funds[0].denom != mix_denom { - return Err(ContractError::WrongDenom(funds[0].denom.clone(), mix_denom)); + return Err(VestingContractError::WrongDenom( + funds[0].denom.clone(), + mix_denom, + )); } Ok(funds[0].clone()) @@ -333,13 +345,13 @@ pub(crate) fn validate_funds(funds: &[Coin], mix_denom: String) -> Result Result<(), ContractError> { +) -> Result<(), VestingContractError> { if let Some(staking_address) = account.staking_address() { if staking_address == addr { return Ok(()); } } - Err(ContractError::InvalidStakingAccount { + Err(VestingContractError::InvalidStakingAccount { address: addr.clone(), for_account: account.owner_address(), }) diff --git a/contracts/vesting/src/lib.rs b/contracts/vesting/src/lib.rs index d2238d4d15..527a9ffa53 100644 --- a/contracts/vesting/src/lib.rs +++ b/contracts/vesting/src/lib.rs @@ -5,7 +5,6 @@ #![warn(clippy::unwrap_used)] pub mod contract; -pub mod errors; mod queries; mod queued_migrations; mod storage; diff --git a/contracts/vesting/src/queries.rs b/contracts/vesting/src/queries.rs index a33bb84256..c3c9c0d99d 100644 --- a/contracts/vesting/src/queries.rs +++ b/contracts/vesting/src/queries.rs @@ -1,19 +1,18 @@ // Copyright 2021-2023 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use crate::errors::ContractError; use crate::storage; use crate::storage::{account_from_address, BlockTimestampSecs, ACCOUNTS, DELEGATIONS, MIX_DENOM}; use crate::traits::VestingAccount; -use crate::vesting::Account; +use crate::vesting::StorableVestingAccountExt; use contracts_common::ContractBuildInformation; use cosmwasm_std::{Coin, Deps, Env, Order, StdResult, Timestamp, Uint128}; use cw_storage_plus::Bound; use mixnet_contract_common::MixId; use vesting_contract_common::{ - AccountVestingCoins, AccountsResponse, AllDelegationsResponse, BaseVestingAccountInfo, + Account, AccountVestingCoins, AccountsResponse, AllDelegationsResponse, BaseVestingAccountInfo, DelegationTimesResponse, OriginalVestingResponse, Period, PledgeData, VestingCoinsResponse, - VestingDelegation, + VestingContractError, VestingDelegation, }; /// Get current vesting period for a given [crate::vesting::Account]. @@ -21,24 +20,30 @@ pub fn try_get_current_vesting_period( address: &str, deps: Deps<'_>, env: Env, -) -> Result { +) -> Result { let account = account_from_address(address, deps.storage, deps.api)?; account.get_current_vesting_period(env.block.time) } /// Loads mixnode bond from vesting contract storage. -pub fn try_get_mixnode(address: &str, deps: Deps<'_>) -> Result, ContractError> { +pub fn try_get_mixnode( + address: &str, + deps: Deps<'_>, +) -> Result, VestingContractError> { let account = account_from_address(address, deps.storage, deps.api)?; account.load_mixnode_pledge(deps.storage) } /// Loads gateway bond from vesting contract storage. -pub fn try_get_gateway(address: &str, deps: Deps<'_>) -> Result, ContractError> { +pub fn try_get_gateway( + address: &str, + deps: Deps<'_>, +) -> Result, VestingContractError> { let account = account_from_address(address, deps.storage, deps.api)?; account.load_gateway_pledge(deps.storage) } -pub fn try_get_account(address: &str, deps: Deps<'_>) -> Result { +pub fn try_get_account(address: &str, deps: Deps<'_>) -> Result { account_from_address(address, deps.storage, deps.api) } @@ -65,7 +70,7 @@ pub fn try_get_all_accounts( deps: Deps<'_>, start_after: Option, limit: Option, -) -> Result { +) -> Result { let limit = limit.unwrap_or(150).min(250) as usize; let start = start_after @@ -96,7 +101,7 @@ pub fn try_get_all_accounts_vesting_coins( env: Env, start_after: Option, limit: Option, -) -> Result { +) -> Result { let limit = limit.unwrap_or(150).min(250) as usize; let start = start_after @@ -133,7 +138,7 @@ pub fn try_get_locked_coins( block_time: Option, env: Env, deps: Deps<'_>, -) -> Result { +) -> Result { let account = account_from_address(vesting_account_address, deps.storage, deps.api)?; account.locked_coins(block_time, &env, deps.storage) } @@ -144,7 +149,7 @@ pub fn try_get_spendable_coins( block_time: Option, env: Env, deps: Deps<'_>, -) -> Result { +) -> Result { let account = account_from_address(vesting_account_address, deps.storage, deps.api)?; account.spendable_coins(block_time, &env, deps.storage) } @@ -155,7 +160,7 @@ pub fn try_get_vested_coins( block_time: Option, env: Env, deps: Deps<'_>, -) -> Result { +) -> Result { let account = account_from_address(vesting_account_address, deps.storage, deps.api)?; account.get_vested_coins(block_time, &env, deps.storage) } @@ -166,7 +171,7 @@ pub fn try_get_vesting_coins( block_time: Option, env: Env, deps: Deps<'_>, -) -> Result { +) -> Result { let account = account_from_address(vesting_account_address, deps.storage, deps.api)?; account.get_vesting_coins(block_time, &env, deps.storage) } @@ -175,7 +180,7 @@ pub fn try_get_vesting_coins( pub fn try_get_start_time( vesting_account_address: &str, deps: Deps<'_>, -) -> Result { +) -> Result { let account = account_from_address(vesting_account_address, deps.storage, deps.api)?; Ok(account.get_start_time()) } @@ -184,7 +189,7 @@ pub fn try_get_start_time( pub fn try_get_end_time( vesting_account_address: &str, deps: Deps<'_>, -) -> Result { +) -> Result { let account = account_from_address(vesting_account_address, deps.storage, deps.api)?; Ok(account.get_end_time()) } @@ -193,7 +198,7 @@ pub fn try_get_end_time( pub fn try_get_original_vesting( vesting_account_address: &str, deps: Deps<'_>, -) -> Result { +) -> Result { let account = account_from_address(vesting_account_address, deps.storage, deps.api)?; account.get_original_vesting() } @@ -201,7 +206,7 @@ pub fn try_get_original_vesting( pub fn try_get_historical_vesting_staking_reward( vesting_account_address: &str, deps: Deps<'_>, -) -> Result { +) -> Result { let account = account_from_address(vesting_account_address, deps.storage, deps.api)?; account.get_historical_vested_staking_rewards(deps.storage) } @@ -210,7 +215,7 @@ pub fn try_get_spendable_vested_coins( vesting_account_address: &str, deps: Deps<'_>, env: Env, -) -> Result { +) -> Result { let account = account_from_address(vesting_account_address, deps.storage, deps.api)?; account.spendable_vested_coins(None, &env, deps.storage) } @@ -219,7 +224,7 @@ pub fn try_get_spendable_reward_coins( vesting_account_address: &str, deps: Deps<'_>, env: Env, -) -> Result { +) -> Result { let account = account_from_address(vesting_account_address, deps.storage, deps.api)?; account.spendable_reward_coins(None, &env, deps.storage) } @@ -227,7 +232,7 @@ pub fn try_get_spendable_reward_coins( pub fn try_get_delegated_coins( vesting_account_address: &str, deps: Deps<'_>, -) -> Result { +) -> Result { let account = account_from_address(vesting_account_address, deps.storage, deps.api)?; let denom = MIX_DENOM.load(deps.storage)?; let amount = account.total_delegations(deps.storage)?; @@ -237,7 +242,7 @@ pub fn try_get_delegated_coins( pub fn try_get_pledged_coins( vesting_account_address: &str, deps: Deps<'_>, -) -> Result { +) -> Result { let account = account_from_address(vesting_account_address, deps.storage, deps.api)?; let denom = MIX_DENOM.load(deps.storage)?; let amount = account.total_pledged(deps.storage)?; @@ -247,7 +252,7 @@ pub fn try_get_pledged_coins( pub fn try_get_staked_coins( vesting_account_address: &str, deps: Deps<'_>, -) -> Result { +) -> Result { let account = account_from_address(vesting_account_address, deps.storage, deps.api)?; let denom = MIX_DENOM.load(deps.storage)?; let amount = account.total_staked(deps.storage)?; @@ -257,7 +262,7 @@ pub fn try_get_staked_coins( pub fn try_get_withdrawn_coins( vesting_account_address: &str, deps: Deps<'_>, -) -> Result { +) -> Result { let account = account_from_address(vesting_account_address, deps.storage, deps.api)?; let denom = MIX_DENOM.load(deps.storage)?; let amount = account.load_withdrawn(deps.storage)?; @@ -269,7 +274,7 @@ pub fn try_get_delegation_times( deps: Deps<'_>, vesting_account_address: &str, mix_id: MixId, -) -> Result { +) -> Result { let owner = deps.api.addr_validate(vesting_account_address)?; let account = account_from_address(vesting_account_address, deps.storage, deps.api)?; @@ -288,7 +293,7 @@ pub fn try_get_all_delegations( deps: Deps<'_>, start_after: Option<(u32, MixId, BlockTimestampSecs)>, limit: Option, -) -> Result { +) -> Result { let limit = limit.unwrap_or(100).min(200) as usize; let start = start_after.map(Bound::exclusive); @@ -325,7 +330,7 @@ pub fn try_get_delegation( vesting_account_address: &str, mix_id: MixId, block_timestamp_secs: BlockTimestampSecs, -) -> Result { +) -> Result { let account = account_from_address(vesting_account_address, deps.storage, deps.api)?; let storage_key = (account.storage_key(), mix_id, block_timestamp_secs); @@ -343,7 +348,7 @@ pub fn try_get_delegation_amount( deps: Deps<'_>, vesting_account_address: &str, mix_id: MixId, -) -> Result { +) -> Result { let account = account_from_address(vesting_account_address, deps.storage, deps.api)?; let amount = DELEGATIONS diff --git a/contracts/vesting/src/storage.rs b/contracts/vesting/src/storage.rs index eb49d3c434..d6d363d36f 100644 --- a/contracts/vesting/src/storage.rs +++ b/contracts/vesting/src/storage.rs @@ -1,16 +1,14 @@ -use crate::errors::ContractError; -use crate::vesting::Account; use cosmwasm_std::{Addr, Api, Storage, Uint128}; use cosmwasm_std::{Coin, Order}; use cw_storage_plus::{Item, Map}; use mixnet_contract_common::{IdentityKey, MixId}; -use vesting_contract_common::PledgeData; +use vesting_contract_common::account::VestingAccountStorageKey; +use vesting_contract_common::{Account, PledgeData, VestingContractError}; pub(crate) type BlockTimestampSecs = u64; -pub(crate) type AccountStorageKey = u32; /// Counter for the unique, monotonically increasing storage key id for the vesting account data. -pub const KEY: Item<'_, AccountStorageKey> = Item::new("key"); +pub const KEY: Item<'_, VestingAccountStorageKey> = Item::new("key"); /// Storage map containing vesting account information associated with particular owner address. pub const ACCOUNTS: Map<'_, Addr, Account> = Map::new("acc"); @@ -18,28 +16,31 @@ pub const ACCOUNTS: Map<'_, Addr, Account> = Map::new("acc"); /// Storage map containing information about amount of tokens associated with particular vesting account /// that are currently present in the contract (and have not been withdrawn or staked in the mixnet contract) // note: this assumes I understood the intent behind this correctly -const BALANCES: Map<'_, AccountStorageKey, Uint128> = Map::new("blc"); +const BALANCES: Map<'_, VestingAccountStorageKey, Uint128> = Map::new("blc"); /// Storage map containing information about amount of tokens withdrawn from the contract by a particular vesting account. -const WITHDRAWNS: Map<'_, AccountStorageKey, Uint128> = Map::new("wthd"); +const WITHDRAWNS: Map<'_, VestingAccountStorageKey, Uint128> = Map::new("wthd"); /// Storage map containing information about amount of tokens pledged towards bonding mixnodes /// in the mixnet contract using a particular vesting account. -const BOND_PLEDGES: Map<'_, AccountStorageKey, PledgeData> = Map::new("bnd"); +const BOND_PLEDGES: Map<'_, VestingAccountStorageKey, PledgeData> = Map::new("bnd"); /// Storage map containing information about amount of tokens pledged towards bonding gateways /// in the mixnet contract using a particular vesting account. -const GATEWAY_PLEDGES: Map<'_, AccountStorageKey, PledgeData> = Map::new("gtw"); +const GATEWAY_PLEDGES: Map<'_, VestingAccountStorageKey, PledgeData> = Map::new("gtw"); /// Old, pre-v2 migration, storage map that used to contain information about tokens delegated /// towards particular mixnodes in the mixnet contract with given vesting account. /// It should be completely empty. -pub const _OLD_DELEGATIONS: Map<'_, (AccountStorageKey, IdentityKey, BlockTimestampSecs), Uint128> = - Map::new("dlg"); +pub const _OLD_DELEGATIONS: Map< + '_, + (VestingAccountStorageKey, IdentityKey, BlockTimestampSecs), + Uint128, +> = Map::new("dlg"); /// Storage map containing information about tokens delegated towards particular mixnodes /// in the mixnet contract with given vesting account. -pub const DELEGATIONS: Map<'_, (AccountStorageKey, MixId, BlockTimestampSecs), Uint128> = +pub const DELEGATIONS: Map<'_, (VestingAccountStorageKey, MixId, BlockTimestampSecs), Uint128> = Map::new("dlg_v2"); /// Explicit contract admin that is allowed, among other things, to create new vesting accounts. @@ -52,10 +53,10 @@ pub const MIXNET_CONTRACT_ADDRESS: Item<'_, Addr> = Item::new("mix"); pub const MIX_DENOM: Item<'_, String> = Item::new("den"); pub fn save_delegation( - key: (AccountStorageKey, MixId, BlockTimestampSecs), + key: (VestingAccountStorageKey, MixId, BlockTimestampSecs), amount: Uint128, storage: &mut dyn Storage, -) -> Result<(), ContractError> { +) -> Result<(), VestingContractError> { let existing_delegation_amount = if let Some(delegation) = DELEGATIONS.may_load(storage, key)? { delegation } else { @@ -67,17 +68,17 @@ pub fn save_delegation( } pub fn remove_delegation( - key: (AccountStorageKey, MixId, BlockTimestampSecs), + key: (VestingAccountStorageKey, MixId, BlockTimestampSecs), storage: &mut dyn Storage, -) -> Result<(), ContractError> { +) -> Result<(), VestingContractError> { DELEGATIONS.remove(storage, key); Ok(()) } pub fn load_delegation_timestamps( - prefix: (AccountStorageKey, MixId), + prefix: (VestingAccountStorageKey, MixId), storage: &dyn Storage, -) -> Result, ContractError> { +) -> Result, VestingContractError> { let block_timestamps = DELEGATIONS .prefix(prefix) .keys(storage, None, None, Order::Ascending) @@ -86,7 +87,7 @@ pub fn load_delegation_timestamps( } pub fn count_subdelegations_for_mix( - prefix: (AccountStorageKey, MixId), + prefix: (VestingAccountStorageKey, MixId), storage: &dyn Storage, ) -> u32 { DELEGATIONS @@ -96,9 +97,9 @@ pub fn count_subdelegations_for_mix( } pub fn load_withdrawn( - key: AccountStorageKey, + key: VestingAccountStorageKey, storage: &dyn Storage, -) -> Result { +) -> Result { Ok(WITHDRAWNS .may_load(storage, key) .unwrap_or(None) @@ -106,9 +107,9 @@ pub fn load_withdrawn( } pub fn load_balance( - key: AccountStorageKey, + key: VestingAccountStorageKey, storage: &dyn Storage, -) -> Result { +) -> Result { Ok(BALANCES .may_load(storage, key) .unwrap_or(None) @@ -116,57 +117,57 @@ pub fn load_balance( } pub fn save_balance( - key: AccountStorageKey, + key: VestingAccountStorageKey, value: Uint128, storage: &mut dyn Storage, -) -> Result<(), ContractError> { +) -> Result<(), VestingContractError> { BALANCES.save(storage, key, &value)?; Ok(()) } pub fn save_withdrawn( - key: AccountStorageKey, + key: VestingAccountStorageKey, value: Uint128, storage: &mut dyn Storage, -) -> Result<(), ContractError> { +) -> Result<(), VestingContractError> { WITHDRAWNS.save(storage, key, &value)?; Ok(()) } pub fn load_bond_pledge( - key: AccountStorageKey, + key: VestingAccountStorageKey, storage: &dyn Storage, -) -> Result, ContractError> { +) -> Result, VestingContractError> { Ok(BOND_PLEDGES.may_load(storage, key).unwrap_or(None)) } pub fn remove_bond_pledge( - key: AccountStorageKey, + key: VestingAccountStorageKey, storage: &mut dyn Storage, -) -> Result<(), ContractError> { +) -> Result<(), VestingContractError> { BOND_PLEDGES.remove(storage, key); Ok(()) } pub fn save_bond_pledge( - key: AccountStorageKey, + key: VestingAccountStorageKey, value: &PledgeData, storage: &mut dyn Storage, -) -> Result<(), ContractError> { +) -> Result<(), VestingContractError> { BOND_PLEDGES.save(storage, key, value)?; Ok(()) } pub fn decrease_bond_pledge( - key: AccountStorageKey, + key: VestingAccountStorageKey, amount: Coin, storage: &mut dyn Storage, -) -> Result<(), ContractError> { +) -> Result<(), VestingContractError> { let mut existing = BOND_PLEDGES.load(storage, key)?; if existing.amount.amount <= amount.amount { // this shouldn't be possible! // (but check for it anyway... just in case) - return Err(ContractError::InvalidBondPledgeReduction { + return Err(VestingContractError::InvalidBondPledgeReduction { current: existing.amount, decrease_by: amount, }); @@ -176,30 +177,33 @@ pub fn decrease_bond_pledge( } pub fn load_gateway_pledge( - key: AccountStorageKey, + key: VestingAccountStorageKey, storage: &dyn Storage, -) -> Result, ContractError> { +) -> Result, VestingContractError> { Ok(GATEWAY_PLEDGES.may_load(storage, key).unwrap_or(None)) } pub fn save_gateway_pledge( - key: AccountStorageKey, + key: VestingAccountStorageKey, value: &PledgeData, storage: &mut dyn Storage, -) -> Result<(), ContractError> { +) -> Result<(), VestingContractError> { GATEWAY_PLEDGES.save(storage, key, value)?; Ok(()) } pub fn remove_gateway_pledge( - key: AccountStorageKey, + key: VestingAccountStorageKey, storage: &mut dyn Storage, -) -> Result<(), ContractError> { +) -> Result<(), VestingContractError> { GATEWAY_PLEDGES.remove(storage, key); Ok(()) } -pub fn save_account(account: &Account, storage: &mut dyn Storage) -> Result<(), ContractError> { +pub fn save_account( + account: &Account, + storage: &mut dyn Storage, +) -> Result<(), VestingContractError> { ACCOUNTS.save(storage, account.owner_address(), account)?; Ok(()) } @@ -207,24 +211,27 @@ pub fn save_account(account: &Account, storage: &mut dyn Storage) -> Result<(), pub fn load_account( address: Addr, storage: &dyn Storage, -) -> Result, ContractError> { +) -> Result, VestingContractError> { Ok(ACCOUNTS.may_load(storage, address).unwrap_or(None)) } -pub fn delete_account(address: Addr, storage: &mut dyn Storage) -> Result<(), ContractError> { +pub fn delete_account( + address: Addr, + storage: &mut dyn Storage, +) -> Result<(), VestingContractError> { ACCOUNTS.remove(storage, address); Ok(()) } -fn validate_account(address: Addr, storage: &dyn Storage) -> Result { +fn validate_account(address: Addr, storage: &dyn Storage) -> Result { load_account(address.clone(), storage)? - .ok_or_else(|| ContractError::NoAccountForAddress(address.into_string())) + .ok_or_else(|| VestingContractError::NoAccountForAddress(address.into_string())) } pub fn account_from_address( address: &str, storage: &dyn Storage, api: &dyn Api, -) -> Result { +) -> Result { validate_account(api.addr_validate(address)?, storage) } diff --git a/contracts/vesting/src/support/tests.rs b/contracts/vesting/src/support/tests.rs index 6f48fe6940..98995b1c8e 100644 --- a/contracts/vesting/src/support/tests.rs +++ b/contracts/vesting/src/support/tests.rs @@ -72,7 +72,7 @@ pub mod helpers { use crate::storage::{ACCOUNTS, ADMIN, MIXNET_CONTRACT_ADDRESS, MIX_DENOM}; use crate::support::tests::helpers::state_dump_decoder::RawState; use crate::traits::VestingAccount; - use crate::vesting::{populate_vesting_periods, Account}; + use crate::vesting::{populate_vesting_periods, StorableVestingAccountExt}; use contracts_common::Percent; use cosmwasm_std::testing::{mock_dependencies, mock_env, mock_info, MockApi, MockQuerier}; use cosmwasm_std::{ @@ -83,8 +83,8 @@ pub mod helpers { use rand_chacha::ChaCha20Rng; use std::path::Path; use std::str::FromStr; - use vesting_contract_common::messages::{InitMsg, VestingSpecification}; - use vesting_contract_common::PledgeCap; + use vesting_contract_common::messages::InitMsg; + use vesting_contract_common::{Account, PledgeCap, VestingSpecification}; // use rng with constant seed for all tests so that they would be deterministic #[allow(unused)] @@ -337,7 +337,7 @@ pub mod helpers { VestingSpecification::new(None, Some(3600), None), ); - Account::new( + Account::save_new( Addr::unchecked("owner"), Some(Addr::unchecked("staking")), Coin { @@ -357,7 +357,7 @@ pub mod helpers { let periods = populate_vesting_periods(start_time.seconds(), VestingSpecification::default()); - Account::new( + Account::save_new( Addr::unchecked("owner"), Some(Addr::unchecked("staking")), Coin { @@ -377,7 +377,7 @@ pub mod helpers { let periods = populate_vesting_periods(start_time.seconds(), VestingSpecification::default()); - Account::new( + Account::save_new( Addr::unchecked("owner"), Some(Addr::unchecked("staking")), Coin { diff --git a/contracts/vesting/src/traits/bonding_account.rs b/contracts/vesting/src/traits/bonding_account.rs index bf5cac3bf5..e5e415e30c 100644 --- a/contracts/vesting/src/traits/bonding_account.rs +++ b/contracts/vesting/src/traits/bonding_account.rs @@ -1,4 +1,3 @@ -use crate::errors::ContractError; use contracts_common::signing::MessageSignature; use cosmwasm_std::{Coin, Env, Response, Storage}; use mixnet_contract_common::{ @@ -6,9 +5,13 @@ use mixnet_contract_common::{ mixnode::{MixNodeConfigUpdate, MixNodeCostParams}, Gateway, MixNode, }; +use vesting_contract_common::VestingContractError; pub trait MixnodeBondingAccount { - fn try_claim_operator_reward(&self, storage: &dyn Storage) -> Result; + fn try_claim_operator_reward( + &self, + storage: &dyn Storage, + ) -> Result; fn try_bond_mixnode( &self, @@ -18,46 +21,46 @@ pub trait MixnodeBondingAccount { pledge: Coin, env: &Env, storage: &mut dyn Storage, - ) -> Result; + ) -> Result; fn try_pledge_additional_tokens( &self, additional_pledge: Coin, env: &Env, storage: &mut dyn Storage, - ) -> Result; + ) -> Result; fn try_decrease_mixnode_pledge( &self, amount: Coin, storage: &mut dyn Storage, - ) -> Result; + ) -> Result; - fn try_unbond_mixnode(&self, storage: &dyn Storage) -> Result; + fn try_unbond_mixnode(&self, storage: &dyn Storage) -> Result; fn try_track_unbond_mixnode( &self, amount: Coin, storage: &mut dyn Storage, - ) -> Result<(), ContractError>; + ) -> Result<(), VestingContractError>; fn try_track_decrease_mixnode_pledge( &self, amount: Coin, storage: &mut dyn Storage, - ) -> Result<(), ContractError>; + ) -> Result<(), VestingContractError>; fn try_update_mixnode_config( &self, new_config: MixNodeConfigUpdate, storage: &mut dyn Storage, - ) -> Result; + ) -> Result; fn try_update_mixnode_cost_params( &self, new_costs: MixNodeCostParams, storage: &mut dyn Storage, - ) -> Result; + ) -> Result; } pub trait GatewayBondingAccount { @@ -68,19 +71,19 @@ pub trait GatewayBondingAccount { pledge: Coin, env: &Env, storage: &mut dyn Storage, - ) -> Result; + ) -> Result; - fn try_unbond_gateway(&self, storage: &dyn Storage) -> Result; + fn try_unbond_gateway(&self, storage: &dyn Storage) -> Result; fn try_track_unbond_gateway( &self, amount: Coin, storage: &mut dyn Storage, - ) -> Result<(), ContractError>; + ) -> Result<(), VestingContractError>; fn try_update_gateway_config( &self, new_config: GatewayConfigUpdate, storage: &mut dyn Storage, - ) -> Result; + ) -> Result; } diff --git a/contracts/vesting/src/traits/delegating_account.rs b/contracts/vesting/src/traits/delegating_account.rs index 4beefabe28..629cfee050 100644 --- a/contracts/vesting/src/traits/delegating_account.rs +++ b/contracts/vesting/src/traits/delegating_account.rs @@ -1,13 +1,13 @@ -use crate::errors::ContractError; use cosmwasm_std::{Coin, Env, Response, Storage, Uint128}; use mixnet_contract_common::MixId; +use vesting_contract_common::VestingContractError; pub trait DelegatingAccount { fn try_claim_delegator_reward( &self, mix_id: MixId, storage: &dyn Storage, - ) -> Result; + ) -> Result; fn try_delegate_to_mixnode( &self, @@ -15,13 +15,13 @@ pub trait DelegatingAccount { amount: Coin, env: &Env, storage: &mut dyn Storage, - ) -> Result; + ) -> Result; fn try_undelegate_from_mixnode( &self, mix_id: MixId, storage: &dyn Storage, - ) -> Result; + ) -> Result; // track_delegation performs internal vesting accounting necessary when // delegating from a vesting account. It accepts the current block height, the @@ -35,7 +35,7 @@ pub trait DelegatingAccount { current_balance: Uint128, delegation: Coin, storage: &mut dyn Storage, - ) -> Result<(), ContractError>; + ) -> Result<(), VestingContractError>; // track_undelegation performs internal vesting accounting necessary when a // vesting account performs an undelegation. fn track_undelegation( @@ -43,5 +43,5 @@ pub trait DelegatingAccount { mix_id: MixId, amount: Coin, storage: &mut dyn Storage, - ) -> Result<(), ContractError>; + ) -> Result<(), VestingContractError>; } diff --git a/contracts/vesting/src/traits/node_families.rs b/contracts/vesting/src/traits/node_families.rs index 163308d30a..f5af460d17 100644 --- a/contracts/vesting/src/traits/node_families.rs +++ b/contracts/vesting/src/traits/node_families.rs @@ -1,32 +1,32 @@ -use crate::errors::ContractError; use contracts_common::signing::MessageSignature; use cosmwasm_std::{Response, Storage}; use mixnet_contract_common::families::FamilyHead; use mixnet_contract_common::IdentityKeyRef; +use vesting_contract_common::VestingContractError; pub trait NodeFamilies { fn try_create_family( &self, storage: &dyn Storage, label: String, - ) -> Result; + ) -> Result; fn try_join_family( &self, storage: &dyn Storage, join_permit: MessageSignature, family_head: FamilyHead, - ) -> Result; + ) -> Result; fn try_leave_family( &self, storage: &dyn Storage, family_head: FamilyHead, - ) -> Result; + ) -> Result; fn try_head_kick_member( &self, storage: &dyn Storage, member: IdentityKeyRef<'_>, - ) -> Result; + ) -> Result; } diff --git a/contracts/vesting/src/traits/vesting_account.rs b/contracts/vesting/src/traits/vesting_account.rs index 258a750492..3699f282ea 100644 --- a/contracts/vesting/src/traits/vesting_account.rs +++ b/contracts/vesting/src/traits/vesting_account.rs @@ -1,9 +1,8 @@ -use crate::errors::ContractError; use cosmwasm_std::{Addr, Coin, Env, Storage, Timestamp, Uint128}; -use vesting_contract_common::OriginalVestingResponse; +use vesting_contract_common::{OriginalVestingResponse, VestingContractError}; pub trait VestingAccount { - fn total_staked(&self, storage: &dyn Storage) -> Result; + fn total_staked(&self, storage: &dyn Storage) -> Result; /// Returns the set of coins that are not spendable (can still be delegated tough) (i.e. locked), /// defined as vesting coins that are not delegated or pledged. @@ -17,7 +16,7 @@ pub trait VestingAccount { block_time: Option, env: &Env, storage: &dyn Storage, - ) -> Result; + ) -> Result; /// Calculated as current_balance minus [crate::traits::VestingAccount::locked_coins] /// See [/vesting-contract/struct.Account.html/method.spendable_coins] for impl @@ -26,21 +25,21 @@ pub trait VestingAccount { block_time: Option, env: &Env, storage: &dyn Storage, - ) -> Result; + ) -> Result; fn spendable_vested_coins( &self, block_time: Option, env: &Env, storage: &dyn Storage, - ) -> Result; + ) -> Result; fn spendable_reward_coins( &self, block_time: Option, env: &Env, storage: &dyn Storage, - ) -> Result; + ) -> Result; /// See [/vesting-contract/struct.Account.html/method.get_vested_coins] for impl fn get_vested_coins( @@ -48,7 +47,7 @@ pub trait VestingAccount { block_time: Option, env: &Env, storage: &dyn Storage, - ) -> Result; + ) -> Result; /// See [/vesting-contract/struct.Account.html/method.get_vesting_coins] for impl fn get_vesting_coins( @@ -56,7 +55,7 @@ pub trait VestingAccount { block_time: Option, env: &Env, storage: &dyn Storage, - ) -> Result; + ) -> Result; /// See [/vesting-contract/struct.Account.html/method.get_start_time] for impl fn get_start_time(&self) -> Timestamp; @@ -65,23 +64,27 @@ pub trait VestingAccount { /// Returns amount of coins set at account creation /// See [/vesting-contract/struct.Account.html/method.get_original_vesting] for impl - fn get_original_vesting(&self) -> Result; + fn get_original_vesting(&self) -> Result; /// See [/vesting-contract/struct.Account.html/method.transfer_ownership] for impl fn transfer_ownership( &mut self, to_address: &Addr, storage: &mut dyn Storage, - ) -> Result<(), ContractError>; + ) -> Result<(), VestingContractError>; /// See [/vesting-contract/struct.Account.html/method.update_staking_address] for impl fn update_staking_address( &mut self, to_address: Option, storage: &mut dyn Storage, - ) -> Result<(), ContractError>; - fn track_reward(&self, amount: Coin, storage: &mut dyn Storage) -> Result<(), ContractError>; + ) -> Result<(), VestingContractError>; + fn track_reward( + &self, + amount: Coin, + storage: &mut dyn Storage, + ) -> Result<(), VestingContractError>; fn get_historical_vested_staking_rewards( &self, storage: &dyn Storage, - ) -> Result; + ) -> Result; } diff --git a/contracts/vesting/src/transactions.rs b/contracts/vesting/src/transactions.rs index bab50ec369..adbfe39436 100644 --- a/contracts/vesting/src/transactions.rs +++ b/contracts/vesting/src/transactions.rs @@ -2,14 +2,13 @@ // SPDX-License-Identifier: Apache-2.0 use crate::contract::{ensure_staking_permission, validate_funds}; -use crate::errors::ContractError; use crate::storage::{ account_from_address, save_account, ADMIN, MIXNET_CONTRACT_ADDRESS, MIX_DENOM, }; use crate::traits::{ DelegatingAccount, GatewayBondingAccount, MixnodeBondingAccount, NodeFamilies, VestingAccount, }; -use crate::vesting::{populate_vesting_periods, Account}; +use crate::vesting::{populate_vesting_periods, StorableVestingAccountExt}; use contracts_common::signing::MessageSignature; use cosmwasm_std::{coin, BankMsg, Coin, DepsMut, Env, MessageInfo, Response, Timestamp}; use mixnet_contract_common::families::FamilyHead; @@ -22,14 +21,13 @@ use vesting_contract_common::events::{ new_track_mixnode_pledge_decrease_event, new_track_mixnode_unbond_event, new_track_reward_event, new_track_undelegation_event, new_vested_coins_withdraw_event, }; -use vesting_contract_common::messages::VestingSpecification; -use vesting_contract_common::PledgeCap; +use vesting_contract_common::{Account, PledgeCap, VestingContractError, VestingSpecification}; pub fn try_create_family( info: MessageInfo, deps: DepsMut, label: String, -) -> Result { +) -> Result { let account = account_from_address(info.sender.as_ref(), deps.storage, deps.api)?; account.try_create_family(deps.storage, label) } @@ -38,7 +36,7 @@ pub fn try_join_family( deps: DepsMut, join_permit: MessageSignature, family_head: FamilyHead, -) -> Result { +) -> Result { let account = account_from_address(info.sender.as_ref(), deps.storage, deps.api)?; account.try_join_family(deps.storage, join_permit, family_head) } @@ -46,7 +44,7 @@ pub fn try_leave_family( info: MessageInfo, deps: DepsMut, family_head: FamilyHead, -) -> Result { +) -> Result { let account = account_from_address(info.sender.as_ref(), deps.storage, deps.api)?; account.try_leave_family(deps.storage, family_head) } @@ -54,7 +52,7 @@ pub fn try_kick_family_member( info: MessageInfo, deps: DepsMut, member: String, -) -> Result { +) -> Result { let account = account_from_address(info.sender.as_ref(), deps.storage, deps.api)?; account.try_head_kick_member(deps.storage, &member) } @@ -67,9 +65,11 @@ pub fn try_update_locked_pledge_cap( cap: PledgeCap, info: MessageInfo, deps: DepsMut, -) -> Result { +) -> Result { if info.sender != ADMIN.load(deps.storage)? { - return Err(ContractError::NotAdmin(info.sender.as_str().to_string())); + return Err(VestingContractError::NotAdmin( + info.sender.as_str().to_string(), + )); } let mut account = account_from_address(&address, deps.storage, deps.api)?; @@ -83,7 +83,7 @@ pub fn try_update_mixnode_config( new_config: MixNodeConfigUpdate, info: MessageInfo, deps: DepsMut, -) -> Result { +) -> Result { let account = account_from_address(info.sender.as_str(), deps.storage, deps.api)?; account.try_update_mixnode_config(new_config, deps.storage) } @@ -92,7 +92,7 @@ pub fn try_update_gateway_config( new_config: GatewayConfigUpdate, info: MessageInfo, deps: DepsMut, -) -> Result { +) -> Result { let account = account_from_address(info.sender.as_str(), deps.storage, deps.api)?; account.try_update_gateway_config(new_config, deps.storage) } @@ -101,7 +101,7 @@ pub fn try_update_mixnode_cost_params( new_costs: MixNodeCostParams, info: MessageInfo, deps: DepsMut, -) -> Result { +) -> Result { let account = account_from_address(info.sender.as_str(), deps.storage, deps.api)?; account.try_update_mixnode_cost_params(new_costs, deps.storage) } @@ -113,9 +113,11 @@ pub fn try_update_mixnet_address( address: String, info: MessageInfo, deps: DepsMut<'_>, -) -> Result { +) -> Result { if info.sender != ADMIN.load(deps.storage)? { - return Err(ContractError::NotAdmin(info.sender.as_str().to_string())); + return Err(VestingContractError::NotAdmin( + info.sender.as_str().to_string(), + )); } let mixnet_contract_address = deps.api.addr_validate(&address)?; @@ -129,16 +131,18 @@ pub fn try_withdraw_vested_coins( env: Env, info: MessageInfo, deps: DepsMut<'_>, -) -> Result { +) -> Result { let mix_denom = MIX_DENOM.load(deps.storage)?; if amount.denom != mix_denom { - return Err(ContractError::WrongDenom(amount.denom, mix_denom)); + return Err(VestingContractError::WrongDenom(amount.denom, mix_denom)); } let address = info.sender; let account = account_from_address(address.as_str(), deps.storage, deps.api)?; if address != account.owner_address() { - return Err(ContractError::NotOwner(account.owner_address().to_string())); + return Err(VestingContractError::NotOwner( + account.owner_address().to_string(), + )); } let spendable_coins = account.spendable_coins(None, &env, deps.storage)?; if amount.amount <= spendable_coins.amount { @@ -157,7 +161,7 @@ pub fn try_withdraw_vested_coins( &coin(new_balance, &amount.denom), ))) } else { - Err(ContractError::InsufficientSpendable( + Err(VestingContractError::InsufficientSpendable( account.owner_address().as_str().to_string(), spendable_coins.amount.u128(), )) @@ -169,7 +173,7 @@ pub fn try_transfer_ownership( to_address: String, info: MessageInfo, deps: DepsMut<'_>, -) -> Result { +) -> Result { let address = info.sender; let to_address = deps.api.addr_validate(&to_address)?; let mut account = account_from_address(address.as_str(), deps.storage, deps.api)?; @@ -177,7 +181,9 @@ pub fn try_transfer_ownership( account.transfer_ownership(&to_address, deps.storage)?; Ok(Response::new().add_event(new_ownership_transfer_event(&address, &to_address))) } else { - Err(ContractError::NotOwner(account.owner_address().to_string())) + Err(VestingContractError::NotOwner( + account.owner_address().to_string(), + )) } } @@ -186,11 +192,13 @@ pub fn try_update_staking_address( to_address: Option, info: MessageInfo, deps: DepsMut<'_>, -) -> Result { +) -> Result { if let Some(ref to_address) = to_address { if account_from_address(to_address, deps.storage, deps.api).is_ok() { // do not allow setting staking address to an existing account's address - return Err(ContractError::StakingAccountExists(to_address.to_string())); + return Err(VestingContractError::StakingAccountExists( + to_address.to_string(), + )); } } @@ -202,7 +210,9 @@ pub fn try_update_staking_address( account.update_staking_address(to_address.clone(), deps.storage)?; Ok(Response::new().add_event(new_staking_address_update_event(&old, &to_address))) } else { - Err(ContractError::NotOwner(account.owner_address().to_string())) + Err(VestingContractError::NotOwner( + account.owner_address().to_string(), + )) } } @@ -214,7 +224,7 @@ pub fn try_bond_gateway( info: MessageInfo, env: Env, deps: DepsMut<'_>, -) -> Result { +) -> Result { let mix_denom = MIX_DENOM.load(deps.storage)?; let pledge = validate_funds(&[amount], mix_denom)?; let account = account_from_address(info.sender.as_str(), deps.storage, deps.api)?; @@ -222,7 +232,10 @@ pub fn try_bond_gateway( } /// Unbond a gateway, sends [mixnet_contract_common::ExecuteMsg::UnbondGatewayOnBehalf] to [crate::storage::MIXNET_CONTRACT_ADDRESS]. -pub fn try_unbond_gateway(info: MessageInfo, deps: DepsMut<'_>) -> Result { +pub fn try_unbond_gateway( + info: MessageInfo, + deps: DepsMut<'_>, +) -> Result { let account = account_from_address(info.sender.as_str(), deps.storage, deps.api)?; account.try_unbond_gateway(deps.storage) } @@ -233,9 +246,9 @@ pub fn try_track_unbond_gateway( amount: Coin, info: MessageInfo, deps: DepsMut<'_>, -) -> Result { +) -> Result { if info.sender != MIXNET_CONTRACT_ADDRESS.load(deps.storage)? { - return Err(ContractError::NotMixnetContract(info.sender)); + return Err(VestingContractError::NotMixnetContract(info.sender)); } let account = account_from_address(owner, deps.storage, deps.api)?; account.try_track_unbond_gateway(amount, deps.storage)?; @@ -251,7 +264,7 @@ pub fn try_bond_mixnode( info: MessageInfo, env: Env, deps: DepsMut<'_>, -) -> Result { +) -> Result { let mix_denom = MIX_DENOM.load(deps.storage)?; let pledge = validate_funds(&[amount], mix_denom)?; let account = account_from_address(info.sender.as_str(), deps.storage, deps.api)?; @@ -270,7 +283,7 @@ pub fn try_pledge_more( env: Env, info: MessageInfo, amount: Coin, -) -> Result { +) -> Result { let mix_denom = MIX_DENOM.load(deps.storage)?; let additional_pledge = validate_funds(&[amount], mix_denom)?; @@ -282,7 +295,7 @@ pub fn try_decrease_pledge( deps: DepsMut<'_>, info: MessageInfo, amount: Coin, -) -> Result { +) -> Result { let mix_denom = MIX_DENOM.load(deps.storage)?; // perform basic validation - is it correct demon, is it non-zero, etc. let decrease = validate_funds(&[amount], mix_denom)?; @@ -292,7 +305,10 @@ pub fn try_decrease_pledge( } /// Unbond a mixnode, sends [mixnet_contract_common::ExecuteMsg::UnbondMixnodeOnBehalf] to [crate::storage::MIXNET_CONTRACT_ADDRESS]. -pub fn try_unbond_mixnode(info: MessageInfo, deps: DepsMut<'_>) -> Result { +pub fn try_unbond_mixnode( + info: MessageInfo, + deps: DepsMut<'_>, +) -> Result { let account = account_from_address(info.sender.as_str(), deps.storage, deps.api)?; account.try_unbond_mixnode(deps.storage) } @@ -303,9 +319,9 @@ pub fn try_track_unbond_mixnode( amount: Coin, info: MessageInfo, deps: DepsMut<'_>, -) -> Result { +) -> Result { if info.sender != MIXNET_CONTRACT_ADDRESS.load(deps.storage)? { - return Err(ContractError::NotMixnetContract(info.sender)); + return Err(VestingContractError::NotMixnetContract(info.sender)); } let account = account_from_address(owner, deps.storage, deps.api)?; account.try_track_unbond_mixnode(amount, deps.storage)?; @@ -319,9 +335,9 @@ pub fn try_track_decrease_mixnode_pledge( amount: Coin, info: MessageInfo, deps: DepsMut<'_>, -) -> Result { +) -> Result { if info.sender != MIXNET_CONTRACT_ADDRESS.load(deps.storage)? { - return Err(ContractError::NotMixnetContract(info.sender)); + return Err(VestingContractError::NotMixnetContract(info.sender)); } let account = account_from_address(owner, deps.storage, deps.api)?; account.try_track_decrease_mixnode_pledge(amount, deps.storage)?; @@ -334,9 +350,9 @@ pub fn try_track_reward( info: MessageInfo, amount: Coin, address: &str, -) -> Result { +) -> Result { if info.sender != MIXNET_CONTRACT_ADDRESS.load(deps.storage)? { - return Err(ContractError::NotMixnetContract(info.sender)); + return Err(VestingContractError::NotMixnetContract(info.sender)); } let account = account_from_address(address, deps.storage, deps.api)?; account.track_reward(amount, deps.storage)?; @@ -350,9 +366,9 @@ pub fn try_track_undelegation( amount: Coin, info: MessageInfo, deps: DepsMut<'_>, -) -> Result { +) -> Result { if info.sender != MIXNET_CONTRACT_ADDRESS.load(deps.storage)? { - return Err(ContractError::NotMixnetContract(info.sender)); + return Err(VestingContractError::NotMixnetContract(info.sender)); } let account = account_from_address(address, deps.storage, deps.api)?; @@ -368,7 +384,7 @@ pub fn try_delegate_to_mixnode( info: MessageInfo, env: Env, deps: DepsMut<'_>, -) -> Result { +) -> Result { // TODO // as of 01.02.23 // thus restricting it to 25, which is more than double of that, doesn't seem too unreasonable. @@ -397,7 +413,7 @@ pub fn try_delegate_to_mixnode( pub fn try_claim_operator_reward( deps: DepsMut<'_>, info: MessageInfo, -) -> Result { +) -> Result { let account = account_from_address(info.sender.as_str(), deps.storage, deps.api)?; account.try_claim_operator_reward(deps.storage) } @@ -407,7 +423,7 @@ pub fn try_claim_delegator_reward( deps: DepsMut<'_>, info: MessageInfo, mix_id: MixId, -) -> Result { +) -> Result { let account = account_from_address(info.sender.as_str(), deps.storage, deps.api)?; account.try_claim_delegator_reward(mix_id, deps.storage) @@ -419,7 +435,7 @@ pub fn try_undelegate_from_mixnode( on_behalf_of: Option, info: MessageInfo, deps: DepsMut<'_>, -) -> Result { +) -> Result { let account = match on_behalf_of { Some(account_owner) => { let account = account_from_address(&account_owner, deps.storage, deps.api)?; @@ -444,16 +460,18 @@ pub fn try_create_periodic_vesting_account( info: MessageInfo, env: Env, deps: DepsMut<'_>, -) -> Result { +) -> Result { if info.sender != ADMIN.load(deps.storage)? { - return Err(ContractError::NotAdmin(info.sender.as_str().to_string())); + return Err(VestingContractError::NotAdmin( + info.sender.as_str().to_string(), + )); } let mix_denom = MIX_DENOM.load(deps.storage)?; let account_exists = account_from_address(owner_address, deps.storage, deps.api).is_ok(); if account_exists { - return Err(ContractError::AccountAlreadyExists( + return Err(VestingContractError::AccountAlreadyExists( owner_address.to_string(), )); } @@ -467,7 +485,9 @@ pub fn try_create_periodic_vesting_account( let staking_account_exists = account_from_address(&staking_address, deps.storage, deps.api).is_ok(); if staking_account_exists { - return Err(ContractError::StakingAccountAlreadyExists(staking_address)); + return Err(VestingContractError::StakingAccountAlreadyExists( + staking_address, + )); } Some(deps.api.addr_validate(&staking_address)?) } else { @@ -483,7 +503,7 @@ pub fn try_create_periodic_vesting_account( let response = Response::new(); - Account::new( + Account::save_new( owner_address.clone(), staking_address.clone(), coin.clone(), diff --git a/contracts/vesting/src/vesting/account/delegating_account.rs b/contracts/vesting/src/vesting/account/delegating_account.rs index beaedf2be2..0371bc7cc3 100644 --- a/contracts/vesting/src/vesting/account/delegating_account.rs +++ b/contracts/vesting/src/vesting/account/delegating_account.rs @@ -1,14 +1,15 @@ use crate::contract::MAX_PER_MIX_DELEGATIONS; -use crate::errors::ContractError; use crate::storage::save_delegation; use crate::storage::MIXNET_CONTRACT_ADDRESS; use crate::traits::DelegatingAccount; +use crate::vesting::account::StorableVestingAccountExt; use cosmwasm_std::{wasm_execute, Coin, Env, Response, Storage, Uint128}; use mixnet_contract_common::ExecuteMsg as MixnetExecuteMsg; use mixnet_contract_common::MixId; use vesting_contract_common::events::{ new_vesting_delegation_event, new_vesting_undelegation_event, }; +use vesting_contract_common::VestingContractError; use super::Account; @@ -17,7 +18,7 @@ impl DelegatingAccount for Account { &self, mix_id: MixId, storage: &dyn Storage, - ) -> Result { + ) -> Result { let msg = MixnetExecuteMsg::WithdrawDelegatorRewardOnBehalf { owner: self.owner_address().to_string(), mix_id, @@ -35,12 +36,12 @@ impl DelegatingAccount for Account { coin: Coin, env: &Env, storage: &mut dyn Storage, - ) -> Result { + ) -> Result { let current_balance = self.ensure_valid_additional_stake(&coin, storage)?; let num_subdelegations = self.num_subdelegations_for_mix(mix_id, storage); if num_subdelegations >= MAX_PER_MIX_DELEGATIONS { - return Err(ContractError::TooManyDelegations { + return Err(VestingContractError::TooManyDelegations { address: self.owner_address.clone(), acc_id: self.storage_key(), mix_id, @@ -75,9 +76,9 @@ impl DelegatingAccount for Account { &self, mix_id: MixId, storage: &dyn Storage, - ) -> Result { + ) -> Result { if !self.any_delegation_for_mix(mix_id, storage) { - return Err(ContractError::NoSuchDelegation( + return Err(VestingContractError::NoSuchDelegation( self.owner_address(), mix_id, )); @@ -102,7 +103,7 @@ impl DelegatingAccount for Account { current_balance: Uint128, delegation: Coin, storage: &mut dyn Storage, - ) -> Result<(), ContractError> { + ) -> Result<(), VestingContractError> { save_delegation( (self.storage_key(), mix_id, block_timestamp_secs), delegation.amount, @@ -118,7 +119,7 @@ impl DelegatingAccount for Account { mix_id: MixId, amount: Coin, storage: &mut dyn Storage, - ) -> Result<(), ContractError> { + ) -> Result<(), VestingContractError> { self.remove_delegations_for_mix(mix_id, storage)?; let new_balance = Uint128::new(self.load_balance(storage)?.u128() + amount.amount.u128()); self.save_balance(new_balance, storage)?; diff --git a/contracts/vesting/src/vesting/account/gateway_bonding_account.rs b/contracts/vesting/src/vesting/account/gateway_bonding_account.rs index 12910acbcc..e63b0d1d09 100644 --- a/contracts/vesting/src/vesting/account/gateway_bonding_account.rs +++ b/contracts/vesting/src/vesting/account/gateway_bonding_account.rs @@ -1,7 +1,7 @@ use super::PledgeData; -use crate::errors::ContractError; use crate::storage::MIXNET_CONTRACT_ADDRESS; use crate::traits::GatewayBondingAccount; +use crate::vesting::account::StorableVestingAccountExt; use contracts_common::signing::MessageSignature; use cosmwasm_std::{wasm_execute, Coin, Env, Response, Storage, Uint128}; use mixnet_contract_common::{ @@ -11,6 +11,7 @@ use vesting_contract_common::events::{ new_vesting_gateway_bonding_event, new_vesting_gateway_unbonding_event, new_vesting_update_gateway_config_event, }; +use vesting_contract_common::VestingContractError; use super::Account; @@ -22,11 +23,11 @@ impl GatewayBondingAccount for Account { pledge: Coin, env: &Env, storage: &mut dyn Storage, - ) -> Result { + ) -> Result { let current_balance = self.ensure_valid_additional_stake(&pledge, storage)?; let pledge_data = if self.load_gateway_pledge(storage)?.is_some() { - return Err(ContractError::AlreadyBonded( + return Err(VestingContractError::AlreadyBonded( self.owner_address().as_str().to_string(), )); } else { @@ -52,7 +53,7 @@ impl GatewayBondingAccount for Account { .add_event(new_vesting_gateway_bonding_event())) } - fn try_unbond_gateway(&self, storage: &dyn Storage) -> Result { + fn try_unbond_gateway(&self, storage: &dyn Storage) -> Result { let msg = MixnetExecuteMsg::UnbondGatewayOnBehalf { owner: self.owner_address().into_string(), }; @@ -64,7 +65,7 @@ impl GatewayBondingAccount for Account { .add_message(unbond_msg) .add_event(new_vesting_gateway_unbonding_event())) } else { - Err(ContractError::NoBondFound( + Err(VestingContractError::NoBondFound( self.owner_address().as_str().to_string(), )) } @@ -74,7 +75,7 @@ impl GatewayBondingAccount for Account { &self, amount: Coin, storage: &mut dyn Storage, - ) -> Result<(), ContractError> { + ) -> Result<(), VestingContractError> { let new_balance = Uint128::new(self.load_balance(storage)?.u128() + amount.amount.u128()); self.save_balance(new_balance, storage)?; @@ -86,7 +87,7 @@ impl GatewayBondingAccount for Account { &self, new_config: GatewayConfigUpdate, storage: &mut dyn Storage, - ) -> Result { + ) -> Result { let msg = MixnetExecuteMsg::UpdateGatewayConfigOnBehalf { new_config, owner: self.owner_address().into_string(), diff --git a/contracts/vesting/src/vesting/account/mixnode_bonding_account.rs b/contracts/vesting/src/vesting/account/mixnode_bonding_account.rs index 6986ac8793..29ee780ff9 100644 --- a/contracts/vesting/src/vesting/account/mixnode_bonding_account.rs +++ b/contracts/vesting/src/vesting/account/mixnode_bonding_account.rs @@ -2,9 +2,9 @@ // SPDX-License-Identifier: Apache-2.0 use super::Account; -use crate::errors::ContractError; use crate::storage::MIXNET_CONTRACT_ADDRESS; use crate::traits::MixnodeBondingAccount; +use crate::vesting::account::StorableVestingAccountExt; use contracts_common::signing::MessageSignature; use cosmwasm_std::{wasm_execute, Coin, Env, Response, Storage, Uint128}; use mixnet_contract_common::mixnode::MixNodeConfigUpdate; @@ -15,10 +15,13 @@ use vesting_contract_common::events::{ new_vesting_mixnode_unbonding_event, new_vesting_pledge_more_event, new_vesting_update_mixnode_config_event, new_vesting_update_mixnode_cost_params_event, }; -use vesting_contract_common::PledgeData; +use vesting_contract_common::{PledgeData, VestingContractError}; impl MixnodeBondingAccount for Account { - fn try_claim_operator_reward(&self, storage: &dyn Storage) -> Result { + fn try_claim_operator_reward( + &self, + storage: &dyn Storage, + ) -> Result { let msg = MixnetExecuteMsg::WithdrawOperatorRewardOnBehalf { owner: self.owner_address().into_string(), }; @@ -37,11 +40,11 @@ impl MixnodeBondingAccount for Account { pledge: Coin, env: &Env, storage: &mut dyn Storage, - ) -> Result { + ) -> Result { let current_balance = self.ensure_valid_additional_stake(&pledge, storage)?; let pledge_data = if self.load_mixnode_pledge(storage)?.is_some() { - return Err(ContractError::AlreadyBonded( + return Err(VestingContractError::AlreadyBonded( self.owner_address().as_str().to_string(), )); } else { @@ -73,13 +76,13 @@ impl MixnodeBondingAccount for Account { additional_pledge: Coin, env: &Env, storage: &mut dyn Storage, - ) -> Result { + ) -> Result { let current_balance = self.ensure_valid_additional_stake(&additional_pledge, storage)?; let mut pledge_data = if let Some(pledge_data) = self.load_mixnode_pledge(storage)? { pledge_data } else { - return Err(ContractError::NoBondFound( + return Err(VestingContractError::NoBondFound( self.owner_address().as_str().to_string(), )); }; @@ -113,18 +116,18 @@ impl MixnodeBondingAccount for Account { &self, amount: Coin, storage: &mut dyn Storage, - ) -> Result { + ) -> Result { match self.load_mixnode_pledge(storage)? { Some(pledge) => { if pledge.amount.amount <= amount.amount { - return Err(ContractError::InvalidBondPledgeReduction { + return Err(VestingContractError::InvalidBondPledgeReduction { current: pledge.amount, decrease_by: amount, }); } } None => { - return Err(ContractError::NoBondFound( + return Err(VestingContractError::NoBondFound( self.owner_address().as_str().to_string(), )); } @@ -147,14 +150,14 @@ impl MixnodeBondingAccount for Account { &self, amount: Coin, storage: &mut dyn Storage, - ) -> Result<(), ContractError> { + ) -> Result<(), VestingContractError> { let new_balance = Uint128::new(self.load_balance(storage)?.u128() + amount.amount.u128()); self.save_balance(new_balance, storage)?; self.decrease_mixnode_pledge(amount, storage) } - fn try_unbond_mixnode(&self, storage: &dyn Storage) -> Result { + fn try_unbond_mixnode(&self, storage: &dyn Storage) -> Result { let msg = MixnetExecuteMsg::UnbondMixnodeOnBehalf { owner: self.owner_address().into_string(), }; @@ -166,7 +169,7 @@ impl MixnodeBondingAccount for Account { .add_message(unbond_msg) .add_event(new_vesting_mixnode_unbonding_event())) } else { - Err(ContractError::NoBondFound( + Err(VestingContractError::NoBondFound( self.owner_address().as_str().to_string(), )) } @@ -176,7 +179,7 @@ impl MixnodeBondingAccount for Account { &self, amount: Coin, storage: &mut dyn Storage, - ) -> Result<(), ContractError> { + ) -> Result<(), VestingContractError> { let new_balance = Uint128::new(self.load_balance(storage)?.u128() + amount.amount.u128()); self.save_balance(new_balance, storage)?; @@ -187,7 +190,7 @@ impl MixnodeBondingAccount for Account { &self, new_config: MixNodeConfigUpdate, storage: &mut dyn Storage, - ) -> Result { + ) -> Result { let msg = MixnetExecuteMsg::UpdateMixnodeConfigOnBehalf { new_config, owner: self.owner_address().into_string(), @@ -205,7 +208,7 @@ impl MixnodeBondingAccount for Account { &self, new_costs: MixNodeCostParams, storage: &mut dyn Storage, - ) -> Result { + ) -> Result { let msg = MixnetExecuteMsg::UpdateMixnodeCostParamsOnBehalf { new_costs, owner: self.owner_address().into_string(), diff --git a/contracts/vesting/src/vesting/account/mod.rs b/contracts/vesting/src/vesting/account/mod.rs index e59f27d1a2..7d016fd130 100644 --- a/contracts/vesting/src/vesting/account/mod.rs +++ b/contracts/vesting/src/vesting/account/mod.rs @@ -1,17 +1,18 @@ +// Copyright 2021-2023 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + use super::VestingPeriod; -use crate::errors::ContractError; use crate::storage::{ count_subdelegations_for_mix, decrease_bond_pledge, load_balance, load_bond_pledge, load_delegation_timestamps, load_gateway_pledge, load_withdrawn, remove_bond_pledge, remove_delegation, remove_gateway_pledge, save_account, save_balance, save_bond_pledge, - save_gateway_pledge, save_withdrawn, AccountStorageKey, BlockTimestampSecs, DELEGATIONS, KEY, + save_gateway_pledge, save_withdrawn, BlockTimestampSecs, DELEGATIONS, KEY, }; use crate::traits::VestingAccount; use cosmwasm_std::{Addr, Coin, Order, Storage, Timestamp, Uint128}; use mixnet_contract_common::MixId; -use schemars::JsonSchema; -use serde::{Deserialize, Serialize}; -use vesting_contract_common::{Period, PledgeCap, PledgeData}; +use vesting_contract_common::account::VestingAccountStorageKey; +use vesting_contract_common::{Account, PledgeCap, PledgeData, VestingContractError}; mod delegating_account; mod gateway_bonding_account; @@ -19,26 +20,17 @@ mod mixnode_bonding_account; mod node_families; mod vesting_account; -fn generate_storage_key(storage: &mut dyn Storage) -> Result { +fn generate_storage_key( + storage: &mut dyn Storage, +) -> Result { let key = KEY.may_load(storage)?.unwrap_or(0) + 1; KEY.save(storage, &key)?; Ok(key) } -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] -pub struct Account { - pub owner_address: Addr, - pub staking_address: Option, - pub start_time: Timestamp, - pub periods: Vec, - pub coin: Coin, - storage_key: AccountStorageKey, - #[serde(default)] - pub pledge_cap: Option, -} - -impl Account { - pub fn new( +/// Helper trait to extend the `Account` type with methods that require access to the underlying storage +pub(crate) trait StorableVestingAccountExt: VestingAccount { + fn save_new( owner_address: Addr, staking_address: Option, coin: Coin, @@ -46,7 +38,7 @@ impl Account { periods: Vec, pledge_cap: Option, storage: &mut dyn Storage, - ) -> Result { + ) -> Result { let storage_key = generate_storage_key(storage)?; let amount = coin.amount; let account = Account { @@ -63,29 +55,31 @@ impl Account { Ok(account) } + fn owner_address(&self) -> Addr; + /// Checks whether the additional stake would be within the cap associated with the account /// and whether the account has enough tokens for staking. /// Returns the value of the current balance of the account. - pub fn ensure_valid_additional_stake( + fn ensure_valid_additional_stake( &self, additional_stake: &Coin, storage: &dyn Storage, - ) -> Result { + ) -> Result { let current_balance = self.load_balance(storage)?; let current_total_staked = self.total_staked(storage)?; let total_staked_after = current_total_staked + additional_stake.amount; let locked_pledge_cap = self.absolute_pledge_cap()?; if locked_pledge_cap < total_staked_after { - return Err(ContractError::LockedPledgeCapReached { + return Err(VestingContractError::LockedPledgeCapReached { current: total_staked_after, cap: locked_pledge_cap, }); } if current_balance < additional_stake.amount { - return Err(ContractError::InsufficientBalance( - self.owner_address().as_str().to_string(), + return Err(VestingContractError::InsufficientBalance( + self.owner_address().into_string(), current_balance.u128(), )); } @@ -93,107 +87,13 @@ impl Account { Ok(current_balance) } - pub fn pledge_cap(&self) -> PledgeCap { - self.pledge_cap.clone().unwrap_or_default() - } + fn absolute_pledge_cap(&self) -> Result; - pub fn absolute_pledge_cap(&self) -> Result { - match self.pledge_cap() { - PledgeCap::Absolute(cap) => Ok(cap), - PledgeCap::Percent(p) => Ok(p * self.get_original_vesting()?.amount.amount), - } - } - - pub fn coin(&self) -> Coin { - self.coin.clone() - } - - pub fn num_vesting_periods(&self) -> usize { - self.periods.len() - } - - pub fn period_duration(&self) -> Result { - self.periods - .get(0) - .ok_or(ContractError::UnpopulatedVestingPeriods { - owner: self.owner_address.clone(), - }) - .map(|p| p.period_seconds) - } - - pub fn storage_key(&self) -> u32 { - self.storage_key - } - - pub fn owner_address(&self) -> Addr { - self.owner_address.clone() - } - - pub fn staking_address(&self) -> Option<&Addr> { - self.staking_address.as_ref() - } - - #[allow(dead_code)] - pub fn periods(&self) -> Vec { - self.periods.clone() - } - - #[allow(dead_code)] - pub fn start_time(&self) -> Timestamp { - self.start_time - } - - pub fn tokens_per_period(&self) -> Result { - let amount = self.coin.amount.u128(); - if amount < self.num_vesting_periods() as u128 { - Err(ContractError::ImprobableVestingAmount(amount)) - } else { - // Remainder tokens will be lumped into the last period. - Ok(amount / self.num_vesting_periods() as u128) - } - } - - /// Returns the index of the next vesting period. Unless the current time is somehow in the past or vesting has not started yet. - /// In case vesting is over it will always return NUM_VESTING_PERIODS. - pub fn get_current_vesting_period( - &self, - block_time: Timestamp, - ) -> Result { - let first_period = - self.periods - .first() - .ok_or(ContractError::UnpopulatedVestingPeriods { - owner: self.owner_address.clone(), - })?; - - let last_period = self - .periods - .last() - .ok_or(ContractError::UnpopulatedVestingPeriods { - owner: self.owner_address.clone(), - })?; - - if block_time.seconds() < first_period.start_time { - Ok(Period::Before) - } else if last_period.end_time() < block_time { - Ok(Period::After) - } else { - let mut index = 0; - for period in &self.periods { - if block_time < period.end_time() { - break; - } - index += 1; - } - Ok(Period::In(index)) - } - } - - pub fn withdraw( + fn withdraw( &self, amount: &Coin, storage: &mut dyn Storage, - ) -> Result { + ) -> Result { let new_balance = self .load_balance(storage)? .u128() @@ -204,127 +104,73 @@ impl Account { Ok(new_balance) } - pub fn load_withdrawn(&self, storage: &dyn Storage) -> Result { - load_withdrawn(self.storage_key, storage) - } + fn load_withdrawn(&self, storage: &dyn Storage) -> Result; - pub fn save_withdrawn( + fn save_withdrawn( &self, withdrawn: Uint128, storage: &mut dyn Storage, - ) -> Result<(), ContractError> { - save_withdrawn(self.storage_key, withdrawn, storage) - } + ) -> Result<(), VestingContractError>; - pub fn load_balance(&self, storage: &dyn Storage) -> Result { - load_balance(self.storage_key(), storage) - } + fn load_balance(&self, storage: &dyn Storage) -> Result; - pub fn save_balance( + fn save_balance( &self, amount: Uint128, storage: &mut dyn Storage, - ) -> Result<(), ContractError> { - save_balance(self.storage_key(), amount, storage) - } + ) -> Result<(), VestingContractError>; - pub fn load_mixnode_pledge( + fn load_mixnode_pledge( &self, storage: &dyn Storage, - ) -> Result, ContractError> { - load_bond_pledge(self.storage_key(), storage) - } + ) -> Result, VestingContractError>; - pub fn save_mixnode_pledge( + fn save_mixnode_pledge( &self, pledge: PledgeData, storage: &mut dyn Storage, - ) -> Result<(), ContractError> { - save_bond_pledge(self.storage_key(), &pledge, storage) - } + ) -> Result<(), VestingContractError>; - pub fn remove_mixnode_pledge(&self, storage: &mut dyn Storage) -> Result<(), ContractError> { - remove_bond_pledge(self.storage_key(), storage) - } + fn remove_mixnode_pledge(&self, storage: &mut dyn Storage) -> Result<(), VestingContractError>; - pub fn decrease_mixnode_pledge( + fn decrease_mixnode_pledge( &self, amount: Coin, storage: &mut dyn Storage, - ) -> Result<(), ContractError> { - decrease_bond_pledge(self.storage_key, amount, storage) - } + ) -> Result<(), VestingContractError>; - pub fn load_gateway_pledge( + fn load_gateway_pledge( &self, storage: &dyn Storage, - ) -> Result, ContractError> { - load_gateway_pledge(self.storage_key(), storage) - } + ) -> Result, VestingContractError>; - pub fn save_gateway_pledge( + fn save_gateway_pledge( &self, pledge: PledgeData, storage: &mut dyn Storage, - ) -> Result<(), ContractError> { - save_gateway_pledge(self.storage_key(), &pledge, storage) - } + ) -> Result<(), VestingContractError>; - pub fn remove_gateway_pledge(&self, storage: &mut dyn Storage) -> Result<(), ContractError> { - remove_gateway_pledge(self.storage_key(), storage) - } + fn remove_gateway_pledge(&self, storage: &mut dyn Storage) -> Result<(), VestingContractError>; - pub fn any_delegation_for_mix(&self, mix_id: MixId, storage: &dyn Storage) -> bool { - DELEGATIONS - .prefix((self.storage_key(), mix_id)) - .range(storage, None, None, Order::Ascending) - .next() - .is_some() - } + fn any_delegation_for_mix(&self, mix_id: MixId, storage: &dyn Storage) -> bool; - pub fn num_subdelegations_for_mix(&self, mix_id: MixId, storage: &dyn Storage) -> u32 { - count_subdelegations_for_mix((self.storage_key(), mix_id), storage) - } + fn num_subdelegations_for_mix(&self, mix_id: MixId, storage: &dyn Storage) -> u32; - pub fn remove_delegations_for_mix( + fn remove_delegations_for_mix( &self, mix_id: MixId, storage: &mut dyn Storage, - ) -> Result<(), ContractError> { - // note that the limit is implicitly set to `MAX_PER_MIX_DELEGATIONS` - // as it should be impossible to create more delegations than that. - let block_timestamps = load_delegation_timestamps((self.storage_key(), mix_id), storage)?; + ) -> Result<(), VestingContractError>; - for block_timestamp in block_timestamps { - remove_delegation((self.storage_key(), mix_id, block_timestamp), storage)?; - } - Ok(()) - } - - pub fn total_delegations_for_mix( + fn total_delegations_for_mix( &self, mix_id: MixId, storage: &dyn Storage, - ) -> Result { - Ok(DELEGATIONS - .prefix((self.storage_key(), mix_id)) - .range(storage, None, None, Order::Ascending) - .filter_map(|x| x.ok()) - .fold(Uint128::zero(), |acc, (_key, val)| acc + val)) - } + ) -> Result; - // TODO: this should get reworked... somehow... (maybe with a memoized value?) - // as it's an unbounded iteration that could fail if an account has made a lot of delegations - // (I guess in order of thousands) - pub fn total_delegations(&self, storage: &dyn Storage) -> Result { - Ok(DELEGATIONS - .sub_prefix(self.storage_key()) - .range(storage, None, None, Order::Ascending) - .filter_map(|x| x.ok()) - .fold(Uint128::zero(), |acc, (_key, val)| acc + val)) - } + fn total_delegations(&self, storage: &dyn Storage) -> Result; - pub fn total_pledged(&self, storage: &dyn Storage) -> Result { + fn total_pledged(&self, storage: &dyn Storage) -> Result { let amount = if let Some(bond) = self .load_mixnode_pledge(storage)? .or(self.load_gateway_pledge(storage)?) @@ -336,11 +182,147 @@ impl Account { Ok(amount) } - pub fn total_delegations_at_timestamp( + fn total_delegations_at_timestamp( &self, storage: &dyn Storage, start_time: BlockTimestampSecs, - ) -> Result { + ) -> Result; +} + +impl StorableVestingAccountExt for Account { + fn owner_address(&self) -> Addr { + self.owner_address.clone() + } + + fn absolute_pledge_cap(&self) -> Result { + match self.pledge_cap() { + PledgeCap::Absolute(cap) => Ok(cap), + PledgeCap::Percent(p) => Ok(p * self.get_original_vesting()?.amount.amount), + } + } + + fn load_withdrawn(&self, storage: &dyn Storage) -> Result { + load_withdrawn(self.storage_key, storage) + } + + fn save_withdrawn( + &self, + withdrawn: Uint128, + storage: &mut dyn Storage, + ) -> Result<(), VestingContractError> { + save_withdrawn(self.storage_key, withdrawn, storage) + } + + fn load_balance(&self, storage: &dyn Storage) -> Result { + load_balance(self.storage_key(), storage) + } + + fn save_balance( + &self, + amount: Uint128, + storage: &mut dyn Storage, + ) -> Result<(), VestingContractError> { + save_balance(self.storage_key(), amount, storage) + } + + fn load_mixnode_pledge( + &self, + storage: &dyn Storage, + ) -> Result, VestingContractError> { + load_bond_pledge(self.storage_key(), storage) + } + + fn save_mixnode_pledge( + &self, + pledge: PledgeData, + storage: &mut dyn Storage, + ) -> Result<(), VestingContractError> { + save_bond_pledge(self.storage_key(), &pledge, storage) + } + + fn remove_mixnode_pledge(&self, storage: &mut dyn Storage) -> Result<(), VestingContractError> { + remove_bond_pledge(self.storage_key(), storage) + } + + fn decrease_mixnode_pledge( + &self, + amount: Coin, + storage: &mut dyn Storage, + ) -> Result<(), VestingContractError> { + decrease_bond_pledge(self.storage_key(), amount, storage) + } + + fn load_gateway_pledge( + &self, + storage: &dyn Storage, + ) -> Result, VestingContractError> { + load_gateway_pledge(self.storage_key(), storage) + } + + fn save_gateway_pledge( + &self, + pledge: PledgeData, + storage: &mut dyn Storage, + ) -> Result<(), VestingContractError> { + save_gateway_pledge(self.storage_key(), &pledge, storage) + } + + fn remove_gateway_pledge(&self, storage: &mut dyn Storage) -> Result<(), VestingContractError> { + remove_gateway_pledge(self.storage_key(), storage) + } + + fn any_delegation_for_mix(&self, mix_id: MixId, storage: &dyn Storage) -> bool { + DELEGATIONS + .prefix((self.storage_key(), mix_id)) + .range(storage, None, None, Order::Ascending) + .next() + .is_some() + } + + fn num_subdelegations_for_mix(&self, mix_id: MixId, storage: &dyn Storage) -> u32 { + count_subdelegations_for_mix((self.storage_key(), mix_id), storage) + } + + fn remove_delegations_for_mix( + &self, + mix_id: MixId, + storage: &mut dyn Storage, + ) -> Result<(), VestingContractError> { + // note that the limit is implicitly set to `MAX_PER_MIX_DELEGATIONS` + // as it should be impossible to create more delegations than that. + let block_timestamps = load_delegation_timestamps((self.storage_key(), mix_id), storage)?; + + for block_timestamp in block_timestamps { + remove_delegation((self.storage_key(), mix_id, block_timestamp), storage)?; + } + Ok(()) + } + + fn total_delegations_for_mix( + &self, + mix_id: MixId, + storage: &dyn Storage, + ) -> Result { + Ok(DELEGATIONS + .prefix((self.storage_key(), mix_id)) + .range(storage, None, None, Order::Ascending) + .filter_map(|x| x.ok()) + .fold(Uint128::zero(), |acc, (_key, val)| acc + val)) + } + + fn total_delegations(&self, storage: &dyn Storage) -> Result { + Ok(DELEGATIONS + .sub_prefix(self.storage_key()) + .range(storage, None, None, Order::Ascending) + .filter_map(|x| x.ok()) + .fold(Uint128::zero(), |acc, (_key, val)| acc + val)) + } + + fn total_delegations_at_timestamp( + &self, + storage: &dyn Storage, + start_time: BlockTimestampSecs, + ) -> Result { Ok(DELEGATIONS .sub_prefix(self.storage_key()) .range(storage, None, None, Order::Ascending) diff --git a/contracts/vesting/src/vesting/account/node_families.rs b/contracts/vesting/src/vesting/account/node_families.rs index 2d6c4ce436..649531688b 100644 --- a/contracts/vesting/src/vesting/account/node_families.rs +++ b/contracts/vesting/src/vesting/account/node_families.rs @@ -1,16 +1,17 @@ use super::Account; -use crate::{errors::ContractError, storage::MIXNET_CONTRACT_ADDRESS, traits::NodeFamilies}; +use crate::{storage::MIXNET_CONTRACT_ADDRESS, traits::NodeFamilies}; use contracts_common::signing::MessageSignature; use cosmwasm_std::{wasm_execute, Response, Storage}; use mixnet_contract_common::families::FamilyHead; use mixnet_contract_common::{ExecuteMsg as MixnetExecuteMsg, IdentityKeyRef}; +use vesting_contract_common::VestingContractError; impl NodeFamilies for Account { fn try_create_family( &self, storage: &dyn Storage, label: String, - ) -> Result { + ) -> Result { let msg = MixnetExecuteMsg::CreateFamilyOnBehalf { owner_address: self.owner_address().into_string(), label, @@ -26,7 +27,7 @@ impl NodeFamilies for Account { storage: &dyn Storage, join_permit: MessageSignature, family_head: FamilyHead, - ) -> Result { + ) -> Result { let msg = MixnetExecuteMsg::JoinFamilyOnBehalf { member_address: self.owner_address().to_string(), join_permit, @@ -42,7 +43,7 @@ impl NodeFamilies for Account { &self, storage: &dyn Storage, family_head: FamilyHead, - ) -> Result { + ) -> Result { let msg = MixnetExecuteMsg::LeaveFamilyOnBehalf { member_address: self.owner_address().to_string(), family_head, @@ -57,7 +58,7 @@ impl NodeFamilies for Account { &self, storage: &dyn Storage, member: IdentityKeyRef<'_>, - ) -> Result { + ) -> Result { let msg = MixnetExecuteMsg::KickFamilyMemberOnBehalf { head_address: self.owner_address().to_string(), member: member.to_string(), diff --git a/contracts/vesting/src/vesting/account/vesting_account.rs b/contracts/vesting/src/vesting/account/vesting_account.rs index 5b64b9896e..1cb51aae19 100644 --- a/contracts/vesting/src/vesting/account/vesting_account.rs +++ b/contracts/vesting/src/vesting/account/vesting_account.rs @@ -1,14 +1,14 @@ -use crate::errors::ContractError; use crate::storage::{delete_account, save_account, MIX_DENOM}; use crate::traits::VestingAccount; +use crate::vesting::account::StorableVestingAccountExt; use cosmwasm_std::{Addr, Coin, Env, Storage, Timestamp, Uint128}; use std::cmp::min; -use vesting_contract_common::{OriginalVestingResponse, Period}; +use vesting_contract_common::{OriginalVestingResponse, Period, VestingContractError}; use super::Account; impl VestingAccount for Account { - fn total_staked(&self, storage: &dyn Storage) -> Result { + fn total_staked(&self, storage: &dyn Storage) -> Result { Ok(self.total_delegations(storage)? + self.total_pledged(storage)?) } @@ -21,7 +21,7 @@ impl VestingAccount for Account { block_time: Option, env: &Env, storage: &dyn Storage, - ) -> Result { + ) -> Result { let still_vesting = self.get_vesting_coins(block_time, env, storage)?.amount; let staked = self.total_staked(storage)?; let locked_amount = still_vesting.saturating_sub(staked); @@ -37,7 +37,7 @@ impl VestingAccount for Account { block_time: Option, env: &Env, storage: &dyn Storage, - ) -> Result { + ) -> Result { Ok(Coin { amount: self .load_balance(storage)? @@ -51,7 +51,7 @@ impl VestingAccount for Account { block_time: Option, env: &Env, storage: &dyn Storage, - ) -> Result { + ) -> Result { let vested = self.get_vested_coins(block_time, env, storage)?; let withdrawn = self.load_withdrawn(storage)?; @@ -76,7 +76,7 @@ impl VestingAccount for Account { block_time: Option, env: &Env, storage: &dyn Storage, - ) -> Result { + ) -> Result { let spendable = self.spendable_coins(block_time, env, storage)?; let spendable_vested = self.spendable_vested_coins(block_time, env, storage)?; @@ -92,7 +92,7 @@ impl VestingAccount for Account { block_time: Option, env: &Env, storage: &dyn Storage, - ) -> Result { + ) -> Result { let block_time = block_time.unwrap_or(env.block.time); let period = self.get_current_vesting_period(block_time)?; let denom = MIX_DENOM.load(storage)?; @@ -119,7 +119,7 @@ impl VestingAccount for Account { block_time: Option, env: &Env, storage: &dyn Storage, - ) -> Result { + ) -> Result { Ok(Coin { amount: self.get_original_vesting()?.amount().amount - self.get_vested_coins(block_time, env, storage)?.amount, @@ -135,7 +135,7 @@ impl VestingAccount for Account { self.periods[self.num_vesting_periods() - 1].end_time() } - fn get_original_vesting(&self) -> Result { + fn get_original_vesting(&self) -> Result { Ok(OriginalVestingResponse::new( self.coin.clone(), self.num_vesting_periods(), @@ -147,7 +147,7 @@ impl VestingAccount for Account { &mut self, to_address: &Addr, storage: &mut dyn Storage, - ) -> Result<(), ContractError> { + ) -> Result<(), VestingContractError> { delete_account(self.owner_address(), storage)?; self.owner_address = to_address.to_owned(); save_account(self, storage)?; @@ -158,7 +158,7 @@ impl VestingAccount for Account { &mut self, to_address: Option, storage: &mut dyn Storage, - ) -> Result<(), ContractError> { + ) -> Result<(), VestingContractError> { if let Some(staking_address) = self.staking_address() { delete_account(staking_address.to_owned(), storage)?; } @@ -167,7 +167,11 @@ impl VestingAccount for Account { Ok(()) } - fn track_reward(&self, amount: Coin, storage: &mut dyn Storage) -> Result<(), ContractError> { + fn track_reward( + &self, + amount: Coin, + storage: &mut dyn Storage, + ) -> Result<(), VestingContractError> { let current_balance = self.load_balance(storage)?; let new_balance = current_balance + amount.amount; self.save_balance(new_balance, storage)?; @@ -183,7 +187,7 @@ impl VestingAccount for Account { fn get_historical_vested_staking_rewards( &self, storage: &dyn Storage, - ) -> Result { + ) -> Result { let balance = self.load_balance(storage)?; let withdrawn = self.load_withdrawn(storage)?; let staked = self.total_staked(storage)?; diff --git a/contracts/vesting/src/vesting/mod.rs b/contracts/vesting/src/vesting/mod.rs index 56a02928d7..b16efbdd23 100644 --- a/contracts/vesting/src/vesting/mod.rs +++ b/contracts/vesting/src/vesting/mod.rs @@ -1,43 +1,18 @@ -use cosmwasm_std::Timestamp; -use schemars::JsonSchema; -use serde::{Deserialize, Serialize}; - mod account; -pub use account::*; -use vesting_contract_common::messages::VestingSpecification; - -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] -pub struct VestingPeriod { - pub start_time: u64, - pub period_seconds: u64, -} - -impl VestingPeriod { - pub fn end_time(&self) -> Timestamp { - Timestamp::from_seconds(self.start_time + self.period_seconds) - } -} +pub(crate) use account::StorableVestingAccountExt; +use vesting_contract_common::{VestingPeriod, VestingSpecification}; pub fn populate_vesting_periods( start_time: u64, vesting_spec: VestingSpecification, ) -> Vec { - let mut periods = Vec::with_capacity(vesting_spec.num_periods() as usize); - for i in 0..vesting_spec.num_periods() { - let period = VestingPeriod { - start_time: start_time + i * vesting_spec.period_seconds(), - period_seconds: vesting_spec.period_seconds(), - }; - periods.push(period); - } - periods + vesting_spec.populate_vesting_periods(start_time) } #[cfg(test)] mod tests { use crate::contract::*; - use crate::errors::ContractError; use crate::storage::*; use crate::support::tests::helpers::vesting_account_percent_fixture; use crate::support::tests::helpers::{ @@ -46,15 +21,16 @@ mod tests { use crate::traits::DelegatingAccount; use crate::traits::VestingAccount; use crate::traits::{GatewayBondingAccount, MixnodeBondingAccount}; - use crate::vesting::{populate_vesting_periods, Account}; + use crate::vesting::account::StorableVestingAccountExt; + use crate::vesting::populate_vesting_periods; use contracts_common::signing::MessageSignature; use cosmwasm_std::testing::{mock_env, mock_info}; use cosmwasm_std::{coin, coins, Addr, Coin, Timestamp, Uint128}; use mixnet_contract_common::mixnode::MixNodeCostParams; use mixnet_contract_common::{Gateway, MixNode, Percent}; - use vesting_contract_common::messages::{ExecuteMsg, VestingSpecification}; - use vesting_contract_common::Period; - use vesting_contract_common::PledgeCap; + use vesting_contract_common::messages::ExecuteMsg; + use vesting_contract_common::{Account, PledgeCap, VestingSpecification}; + use vesting_contract_common::{Period, VestingContractError}; #[test] fn test_account_creation() { @@ -317,7 +293,9 @@ mod tests { staking_address_change, ); assert_eq!( - Err(ContractError::StakingAccountExists("vesting1".to_string())), + Err(VestingContractError::StakingAccountExists( + "vesting1".to_string() + )), res ); @@ -802,7 +780,7 @@ mod tests { VestingSpecification::new(None, Some(vesting_period_length_secs), None), ); - let vesting_account = Account::new( + let vesting_account = Account::save_new( Addr::unchecked("owner"), Some(Addr::unchecked("staking")), Coin { @@ -842,7 +820,7 @@ mod tests { assert_eq!( res, - ContractError::TooManyDelegations { + VestingContractError::TooManyDelegations { address: vesting_account.owner_address(), acc_id: vesting_account.storage_key(), mix_id, diff --git a/nym-connect/desktop/Cargo.lock b/nym-connect/desktop/Cargo.lock index 1b9ca04b31..c1acf4ee4e 100644 --- a/nym-connect/desktop/Cargo.lock +++ b/nym-connect/desktop/Cargo.lock @@ -284,8 +284,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "93f2635620bf0b9d4576eb7bb9a38a55df78bd1205d26fa994b25911a69f212f" dependencies = [ "bitcoin_hashes", - "rand 0.7.3", - "rand_core 0.5.1", + "rand 0.8.5", + "rand_core 0.6.4", "serde", "unicode-normalization", "zeroize", @@ -399,6 +399,12 @@ dependencies = [ "zeroize", ] +[[package]] +name = "bnum" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "845141a4fade3f790628b7daaaa298a25b204fb28907eb54febe5142db6ce653" + [[package]] name = "brotli" version = "3.3.4" @@ -833,9 +839,9 @@ dependencies = [ [[package]] name = "cosmwasm-crypto" -version = "1.2.5" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75836a10cb9654c54e77ee56da94d592923092a10b369cdb0dbd56acefc16340" +checksum = "0d076a08ec01ed23c4396aca98ec73a38fa1fee5f310465add52b4108181c7a8" dependencies = [ "digest 0.10.7", "ed25519-zebra", @@ -846,18 +852,18 @@ dependencies = [ [[package]] name = "cosmwasm-derive" -version = "1.2.5" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c9f7f0e51bfc7295f7b2664fe8513c966428642aa765dad8a74acdab5e0c773" +checksum = "dec361f3c09d7b41221948fc17be9b3c96cb58e55a02f82da36f888a651f2584" dependencies = [ "syn 1.0.109", ] [[package]] name = "cosmwasm-schema" -version = "1.2.5" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f00b363610218eea83f24bbab09e1a7c3920b79f068334fdfcc62f6129ef9fc" +checksum = "bb6b2fb76758ef59cddc77f2e2ae91c22f77da49037e9f182e9c2833f0e959b1" dependencies = [ "cosmwasm-schema-derive", "schemars", @@ -868,9 +874,9 @@ dependencies = [ [[package]] name = "cosmwasm-schema-derive" -version = "1.2.5" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae38f909b2822d32b275c9e2db9728497aa33ffe67dd463bc67c6a3b7092785c" +checksum = "2bfa39422f0d9f1c9a6fd3711573258495314dfa3aae738ea825ecd9964bc659" dependencies = [ "proc-macro2", "quote", @@ -879,11 +885,12 @@ dependencies = [ [[package]] name = "cosmwasm-std" -version = "1.2.5" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a49b85345e811c8e80ec55d0d091e4fcb4f00f97ab058f9be5f614c444a730cb" +checksum = "1f6dc2ee23313add5ecacc3ccac217b9967ad9d2d11bd56e5da6aa65a9da6138" dependencies = [ "base64 0.13.1", + "bnum", "cosmwasm-crypto", "cosmwasm-derive", "derivative", @@ -891,10 +898,9 @@ dependencies = [ "hex", "schemars", "serde", - "serde-json-wasm 0.5.1", + "serde-json-wasm", "sha2 0.10.6", "thiserror", - "uint", ] [[package]] @@ -998,12 +1004,6 @@ dependencies = [ "cfg-if", ] -[[package]] -name = "crunchy" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" - [[package]] name = "crypto-bigint" version = "0.4.9" @@ -1151,9 +1151,9 @@ dependencies = [ [[package]] name = "cw-controllers" -version = "1.0.1" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91440ce8ec4f0642798bc8c8cb6b9b53c1926c6dadaf0eed267a5145cd529071" +checksum = "d5d8edce4b78785f36413f67387e4be7d0cb7d032b5d4164bcc024f9c3f3f2ea" dependencies = [ "cosmwasm-schema", "cosmwasm-std", @@ -1166,9 +1166,9 @@ dependencies = [ [[package]] name = "cw-storage-plus" -version = "1.0.1" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "053a5083c258acd68386734f428a5a171b29f7d733151ae83090c6fcc9417ffa" +checksum = "3f0e92a069d62067f3472c62e30adedb4cab1754725c0f2a682b3128d2bf3c79" dependencies = [ "cosmwasm-std", "schemars", @@ -1192,22 +1192,23 @@ dependencies = [ [[package]] name = "cw2" -version = "1.0.1" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fb70cee2cf0b4a8ff7253e6bc6647107905e8eb37208f87d54f67810faa62f8" +checksum = "29ac2dc7a55ad64173ca1e0a46697c31b7a5c51342f55a1e84a724da4eb99908" dependencies = [ "cosmwasm-schema", "cosmwasm-std", "cw-storage-plus", "schemars", "serde", + "thiserror", ] [[package]] name = "cw20" -version = "1.0.1" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91666da6c7b40c8dd5ff94df655a28114efc10c79b70b4d06f13c31e37d60609" +checksum = "011c45920f8200bd5d32d4fe52502506f64f2f75651ab408054d4cfc75ca3a9b" dependencies = [ "cosmwasm-schema", "cosmwasm-std", @@ -1218,9 +1219,9 @@ dependencies = [ [[package]] name = "cw3" -version = "1.0.1" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fe0b587008aa221cd2a2579a21990a28c4347dc53ad43167c68ad765f5b6efa" +checksum = "171af3d9127de6805a7dd819fb070c7d2f6c3ea85f4193f42cef259f0a7f33d5" dependencies = [ "cosmwasm-schema", "cosmwasm-std", @@ -1233,9 +1234,9 @@ dependencies = [ [[package]] name = "cw4" -version = "1.0.1" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c236e0bae02ce97e89235a681dd0e07d099524b369f1ef908d704db3e6b049b" +checksum = "a398696307efadaaa2d0850076f865fa706c959d493cb4203314f72be6b77a64" dependencies = [ "cosmwasm-schema", "cosmwasm-std", @@ -3614,22 +3615,20 @@ dependencies = [ name = "nym-coconut-bandwidth-contract-common" version = "0.1.0" dependencies = [ + "cosmwasm-schema", "cosmwasm-std", "nym-multisig-contract-common", - "schemars", - "serde", ] [[package]] name = "nym-coconut-dkg-common" version = "0.1.0" dependencies = [ + "cosmwasm-schema", "cosmwasm-std", "cw-utils", "nym-contracts-common", "nym-multisig-contract-common", - "schemars", - "serde", ] [[package]] @@ -3709,6 +3708,7 @@ name = "nym-contracts-common" version = "0.5.0" dependencies = [ "bs58 0.4.0", + "cosmwasm-schema", "cosmwasm-std", "schemars", "serde", @@ -3851,13 +3851,14 @@ name = "nym-mixnet-contract-common" version = "0.6.0" dependencies = [ "bs58 0.4.0", + "cosmwasm-schema", "cosmwasm-std", "humantime-serde", "log", "nym-contracts-common", "schemars", "serde", - "serde-json-wasm 0.4.1", + "serde-json-wasm", "serde_repr", "thiserror", "time", @@ -3882,11 +3883,11 @@ dependencies = [ name = "nym-name-service-common" version = "0.1.0" dependencies = [ + "cosmwasm-schema", "cosmwasm-std", "cw-controllers", "cw-utils", "nym-contracts-common", - "schemars", "serde", "thiserror", ] @@ -3952,12 +3953,11 @@ dependencies = [ name = "nym-service-provider-directory-common" version = "0.1.0" dependencies = [ + "cosmwasm-schema", "cosmwasm-std", "cw-controllers", "cw-utils", "nym-contracts-common", - "schemars", - "serde", "thiserror", ] @@ -4231,7 +4231,6 @@ dependencies = [ "nym-name-service-common", "nym-network-defaults", "nym-service-provider-directory-common", - "nym-vesting-contract", "nym-vesting-contract-common", "openssl", "prost", @@ -4246,33 +4245,16 @@ dependencies = [ "zeroize", ] -[[package]] -name = "nym-vesting-contract" -version = "1.4.0" -dependencies = [ - "cosmwasm-derive", - "cosmwasm-std", - "cw-storage-plus", - "cw2", - "nym-contracts-common", - "nym-mixnet-contract-common", - "nym-vesting-contract-common", - "schemars", - "semver 1.0.17", - "serde", - "thiserror", - "vergen", -] - [[package]] name = "nym-vesting-contract-common" version = "0.7.0" dependencies = [ + "cosmwasm-schema", "cosmwasm-std", "nym-contracts-common", "nym-mixnet-contract-common", - "schemars", "serde", + "thiserror", ] [[package]] @@ -5691,18 +5673,9 @@ dependencies = [ [[package]] name = "serde-json-wasm" -version = "0.4.1" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "479b4dbc401ca13ee8ce902851b834893251404c4f3c65370a49e047a6be09a5" -dependencies = [ - "serde", -] - -[[package]] -name = "serde-json-wasm" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16a62a1fad1e1828b24acac8f2b468971dade7b8c3c2e672bcadefefb1f8c137" +checksum = "a15bee9b04dd165c3f4e142628982ddde884c2022a89e8ddf99c4829bf2c3a58" dependencies = [ "serde", ] @@ -6247,12 +6220,6 @@ dependencies = [ "loom", ] -[[package]] -name = "static_assertions" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" - [[package]] name = "string_cache" version = "0.8.7" @@ -7173,18 +7140,6 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9e79c4d996edb816c91e4308506774452e55e95c3c9de07b6729e17e15a5ef81" -[[package]] -name = "uint" -version = "0.9.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76f64bba2c53b04fcab63c01a7d7427eadc821e3bc48c34dc9ba29c501164b52" -dependencies = [ - "byteorder", - "crunchy", - "hex", - "static_assertions", -] - [[package]] name = "uname" version = "0.1.1" diff --git a/nym-wallet/Cargo.lock b/nym-wallet/Cargo.lock index 050930055a..2e7df7a43e 100644 --- a/nym-wallet/Cargo.lock +++ b/nym-wallet/Cargo.lock @@ -302,6 +302,12 @@ dependencies = [ "zeroize", ] +[[package]] +name = "bnum" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "845141a4fade3f790628b7daaaa298a25b204fb28907eb54febe5142db6ce653" + [[package]] name = "brotli" version = "3.3.4" @@ -680,9 +686,9 @@ dependencies = [ [[package]] name = "cosmwasm-crypto" -version = "1.2.5" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75836a10cb9654c54e77ee56da94d592923092a10b369cdb0dbd56acefc16340" +checksum = "0d076a08ec01ed23c4396aca98ec73a38fa1fee5f310465add52b4108181c7a8" dependencies = [ "digest 0.10.7", "ed25519-zebra", @@ -693,18 +699,18 @@ dependencies = [ [[package]] name = "cosmwasm-derive" -version = "1.2.5" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c9f7f0e51bfc7295f7b2664fe8513c966428642aa765dad8a74acdab5e0c773" +checksum = "dec361f3c09d7b41221948fc17be9b3c96cb58e55a02f82da36f888a651f2584" dependencies = [ "syn 1.0.107", ] [[package]] name = "cosmwasm-schema" -version = "1.2.5" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f00b363610218eea83f24bbab09e1a7c3920b79f068334fdfcc62f6129ef9fc" +checksum = "bb6b2fb76758ef59cddc77f2e2ae91c22f77da49037e9f182e9c2833f0e959b1" dependencies = [ "cosmwasm-schema-derive", "schemars", @@ -715,9 +721,9 @@ dependencies = [ [[package]] name = "cosmwasm-schema-derive" -version = "1.2.5" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae38f909b2822d32b275c9e2db9728497aa33ffe67dd463bc67c6a3b7092785c" +checksum = "2bfa39422f0d9f1c9a6fd3711573258495314dfa3aae738ea825ecd9964bc659" dependencies = [ "proc-macro2", "quote", @@ -726,11 +732,12 @@ dependencies = [ [[package]] name = "cosmwasm-std" -version = "1.2.5" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a49b85345e811c8e80ec55d0d091e4fcb4f00f97ab058f9be5f614c444a730cb" +checksum = "1f6dc2ee23313add5ecacc3ccac217b9967ad9d2d11bd56e5da6aa65a9da6138" dependencies = [ "base64 0.13.1", + "bnum", "cosmwasm-crypto", "cosmwasm-derive", "derivative", @@ -738,10 +745,9 @@ dependencies = [ "hex", "schemars", "serde", - "serde-json-wasm 0.5.1", + "serde-json-wasm", "sha2 0.10.6", "thiserror", - "uint", ] [[package]] @@ -781,12 +787,6 @@ dependencies = [ "cfg-if", ] -[[package]] -name = "crunchy" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" - [[package]] name = "crypto-bigint" version = "0.4.9" @@ -911,9 +911,9 @@ dependencies = [ [[package]] name = "cw-controllers" -version = "1.0.1" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91440ce8ec4f0642798bc8c8cb6b9b53c1926c6dadaf0eed267a5145cd529071" +checksum = "d5d8edce4b78785f36413f67387e4be7d0cb7d032b5d4164bcc024f9c3f3f2ea" dependencies = [ "cosmwasm-schema", "cosmwasm-std", @@ -926,9 +926,9 @@ dependencies = [ [[package]] name = "cw-storage-plus" -version = "1.0.1" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "053a5083c258acd68386734f428a5a171b29f7d733151ae83090c6fcc9417ffa" +checksum = "3f0e92a069d62067f3472c62e30adedb4cab1754725c0f2a682b3128d2bf3c79" dependencies = [ "cosmwasm-std", "schemars", @@ -952,22 +952,23 @@ dependencies = [ [[package]] name = "cw2" -version = "1.0.1" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fb70cee2cf0b4a8ff7253e6bc6647107905e8eb37208f87d54f67810faa62f8" +checksum = "29ac2dc7a55ad64173ca1e0a46697c31b7a5c51342f55a1e84a724da4eb99908" dependencies = [ "cosmwasm-schema", "cosmwasm-std", "cw-storage-plus", "schemars", "serde", + "thiserror", ] [[package]] name = "cw20" -version = "1.0.1" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91666da6c7b40c8dd5ff94df655a28114efc10c79b70b4d06f13c31e37d60609" +checksum = "011c45920f8200bd5d32d4fe52502506f64f2f75651ab408054d4cfc75ca3a9b" dependencies = [ "cosmwasm-schema", "cosmwasm-std", @@ -978,9 +979,9 @@ dependencies = [ [[package]] name = "cw3" -version = "1.0.1" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fe0b587008aa221cd2a2579a21990a28c4347dc53ad43167c68ad765f5b6efa" +checksum = "171af3d9127de6805a7dd819fb070c7d2f6c3ea85f4193f42cef259f0a7f33d5" dependencies = [ "cosmwasm-schema", "cosmwasm-std", @@ -993,9 +994,9 @@ dependencies = [ [[package]] name = "cw4" -version = "1.0.1" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c236e0bae02ce97e89235a681dd0e07d099524b369f1ef908d704db3e6b049b" +checksum = "a398696307efadaaa2d0850076f865fa706c959d493cb4203314f72be6b77a64" dependencies = [ "cosmwasm-schema", "cosmwasm-std", @@ -2937,22 +2938,20 @@ dependencies = [ name = "nym-coconut-bandwidth-contract-common" version = "0.1.0" dependencies = [ + "cosmwasm-schema", "cosmwasm-std", "nym-multisig-contract-common", - "schemars", - "serde", ] [[package]] name = "nym-coconut-dkg-common" version = "0.1.0" dependencies = [ + "cosmwasm-schema", "cosmwasm-std", "cw-utils", "nym-contracts-common", "nym-multisig-contract-common", - "schemars", - "serde", ] [[package]] @@ -2984,6 +2983,7 @@ name = "nym-contracts-common" version = "0.5.0" dependencies = [ "bs58 0.4.0", + "cosmwasm-schema", "cosmwasm-std", "schemars", "serde", @@ -3042,13 +3042,14 @@ name = "nym-mixnet-contract-common" version = "0.6.0" dependencies = [ "bs58 0.4.0", + "cosmwasm-schema", "cosmwasm-std", "humantime-serde", "log", "nym-contracts-common", "schemars", "serde", - "serde-json-wasm 0.4.1", + "serde-json-wasm", "serde_repr", "thiserror", "time", @@ -3074,11 +3075,11 @@ dependencies = [ name = "nym-name-service-common" version = "0.1.0" dependencies = [ + "cosmwasm-schema", "cosmwasm-std", "cw-controllers", "cw-utils", "nym-contracts-common", - "schemars", "serde", "thiserror", ] @@ -3107,12 +3108,11 @@ dependencies = [ name = "nym-service-provider-directory-common" version = "0.1.0" dependencies = [ + "cosmwasm-schema", "cosmwasm-std", "cw-controllers", "cw-utils", "nym-contracts-common", - "schemars", - "serde", "thiserror", ] @@ -3151,7 +3151,6 @@ dependencies = [ "nym-config", "nym-mixnet-contract-common", "nym-validator-client", - "nym-vesting-contract", "nym-vesting-contract-common", "reqwest", "schemars", @@ -3192,7 +3191,6 @@ dependencies = [ "nym-name-service-common", "nym-network-defaults", "nym-service-provider-directory-common", - "nym-vesting-contract", "nym-vesting-contract-common", "openssl", "prost", @@ -3207,33 +3205,16 @@ dependencies = [ "zeroize", ] -[[package]] -name = "nym-vesting-contract" -version = "1.4.0" -dependencies = [ - "cosmwasm-derive", - "cosmwasm-std", - "cw-storage-plus", - "cw2", - "nym-contracts-common", - "nym-mixnet-contract-common", - "nym-vesting-contract-common", - "schemars", - "semver 1.0.16", - "serde", - "thiserror", - "vergen", -] - [[package]] name = "nym-vesting-contract-common" version = "0.7.0" dependencies = [ + "cosmwasm-schema", "cosmwasm-std", "nym-contracts-common", "nym-mixnet-contract-common", - "schemars", "serde", + "thiserror", ] [[package]] @@ -3263,7 +3244,6 @@ dependencies = [ "nym-network-defaults", "nym-types", "nym-validator-client", - "nym-vesting-contract", "nym-vesting-contract-common", "serde", "serde_json", @@ -3298,7 +3278,6 @@ dependencies = [ "nym-store-cipher", "nym-types", "nym-validator-client", - "nym-vesting-contract", "nym-vesting-contract-common", "nym-wallet-types", "once_cell", @@ -4468,18 +4447,9 @@ dependencies = [ [[package]] name = "serde-json-wasm" -version = "0.4.1" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "479b4dbc401ca13ee8ce902851b834893251404c4f3c65370a49e047a6be09a5" -dependencies = [ - "serde", -] - -[[package]] -name = "serde-json-wasm" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16a62a1fad1e1828b24acac8f2b468971dade7b8c3c2e672bcadefefb1f8c137" +checksum = "a15bee9b04dd165c3f4e142628982ddde884c2022a89e8ddf99c4829bf2c3a58" dependencies = [ "serde", ] @@ -4785,12 +4755,6 @@ dependencies = [ "loom", ] -[[package]] -name = "static_assertions" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" - [[package]] name = "string_cache" version = "0.8.4" @@ -5622,18 +5586,6 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9e79c4d996edb816c91e4308506774452e55e95c3c9de07b6729e17e15a5ef81" -[[package]] -name = "uint" -version = "0.9.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76f64bba2c53b04fcab63c01a7d7427eadc821e3bc48c34dc9ba29c501164b52" -dependencies = [ - "byteorder", - "crunchy", - "hex", - "static_assertions", -] - [[package]] name = "unicode-bidi" version = "0.3.10" diff --git a/nym-wallet/nym-wallet-types/Cargo.toml b/nym-wallet/nym-wallet-types/Cargo.toml index 63a4aa5daf..670739ad5f 100644 --- a/nym-wallet/nym-wallet-types/Cargo.toml +++ b/nym-wallet/nym-wallet-types/Cargo.toml @@ -11,7 +11,7 @@ serde_json = "1.0" strum = { version = "0.23", features = ["derive"] } ts-rs = "6.1.2" -cosmwasm-std = "1.2.0" +cosmwasm-std = "1.3.0" cosmrs = "=0.14.0" nym-config = { path = "../../common/config" } @@ -19,8 +19,6 @@ nym-network-defaults = { path = "../../common/network-defaults" } nym-mixnet-contract-common = { path = "../../common/cosmwasm-smart-contracts/mixnet-contract" } nym-validator-client = { path = "../../common/client-libs/validator-client" } nym-vesting-contract-common = { path = "../../common/cosmwasm-smart-contracts/vesting-contract" } -# Used for Type conversion, can be extracted but its a lot of work -nym-vesting-contract = { path = "../../contracts/vesting" } nym-types = { path = "../../common/types" } diff --git a/nym-wallet/src-tauri/Cargo.toml b/nym-wallet/src-tauri/Cargo.toml index 19b5f0e3c9..8c96f181f1 100644 --- a/nym-wallet/src-tauri/Cargo.toml +++ b/nym-wallet/src-tauri/Cargo.toml @@ -48,7 +48,7 @@ k256 = { version = "0.13", features = ["ecdsa", "sha256"] } base64 = "0.13" zeroize = { version = "1.5", features = ["zeroize_derive", "serde"] } -cosmwasm-std = "1.2.0" +cosmwasm-std = "1.3.0" cosmrs = "=0.14.0" nym-validator-client = { path = "../../common/client-libs/validator-client", features = [ @@ -60,8 +60,6 @@ nym-mixnet-contract-common = { path = "../../common/cosmwasm-smart-contracts/mix nym-vesting-contract-common = { path = "../../common/cosmwasm-smart-contracts/vesting-contract" } nym-config = { path = "../../common/config" } nym-coconut-interface = { path = "../../common/coconut-interface" } -# Used for Type conversion, can be extracted but its a lot of work -nym-vesting-contract = { path = "../../contracts/vesting" } nym-types = { path = "../../common/types" } nym-wallet-types = { path = "../nym-wallet-types" } nym-store-cipher = { path = "../../common/store-cipher", features = ["json"] } diff --git a/nym-wallet/src-tauri/src/utils.rs b/nym-wallet/src-tauri/src/utils.rs index e5aed4e092..579ccc5add 100644 --- a/nym-wallet/src-tauri/src/utils.rs +++ b/nym-wallet/src-tauri/src/utils.rs @@ -53,7 +53,9 @@ pub async fn try_convert_pubkey_to_mix_id( let res = nyxd_client!(state) .get_mixnode_details_by_identity(mix_identity) .await?; - Ok(res.map(|mixnode_details| mixnode_details.mix_id())) + Ok(res + .mixnode_details + .map(|mixnode_details| mixnode_details.mix_id())) } #[tauri::command] From 10696ae4dc8701d672e25cba777dde5ac6ec4958 Mon Sep 17 00:00:00 2001 From: mfahampshire Date: Thu, 3 Aug 2023 16:31:45 +0200 Subject: [PATCH 059/361] tweaked intro page --- documentation/operators/src/introduction.md | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/documentation/operators/src/introduction.md b/documentation/operators/src/introduction.md index acf29f9363..dc160825d6 100644 --- a/documentation/operators/src/introduction.md +++ b/documentation/operators/src/introduction.md @@ -1,19 +1,17 @@ # Introduction -This is Nym's operators guide, containing information and setup tutorials about the various pieces of Nym mixnet infrastructure nodes, such as mix node, gateway and network requester as well as Nyx chain validators. +This is Nym's Operators guide, containing information and setup guides for the various pieces of Nym mixnet infrastructure (mix node, gateway and network requester) and Nyx blockchain validators. -If you are new to Nym and want to learn about the mixnet, how to integrate with the network, developer tutorials and quickstart guides, check out the [Developer Portal](https://nymtech.net/developers/). +If you are new to Nym and want to learn about the mixnet, explore quickstart options and demos, learn how to integrate with the network, and follow developer tutorials check out the [Developer Portal](https://nymtech.net/developers/). -If you want to dive deeper into Nym architecture and such as different clients, existing applications like the desktop wallet, explorers or SDKs, visit [Nym's technical documentation](https://nymtech.net/docs/). +If you want to dive deeper into Nym's architecture, clients, nodes, and SDK examples visit the [technical docs](https://nymtech.net/docs/). ## Popular pages - - - -**Network Architecture:** -* [Network Overview](https://nymtech.net/docs/architecture/network-overview.html) -* [Mixnet Traffic Flow](https://nymtech.net/docs/architecture/traffic-flow.html) +**Binary Information** +* [Building Nym](./binaries/building-nym.md) +* [Pre-built Binaries](./binaries/pre-built-binaries.md) +* [Init & Configuration](./binaries/init-and-config.md) **Node setup and usage guides:** * [Mix nodes](./nodes/mix-node-setup.md) From 0b6eb177937fef01e4d458502fa9bfaf97242c99 Mon Sep 17 00:00:00 2001 From: mfahampshire Date: Thu, 3 Aug 2023 16:43:58 +0200 Subject: [PATCH 060/361] * removed old `wallet-address` flag * 'mixnode' -> 'mix node' for consistency in non-code / non-binary references --- documentation/operators/src/faq.md | 6 +-- .../operators/src/nodes/maintenance.md | 32 ++++++------- .../operators/src/nodes/mix-node-setup.md | 18 ++++---- .../operators/src/nodes/troubleshooting.md | 46 +++++++++---------- .../operators/src/preliminary-steps.md | 12 ++--- 5 files changed, 57 insertions(+), 57 deletions(-) diff --git a/documentation/operators/src/faq.md b/documentation/operators/src/faq.md index bc3c210352..a42af22825 100644 --- a/documentation/operators/src/faq.md +++ b/documentation/operators/src/faq.md @@ -2,7 +2,7 @@ ## Mixnet nodes -### What determines the rewards when running a mixnode? +### What determines the rewards when running a mix node? The stake required for a mix node to achieve maximum rewards is called mix node saturation point. This is calculated from the staking supply (all circulating supply + part of unlocked tokens). The target level of staking is to have 50% of the staking supply locked in mix nodes. @@ -16,7 +16,7 @@ For more detailed calculation, read our blog post [Nym Token Economics update](h Consider in which jurisdiction you reside and where do you want to run a mix node. Do you want to pay by crypto or not and what are the other important particularities for your case? We always recommend operators to try to chose smaller and decentralized VPS providers over the most known ones controlling a majority of the internet. We recieve some good feedback on these: Hertzner, DigitalOcean, Linode, Ghandi and Exoscale. Do your own research and share with the community. - + ### What's the Sphinx packet size? @@ -32,7 +32,7 @@ Right now only mix nodes are rewarded. We're working on gateway and service paym ### Are mixnodes whitelisted? -Nope, anyone can run a mixnode. Purely reliant on the node's reputation (self stake + delegations) & routing score. +Nope, anyone can run a mix node. Purely reliant on the node's reputation (self stake + delegations) & routing score. ## Validators and tokens diff --git a/documentation/operators/src/nodes/maintenance.md b/documentation/operators/src/nodes/maintenance.md index 26d5375a7d..7ffe1d6d58 100644 --- a/documentation/operators/src/nodes/maintenance.md +++ b/documentation/operators/src/nodes/maintenance.md @@ -2,7 +2,7 @@ ``` -The `nym-mixnode` binary is currently one point version ahead of the rest of the platform binaries due to a patch applied between releases. +The `nym-mix node` binary is currently one point version ahead of the rest of the platform binaries due to a patch applied between releases. ## Preliminary steps -Make sure you do the preparation listed in the [preliminary steps page](../preliminary-steps.md) before setting up your mixnode. +Make sure you do the preparation listed in the [preliminary steps page](../preliminary-steps.md) before setting up your mix node. ## Mix node setup -Now that you have built the [codebase](../binaries/building-nym.md), set up your [wallet](https://nymtech.net/docs/wallet/desktop-wallet.html), and have a VPS with the `nym-mixnode` binary, you can set up your mix node with the instructions below. +Now that you have built the [codebase](../binaries/building-nym.md), set up your [wallet](https://nymtech.net/docs/wallet/desktop-wallet.html), and have a VPS with the `nym-mix node` binary, you can set up your mix node with the instructions below. -To begin, move to `/taget/release` directory from which you run the node commands: +To begin, move to `/target/release` directory from which you run the node commands: ``` cd target/release @@ -63,10 +63,10 @@ To check available configuration options for initializing your node use: ``` ~~~ -Initalise your mixnode with the following command, replacing the value of `--id` with the moniker you wish to give your mixnode, and the `--wallet-address` with the Nym address you created earlier. Your `--host` must be publicly routable on the internet in order to mix packets, and can be either an Ipv4 or IPv6 address. The `$(curl ifconfig.me)` command returns your IP automatically using an external service. If you enter your IP address manually, enter it **without** any port information. +Initalise your mix node with the following command, replacing the value of `--id` with the moniker you wish to give your mix node. Your `--host` must be publicly routable on the internet in order to mix packets, and can be either an Ipv4 or IPv6 address. The `$(curl ifconfig.me)` command returns your IP automatically using an external service. If you enter your IP address manually, enter it **without** any port information. ``` -./nym-mixnode init --id --host $(curl ifconfig.me) --wallet-address +./nym-mixnode init --id --host $(curl ifconfig.me) ``` @@ -105,7 +105,7 @@ During the `init` process you will have the option to change the `http_api`, `ve ### Bonding your mix node ```admonish caution -From `v1.1.3`, if you unbond your mixnode that means you are leaving the mixnet and you will lose all your delegations (permanently). You can join again with the same identity key, however, you will start with **no delegations**. +From `v1.1.3`, if you unbond your mix node that means you are leaving the mixnet and you will lose all your delegations (permanently). You can join again with the same identity key, however, you will start with **no delegations**. ``` #### Bond via the Desktop wallet (recommended) @@ -183,7 +183,7 @@ Have a look at the saved configuration files in `$HOME/.nym/mixnodes/` to see mo ## Node Description (optional) -In order to easily identify your node via human-readable information later on in the development of the testnet when delegated staking is implemented, you can `describe` your mixnode with the following command: +In order to easily identify your node via human-readable information later on in the development of the testnet when delegated staking is implemented, you can `describe` your mix node with the following command: ``` ./nym-mixnode describe --id @@ -311,5 +311,5 @@ For more details see [Troubleshooting FAQ](../nodes/troubleshooting.md) ## Maintenance -For mixnode upgrade, firewall setup, port configuration, API endpoints, VPS suggestions, automation and more, see the [maintenance page](./maintenance.md) +For mix node upgrade, firewall setup, port configuration, API endpoints, VPS suggestions, automation and more, see the [maintenance page](./maintenance.md) diff --git a/documentation/operators/src/nodes/troubleshooting.md b/documentation/operators/src/nodes/troubleshooting.md index f063d985e0..d8fb566b58 100644 --- a/documentation/operators/src/nodes/troubleshooting.md +++ b/documentation/operators/src/nodes/troubleshooting.md @@ -78,7 +78,7 @@ Additional details can be obtained via various methods after you connect to your ##### Socket statistics with `ss` ``` -sudo ss -s -t | grep 1789 # if you have specified a different port in your mixnode config, change accordingly +sudo ss -s -t | grep 1789 # if you have specified a different port in your mix node config, change accordingly ``` This command should return a lot of data containing `ESTAB`. This command should work on every unix based system. @@ -90,7 +90,7 @@ This command should return a lot of data containing `ESTAB`. This command should lsof -v # install if not installed sudo apt install lsof -# run against mixnode port +# run against mix node port sudo lsof -i TCP:1789 # if you have specified a different port in your mixnode config, change accordingly ``` @@ -110,7 +110,7 @@ nym-mixno 103349 root 57u IPv6 1333229976 0t0 TCP [2a03:b0c0:3:d0::ff3: sudo journalctl -u nym-mixnode -o cat | grep "Since startup mixed" ``` -If you have created `nym-mixnode.service` file (i.e. you are running your mixnode via `systemd`) then this command shows you how many packets have you mixed so far, and should return a list of messages like this: +If you have created `nym-mixnode.service` file (i.e. you are running your mix node via `systemd`) then this command shows you how many packets have you mixed so far, and should return a list of messages like this: ``` 2021-05-18T12:35:24.057Z INFO nym_mixnode::node::metrics > Since startup mixed 233639 packets! @@ -140,7 +140,7 @@ For example `./target/debug/nym-network-requester --no-banner build-info --outpu nmap -p 1789 -Pn ``` -If your mixnode is configured properly it should output something like this: +If your mix node is configured properly it should output something like this: ``` bob@desktop:~$ nmap -p 1789 95.296.134.220 -Pn @@ -164,7 +164,7 @@ You can query gateways by replacing `mixnodes` with `gateways` in the above comm #### Check with Network API -We currently have an API set up returning our metrics tests of the network. There are two endpoints to ping for information about your mixnode, `report` and `history`. Find more information about this in the [Mixnodes metrics documentation](./maintenance.md#metrics--api-endpoints). +We currently have an API set up returning our metrics tests of the network. There are two endpoints to ping for information about your mix node, `report` and `history`. Find more information about this in the [Mixnodes metrics documentation](./maintenance.md#metrics--api-endpoints). ### Why is my node not mixing any packets? @@ -172,24 +172,24 @@ If you are still unable to see your node on the dashboard, or your node is decla - The firewall on your host machine is not configured properly. Checkout the [instructions](./maintenance.md#configure-your-firewall). - You provided incorrect information when bonding your node. -- You are running your mixnode from a VPS without IPv6 support. -- You did not use the `--announce-host` flag while running the mixnode from your local machine behind NAT. -- You did not configure your router firewall while running the mixnode from your local machine behind NAT, or you are lacking IPv6 support. -- Your mixnode is not running at all, it has either exited / panicked or you closed the session without making the node persistent. Check out the [instructions](./maintenance.md#automating-your-node-with-tmux-and-systemd). +- You are running your mix node from a VPS without IPv6 support. +- You did not use the `--announce-host` flag while running the mix node from your local machine behind NAT. +- You did not configure your router firewall while running the mix node from your local machine behind NAT, or you are lacking IPv6 support. +- Your mix node is not running at all, it has either exited / panicked or you closed the session without making the node persistent. Check out the [instructions](./maintenance.md#automating-your-node-with-tmux-and-systemd). ```admonish caution -Your mixnode **must speak both IPv4 and IPv6** in order to cooperate with other nodes and route traffic. This is a common reason behind many errors we are seeing among node operators, so check with your provider that your VPS is able to do this! +Your mix node **must speak both IPv4 and IPv6** in order to cooperate with other nodes and route traffic. This is a common reason behind many errors we are seeing among node operators, so check with your provider that your VPS is able to do this! ``` #### Incorrect bonding information -Check that you have provided the correct information when bonding your mixnode in the web wallet [interface](https://sandbox-wallet.nymtech.net/). When in doubt, un-bond and then re-bond your node! +Check that you have provided the correct information when bonding your mix node in the web wallet [interface](https://sandbox-wallet.nymtech.net/). When in doubt, un-bond and then re-bond your node! -> All delegated stake will be lost when un-bonding! However the mixnode must be operational in the first place for the delegation to have any effect. +> All delegated stake will be lost when un-bonding! However the mix node must be operational in the first place for the delegation to have any effect. #### Missing `announce-host` flag -On certain cloud providers such as AWS and Google Cloud, you need to do some additional configuration of your firewall and use `--host` with your **local ip** and `--announce-host` with the **public ip** of your mixnode host. +On certain cloud providers such as AWS and Google Cloud, you need to do some additional configuration of your firewall and use `--host` with your **local ip** and `--announce-host` with the **public ip** of your mix node host. If the difference between the two is unclear, contact the help desk of your VPS provider. @@ -222,15 +222,15 @@ bob@nym:~$ hostname -I ### Running on a local machine behind NAT with no fixed IP address -Your ISP has to be IPv6 ready if you want to run a mixnode on your local machine. Sadly, in 2020, most of them are not and you won't get an IPv6 address by default from your ISP. Usually it is a extra paid service or they simply don't offer it. +Your ISP has to be IPv6 ready if you want to run a mix node on your local machine. Sadly, in 2020, most of them are not and you won't get an IPv6 address by default from your ISP. Usually it is a extra paid service or they simply don't offer it. Before you begin, check if you have IPv6 [here](https://test-ipv6.cz/) or by running command explained in the [section above](./troubleshooting.md#no-ipv6-connectivity). If not, then don't waste your time to run a node which won't ever be able to mix any packet due to this limitation. Call your ISP and ask for IPv6, there is a plenty of it for everyone! -If all goes well and you have IPv6 available, then you will need to `init` the mixnode with an extra flag, `--announce-host`. You will also need to edit your `config.toml` file each time your IPv4 address changes, that could be a few days or a few weeks. Check the your IPv4 in the [section above](./troubleshooting.md#no-ipv6-connectivity). +If all goes well and you have IPv6 available, then you will need to `init` the mix node with an extra flag, `--announce-host`. You will also need to edit your `config.toml` file each time your IPv4 address changes, that could be a few days or a few weeks. Check the your IPv4 in the [section above](./troubleshooting.md#no-ipv6-connectivity). Additional configuration on your router might also be needed to allow traffic in and out to port 1789 and IPv6 support. -Here is a sample of the `init` command example to create the mixnode config. +Here is a sample of the `init` command example to create the mix node config. ``` ./nym-mixnode init --id --host 0.0.0.0 --announce-host 85.160.12.13 @@ -244,7 +244,7 @@ Make sure you check if your node is really mixing. We are aiming to improve the ### Accidentally killing your node process on exiting session -When you close your current terminal session, you need to make sure you don't kill the mixnode process! There are multiple ways on how to make it persistent even after exiting your ssh session, the easiest solution is to use `tmux` or `nohup`, and the more elegant solution is to run the node with `systemd`. Read the automation manual [here](./maintenance.md#automating-your-node-with-tmux-and-systemd). +When you close your current terminal session, you need to make sure you don't kill the mix node process! There are multiple ways on how to make it persistent even after exiting your ssh session, the easiest solution is to use `tmux` or `nohup`, and the more elegant solution is to run the node with `systemd`. Read the automation manual [here](./maintenance.md#automating-your-node-with-tmux-and-systemd). ### Common errors and warnings @@ -273,24 +273,24 @@ Rocket runs on port `8000` by default. Although at this stage we need Rocket to #### `failed to receive reply to our echo packet within 1.5s. Stopping the test` -This relates to the VerLoc implementation that appeared in `0.10.1`, which has a particularly high log sensitivity. This warning means that the echo packet sent to the mixnode was received, but not sent back. _This will not affect the rate of rewards or performance metrics of your mixnode in the testnet at this point._ +This relates to the VerLoc implementation that appeared in `0.10.1`, which has a particularly high log sensitivity. This warning means that the echo packet sent to the mix node was received, but not sent back. _This will not affect the rate of rewards or performance metrics of your mix node in the testnet at this point._ #### `Connection to :1789 seems to be dead` -This warning is normal at the moment, and is _nothing to do with your mixnode!_ It is simply a warning that your node is unable to connect to other peoples' mixnodes for some reason, most likely because they are offline or poorly configured. +This warning is normal at the moment, and is _nothing to do with your mix node!_ It is simply a warning that your node is unable to connect to other peoples' mixnodes for some reason, most likely because they are offline or poorly configured. ---> ### Can I use a port other than 1789 ? Yes! Here is what you will need to do: -Assuming you would like to use port `1337` for your mixnode, you need to open the new port (and close the old one): +Assuming you would like to use port `1337` for your mix node, you need to open the new port (and close the old one): ``` sudo ufw allow 1337 sudo ufw deny 1789 ``` -And then edit the mixnode's config. +And then edit the mix node's config. > If you want to change the port for an already running node, you need to stop the process before editing your config file. @@ -304,9 +304,9 @@ nano ~/.nym/mixnodes/alice-node/config/config.toml You will need to edit two parts of the file. `announce_address` and `listening_address` in the config.toml file. Simply replace `:1789` (the default port) with `:1337` (your new port) after your IP address. -Finally, restart your node. You should see if the mixnode is using the port you have changed in the config.toml file right after you run the node. +Finally, restart your node. You should see if the mix node is using the port you have changed in the config.toml file right after you run the node. -### What is `verloc` and do I have to configure my mixnode to implement it? +### What is `verloc` and do I have to configure my mix node to implement it? `verloc` is short for _verifiable location_. Mixnodes and gateways now measure speed-of-light distances to each other, in an attempt to verify how far apart they are. In later releases, this will allow us to algorithmically verify node locations in a non-fakeable and trustworthy manner. diff --git a/documentation/operators/src/preliminary-steps.md b/documentation/operators/src/preliminary-steps.md index 53f5383838..d36ca76da4 100644 --- a/documentation/operators/src/preliminary-steps.md +++ b/documentation/operators/src/preliminary-steps.md @@ -1,23 +1,23 @@ # Preliminary Steps -> The Nym `mixnode`, `gateway` and `network-requester` binaries were built in the [building nym](../binaries/building-nym.md) section. If you haven't yet built Nym and want to run the code, go there first. +> The Nym `mix node`, `gateway` and `network-requester` binaries were built in the [building nym](../binaries/building-nym.md) section. If you haven't yet built Nym and want to run the code, go there first. ## Preliminary steps -There are a couple of steps that need completing before starting to set up your mixnode, gateway or a network requester: +There are a couple of steps that need completing before starting to set up your mix node, gateway or a network requester: - preparing your [desktop wallet](https://nymtech.net/docs/wallet/desktop-wallet.html) or [CLI wallet](https://nymtech.net/docs/wallet/cli-wallet.html). - requisitioning a VPS (Virtual Private Server) ### Wallet preparation #### Mainnet -Before you initialise and run your mixnode, head to our [website](https://nymtech.net/download/) and download the Nym wallet for your operating system. If pre-compiled binaries for your operating system aren't available, you can build the wallet yourself with instructions [here](https://nymtech.net/docs/wallet/desktop-wallet.html). +Before you initialise and run your mix node, head to our [website](https://nymtech.net/download/) and download the Nym wallet for your operating system. If pre-compiled binaries for your operating system aren't available, you can build the wallet yourself with instructions [here](https://nymtech.net/docs/wallet/desktop-wallet.html). -If you don't already have one, please create a Nym address using the wallet, and fund it with tokens. The minimum amount required to bond a mixnode is 100 `NYM`, but make sure you have a bit more to account for gas costs. +If you don't already have one, please create a Nym address using the wallet, and fund it with tokens. The minimum amount required to bond a mix node is 100 `NYM`, but make sure you have a bit more to account for gas costs. `NYM` can be purchased via Bity from the wallet itself with BTC or fiat, and is currently present on several [exchanges](https://www.coingecko.com/en/coins/nym#markets). -> Remember that you can **only** use Cosmos `NYM` tokens to bond your mixnode. You **cannot** use ERC20 representations of `NYM` to run a node. +> Remember that you can **only** use Cosmos `NYM` tokens to bond your mix node. You **cannot** use ERC20 representations of `NYM` to run a node. #### Sandbox testnet @@ -32,7 +32,7 @@ For now, see the below rough specs: - Processors: 2 cores are fine. Get the fastest CPUs you can afford. -#### For Mixnode +#### For mix node - RAM: Memory requirements are very low - typically a mix node may use only a few hundred MB of RAM. - Disks: The mixnodes require no disk space beyond a few bytes for the configuration files. From c1e8ebd46a26e3389b2700bae49ae01b91cd63cc Mon Sep 17 00:00:00 2001 From: mfahampshire Date: Thu, 3 Aug 2023 16:46:05 +0200 Subject: [PATCH 061/361] typo fix --- documentation/operators/src/nodes/gateway-setup.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/documentation/operators/src/nodes/gateway-setup.md b/documentation/operators/src/nodes/gateway-setup.md index 026e7afde0..f1cb589756 100644 --- a/documentation/operators/src/nodes/gateway-setup.md +++ b/documentation/operators/src/nodes/gateway-setup.md @@ -17,7 +17,7 @@ Make sure you do the preparation listed in the [preliminary steps page](../preli ## Gateway setup Now that you have built the codebase, set up your wallet, and have a VPS with the `nym-gateway` binary, you can set up your gateway with the instructions below. -To begin, move to `/taget/release` directory from which you run the node commands: +To begin, move to `/target/release` directory from which you run the node commands: ``` cd target/release From e18c541400b3220a36c73bb9027466f3c1a94e8b Mon Sep 17 00:00:00 2001 From: mfahampshire Date: Thu, 3 Aug 2023 19:28:13 +0200 Subject: [PATCH 062/361] added custom 404 + updated platform version --- documentation/docs/book.toml | 6 +++--- documentation/docs/src/not-found.md | 11 +++++++++++ 2 files changed, 14 insertions(+), 3 deletions(-) create mode 100644 documentation/docs/src/not-found.md diff --git a/documentation/docs/book.toml b/documentation/docs/book.toml index b70997a556..f507798295 100644 --- a/documentation/docs/book.toml +++ b/documentation/docs/book.toml @@ -49,8 +49,8 @@ assets_version = "2.0.0" # do not edit: managed by `mdbook-admonish install` [preprocessor.variables.variables] minimum_rust_version = "1.66" # vars for links: TODO think on how to streamline updating -platform_release_version = "v1.1.22" -wallet_release_version = "v1.2.5" +platform_release_version = "v1.1.26" +wallet_release_version = "v1.2.7" [preprocessor.last-changed] command = "mdbook-last-changed" @@ -92,7 +92,7 @@ git-repository-icon = "fa-github" # edit-url-template = "https://github.com/rust-lang/mdBook/edit/master/guide/{path}" # site-url = "/docs/" # cname = "nymtech.net" -# input-404 = "not-found.md" # todo make custom 404 page +input-404 = "not-found.md" [output.html.fold] enable = true # whether or not to enable section folding diff --git a/documentation/docs/src/not-found.md b/documentation/docs/src/not-found.md new file mode 100644 index 0000000000..3d203a9bb8 --- /dev/null +++ b/documentation/docs/src/not-found.md @@ -0,0 +1,11 @@ +# Page Not Found + +You seem to have followed a link to a page that no longer exists. Our documentation is changing and growing over time, so this page has most likely been moved somewhere else. + +For node setup guides, see the [Operators Guides book](https://nymtech.net/operators). + +For developer tutorials, app guides, and demo and community apps, see the [Developer Portal](https://nymtech.net/developers). + +If you are looking for information on network architecture traffic flow, clients, the SDKs, or interacting with the Nyx blockchain, check the sitemap on the left hand side of the screen. + +If you still can't find what you're looking for get in touch with us via [Matrix](TODO). \ No newline at end of file From cc65236085b1b2a668773ee24ad3afc3d2795b50 Mon Sep 17 00:00:00 2001 From: mfahampshire Date: Thu, 3 Aug 2023 19:28:37 +0200 Subject: [PATCH 063/361] * deleted old setup guides * added new info on operator guides * adding stubs for in-depth node pages --- documentation/docs/src/SUMMARY.md | 13 +- documentation/docs/src/nodes/gateway-setup.md | 262 ------- documentation/docs/src/nodes/gateway.md | 1 + .../docs/src/nodes/mix-node-setup.md | 566 --------------- documentation/docs/src/nodes/mixnode.md | 17 + .../docs/src/nodes/network-requester-setup.md | 255 ------- .../docs/src/nodes/network-requester.md | 1 + documentation/docs/src/nodes/overview.md | 9 + documentation/docs/src/nodes/setup-guides.md | 8 - .../docs/src/nodes/troubleshooting.md | 368 ---------- .../docs/src/nodes/validator-setup.md | 669 ------------------ documentation/docs/src/nodes/validator.md | 12 + 12 files changed, 46 insertions(+), 2135 deletions(-) delete mode 100644 documentation/docs/src/nodes/gateway-setup.md create mode 100644 documentation/docs/src/nodes/gateway.md delete mode 100644 documentation/docs/src/nodes/mix-node-setup.md create mode 100644 documentation/docs/src/nodes/mixnode.md delete mode 100644 documentation/docs/src/nodes/network-requester-setup.md create mode 100644 documentation/docs/src/nodes/network-requester.md create mode 100644 documentation/docs/src/nodes/overview.md delete mode 100644 documentation/docs/src/nodes/setup-guides.md delete mode 100644 documentation/docs/src/nodes/troubleshooting.md delete mode 100644 documentation/docs/src/nodes/validator-setup.md create mode 100644 documentation/docs/src/nodes/validator.md diff --git a/documentation/docs/src/SUMMARY.md b/documentation/docs/src/SUMMARY.md index 8d9c174f5a..c9fc9c6d1d 100644 --- a/documentation/docs/src/SUMMARY.md +++ b/documentation/docs/src/SUMMARY.md @@ -14,12 +14,11 @@ # Nodes -- [Node Setup Guides](nodes/setup-guides.md) - - [Mix Node](nodes/mix-node-setup.md) - - [Gateway](nodes/gateway-setup.md) - - [Network Requester](nodes/network-requester-setup.md) - - [Validator](nodes/validator-setup.md) -- [Troubleshooting](nodes/troubleshooting.md) +- [Node Types (Previously Setup Guides)](nodes/overview.md) + - [Mix Node](nodes/mixnode.md) + - [Gateway](nodes/gateway.md) + - [Network Requester](nodes/network-requester.md) + - [Validator](nodes/validator.md) # Clients - [Clients Overview](clients/overview.md) @@ -40,7 +39,7 @@ - [Mixnet Explorer](explorers/mixnet-explorer.md) # Nyx Blockchain - +- [Interacting with Nyx Chain and Smart Contracts](nyx/interacting-with-chain.md) - [Smart Contracts](nyx/smart-contracts.md) - [Mixnet Contract](nyx/mixnet-contract.md) - [Vesting Contract](nyx/vesting-contract.md) diff --git a/documentation/docs/src/nodes/gateway-setup.md b/documentation/docs/src/nodes/gateway-setup.md deleted file mode 100644 index fe48fc8566..0000000000 --- a/documentation/docs/src/nodes/gateway-setup.md +++ /dev/null @@ -1,262 +0,0 @@ -# Gateways - -> The Nym gateway was built in the [building nym](../binaries/building-nym.md) section. If you haven't yet built Nym and want to run the code, go there first. - -## Current version -``` - -``` - - -## Preliminary steps -There are a couple of steps that need completing before starting to set up your gateway: - -- preparing your wallet -- requisitioning a VPS (Virtual Private Server) - -### Wallet preparation -#### Mainnet -Before you initialise and run your gateway, head to our [website](https://nymtech.net/download/) and download the Nym wallet for your operating system. If pre-compiled binaries for your operating system aren't availiable, you can build the wallet yourself with instructions [here](../wallet/desktop-wallet.md). - -If you don't already have one, please create a Nym address using the wallet, and fund it with tokens. The minimum amount required to bond a gateway is 100 `NYM`, but make sure you have a bit more to account for gas costs. - -`NYM` can be purchased via Bity from the wallet itself with BTC or fiat, and is currently present on several exchanges. - -> Remember that you can **only** use native Cosmos `NYM` tokens to bond your gateway. You **cannot** use ERC20 representations of `NYM` to run a node. - - -#### Sandbox testnet -Make sure to download a wallet and create an account as outlined above. Then head to our [token faucet](https://faucet.nymtech.net/) and get some tokens to use to bond it. - -### VPS Hardware Specs -You will need to rent a VPS to run your mix node on. One key reason for this is that your node **must be able to send TCP data using both IPv4 and IPv6** (as other nodes you talk to may use either protocol. - -We currently have these _rough_ specs for VPS hardware: - -- Processors: 2 cores are fine. Get the fastest CPUs you can afford. -- RAM: Memory requirements depend on the amount of users your Gateway will be serving at any one time. If you're just going to be using it yourself, then minimal RAM is fine. **If you're running your Gateway as part of a Service Grant, get something with at least 4GB RAM.** -- Disks: much like the amount of RAM your Gateway could use, the amount of disk space required will vary with the amount of users your Gateway is serving. **If you're running your Gateway as part of a Service Grant, get something with at least 40GB storage.** - -## Gateway setup -Now that you have built the codebase, set up your wallet, and have a VPS with the `nym-gateway` binary, you can set up your gateway with the instructions below. - -### Viewing command help -You can check that your binaries are properly compiled with: - -``` -./nym-gateway --help -``` - -~~~admonish example collapsible=true title="Console output" -``` - -``` -~~~ - -You can also check the various arguments required for individual commands with: - -``` -./nym-gateway --help -``` - -### Initialising your gateway -To check available configuration options use: - -``` - ./nym-gateway init --help -``` - -~~~admonish example collapsible=true title="Console output" -``` - -``` -~~~ - -The following command returns a gateway on your current IP with the `id` of `supergateway`: - -``` -./nym-gateway init --id supergateway --host $(curl ifconfig.me) --wallet-address n1eufxdlgt0puwrwptgjfqne8pj4nhy2u5ft62uq -``` - -~~~admonish example collapsible=true title="Console output" -``` - -``` -~~~ - -The `$(curl ifconfig.me)` command above returns your IP automatically using an external service. Alternatively, you can enter your IP manually wish. If you do this, remember to enter your IP **without** any port information. - -### Bonding your gateway -#### Via the Desktop wallet -You can bond your gateway via the Desktop wallet. - -* Open your wallet, and head to the `Bond` page, then select the node type and input your node details. Press `continue` - -* You will be asked to run a the `sign` command with your `gateway` - copy and paste the long signature as the value of `--contract-msg` and run it. It will look something like this: - -~~~admonish example collapsible=true title="Console output" -``` -./nym-gateway sign --id upgrade_test --contract-msg 2Mf8xYytgEeyJke9LA7TjhHoGQWNBEfgHZtTyy2krFJfGHSiqy7FLgTnauSkQepCZTqKN5Yfi34JQCuog9k6FGA2EjsdpNGAWHZiuUGDipyJ6UksNKRxnFKhYW7ri4MRduyZwbR98y5fQMLAwHne1Tjm9cXYCn8McfigNt77WAYwBk5bRRKmC34BJMmWcAxphcLES2v9RdSR68tkHSpy2C8STfdmAQs3tZg8bJS5Qa8pQdqx14TnfQAPLk3QYCynfUJvgcQTrg29aqCasceGRpKdQ3Tbn81MLXAGAs7JLBbiMEAhCezAr2kEN8kET1q54zXtKz6znTPgeTZoSbP8rzf4k2JKHZYWrHYF9JriXepuZTnyxAKAxvGFPBk8Z6KAQi33NRQkwd7MPyttatHna6kG9x7knffV6ebGzgRBf7NV27LurH8x4L1uUXwm1v1UYCA1WSBQ9Pp2JW69k5v5v7G9gBy8RUcZnMbeL26Qqb8WkuGcmuHhaFfoqSfV7PRHPpPT4M8uRqUyR4bjUtSJJM1yh6QSeZk9BEazzoJqPeYeGoiFDZ3LMj2jesbJweQR4caaYuRczK92UGSSqu9zBKmE45a - - - _ __ _ _ _ __ ___ - | '_ \| | | | '_ \ _ \ - | | | | |_| | | | | | | - |_| |_|\__, |_| |_| |_| - |___/ - - (nym-gateway - version {{platform_release_version}}) - - ->>> attempting to sign 2Mf8xYytgEeyJke9LA7TjhHoGQWNBEfgHZtTyy2krFJfGHSiqy7FLgTnauSkQepCZTqKN5Yfi34JQCuog9k6FGA2EjsdpNGAWHZiuUGDipyJ6UksNKRxnFKhYW7ri4MRduyZwbR98y5fQMLAwHne1Tjm9cXYCn8McfigNt77WAYwBk5bRRKmC34BJMmWcAxphcLES2v9RdSR68tkHSpy2C8STfdmAQs3tZg8bJS5Qa8pQdqx14TnfQAPLk3QYCynfUJvgcQTrg29aqCasceGRpKdQ3Tbn81MLXAGAs7JLBbiMEAhCezAr2kEN8kET1q54zXtKz6znTPgeTZoSbP8rzf4k2JKHZYWrHYF9JriXepuZTnyxAKAxvGFPBk8Z6KAQi33NRQkwd7MPyttatHna6kG9x7knffV6ebGzgRBf7NV27LurH8x4L1uUXwm1v1UYCA1WSBQ9Pp2JW69k5v5v7G9gBy8RUcZnMbeL26Qqb8WkuGcmuHhaFfoqSfV7PRHPpPT4M8uRqUyR4bjUtSJJM1yh6QSeZk9BEazzoJqPeYeGoiFDZ3LMj2jesbJweQR4caaYuRczK92UGSSqu9zBKmE45a ->>> decoding the message... ->>> message to sign: {"nonce":0,"algorithm":"ed25519","message_type":"gateway-bonding","content":{"sender":"n1ewmme88q22l8syvgshqma02jv0vqrug9zq9dy8","proxy":null,"funds":[{"denom":"unym","amount":"100000000"}],"data":{"gateway":{"host":"62.240.134.189","mix_port":1789,"clients_port":9000,"location":"62.240.134.189","sphinx_key":"FKbuN7mPdoCG9jA3CkAfXxC5X4rHhqeMVtmfRtJ3cFZd","identity_key":"3RoAhR8gEdfBETMjm2vbMFzKddxXDdE9ygBAnJHWqSzD","version":"1.1.13"}}}} -``` -~~~ - -* Copy the resulting signature: - -``` ->>> The base58-encoded signature is: -2SPDjLjX4b6XEtkgG7yD8Znsb1xycL1edFvRK4JcVnPsM9k6HXEUUeVS6rswRiYxoj1bMgiRKyPDwiksiuyxu8Xi -``` - -* And paste it into the wallet nodal, then confirm the transaction. - -![Paste Signature](../images/wallet-sign.png) - -* Your gateway is now bonded. - -> You are asked to `sign` a transaction on bonding so that the mixnet smart contract is able to map your nym address to your node. This allows us to create a nonce for each account and defend against replay attacks. - -#### Via the CLI (power users) -If you want to bond your mix node via the CLI, then check out the [relevant section in the Nym CLI](../tools/nym-cli.md#bond-a-gateway) docs. - -### Running your gateway -The `run` command starts the gateway: - -``` -./nym-gateway run --id supergateway -``` - -### Upgrading your gateway -Upgrading your node is a two-step process: -* Updating the binary and `config.toml` on your VPS -* Updating the node information in the [mixnet smart contract](../nyx/mixnet-contract.md). **This is the information that is present on the [mixnet explorer](https://explorer.nymtech.net)**. - ->These instructions are specifically regarding upgrading your gateway binary from one version to another. If you want to change node information such as the listening port, you can do this by clicking the `node settings` tab in the `bond` page of the wallet. - -#### Step 1: upgrading your binary -Follow these steps to upgrade your binary and update its config file: -* pause your gateway process. -* replace the existing binary with the newest binary (which you can either compile yourself or grab from our [releases page](https://github.com/nymtech/nym/releases)). -* re-run `init` with the same values as you used initially. **This will just update the config file, it will not overwrite existing keys**. -* restart your gateway process with the new binary. - -> Do **not** use the `upgrade` command: there is a known error with the command that will be fixed in a subsequent release. - -#### Step 2: updating your node information in the smart contract -Follow these steps to update the information about your node which is publically avaliable from the [Nym API](https://validator.nymtech.net/api/swagger/index.html) and information displayed on the [mixnet explorer](https://explorer.nymtech.net). - -You can either do this graphically via the Desktop Wallet, or the CLI. - -#### Updating node information via the Desktop Wallet -* Navigate to the `Bonding` page and click the `Node Settings` link in the top right corner: -![Bonding page](../images/wallet-screenshots/bonding.png) - -* Update the fields in the `Node Settings` page and click `Submit changes to the blockchain`. -![Node Settings Page](../images/wallet-screenshots/node_settings.png) - -#### Updating node information via the CLI -If you want to bond your mix node via the CLI, then check out the [relevant section in the Nym CLI](../tools/nym-cli.md#upgrade-a-mix-node) docs. - -## VPS Setup and Automation -### Configure your firewall -Although your gateway is now ready to receive traffic, your server may not be - the following commands will allow you to set up a properly configured firewall using `ufw`: - -``` -# check if you have ufw installed -ufw version -# if it is not installed, install with -sudo apt install ufw -y -# enable ufw -sudo ufw enable -# check the status of the firewall -sudo ufw status -``` - -Finally open your gateway's p2p port, as well as ports for ssh and incoming traffic connections: - -``` -sudo ufw allow 1789,22,9000/tcp -# check the status of the firewall -sudo ufw status -``` - -For more information about your gateway's port configuration, check the [gateway port reference table](#gateway-port-reference) below. - -### Automating your gateway with systemd -Although it's not totally necessary, it's useful to have the gateway automatically start at system boot time. Here's a systemd service file to do that: - -```ini -[Unit] -Description=Nym Gateway ({{platform_release_version}}) -StartLimitInterval=350 -StartLimitBurst=10 - -[Service] -User=nym -LimitNOFILE=65536 -ExecStart=/home/nym/nym-gateway run --id supergateway -KillSignal=SIGINT -Restart=on-failure -RestartSec=30 - -[Install] -WantedBy=multi-user.target -``` - -Put the above file onto your system at `/etc/systemd/system/nym-gateway.service`. - -Change the path in `ExecStart` to point at your gateway binary (`nym-gateway`), and the `User` so it is the user you are running as. - -If you have built nym on your server, and your username is `jetpanther`, then the start command might look like this: - -`ExecStart=/home/jetpanther/nym/target/release/nym-gateway run --id your-id`. Basically, you want the full `/path/to/nym-gateway run --id whatever-your-node-id-is` - - -Then run: - -``` -systemctl enable nym-gateway.service -``` - -Start your node: - -``` -service nym-gateway start -``` - -This will cause your node to start at system boot time. If you restart your machine, the node will come back up automatically. - -You can also do `service nym-gateway stop` or `service nym-gateway restart`. - -Note: if you make any changes to your systemd script after you've enabled it, you will need to run: - -``` -systemctl daemon-reload -``` - -This lets your operating system know it's ok to reload the service configuration. - -## Gateway related Validator API endpoints -Numerous gateway related API endpoints are documented on the Validator API's [Swagger Documentation](https://validator.nymtech.net/api/swagger/index.html). There you can also try out various requests from your broswer, and download the response from the API. Swagger will also show you what commands it is running, so that you can run these from an app or from your CLI if you prefer. - -## Ports -All gateway specific port configuration can be found in `$HOME/.nym/gateways//config/config.toml`. If you do edit any port configs, remember to restart your gateway. - -### Gateway port reference -| Default port | Use | -|--------------|---------------------------| -| 1789 | Listen for Mixnet traffic | -| 9000 | Listen for Client traffic | diff --git a/documentation/docs/src/nodes/gateway.md b/documentation/docs/src/nodes/gateway.md new file mode 100644 index 0000000000..f4ee68e242 --- /dev/null +++ b/documentation/docs/src/nodes/gateway.md @@ -0,0 +1 @@ +# Gateway diff --git a/documentation/docs/src/nodes/mix-node-setup.md b/documentation/docs/src/nodes/mix-node-setup.md deleted file mode 100644 index 408ef20b99..0000000000 --- a/documentation/docs/src/nodes/mix-node-setup.md +++ /dev/null @@ -1,566 +0,0 @@ -# Mix Nodes - -> The Nym mix node binary was built in the [building nym](../binaries/building-nym.md) section. If you haven't yet built Nym and want to run the code, go there first. - -## Current version -``` - -``` - -The `nym-mixnode` binary is currently one point version ahead of the rest of the platform binaries due to a patch applied between releases. - -## Preliminary steps - -There are a couple of steps that need completing before starting to set up your mix node: - -- preparing your wallet -- requisitioning a VPS (Virtual Private Server) - -### Wallet preparation - -#### Mainnet - -Before you initialise and run your mixnode, head to our [website](https://nymtech.net/download/) and download the Nym wallet for your operating system. If pre-compiled binaries for your operating system aren't availiable, you can build the wallet yourself with instructions [here](../wallet/desktop-wallet.md). - -If you don't already have one, please create a Nym address using the wallet, and fund it with tokens. The minimum amount required to bond a mixnode is 100 `NYM`, but make sure you have a bit more to account for gas costs. - -`NYM` can be purchased via Bity from the wallet itself with BTC or fiat, and is currently present on several exchanges. - -> Remember that you can **only** use Cosmos `NYM` tokens to bond your mixnode. You **cannot** use ERC20 representations of `NYM` to run a node. - -#### Sandbox testnet - -Make sure to download a wallet and create an account as outlined above. Then head to our [token faucet](https://faucet.nymtech.net/) and get some tokens to use to bond it. - -### VPS Hardware Specs - -You will need to rent a VPS to run your mix node on. One key reason for this is that your node **must be able to send TCP data using both IPv4 and IPv6** (as other nodes you talk to may use either protocol). - -For the moment, we haven't put a great amount of effort into optimizing concurrency to increase throughput, so don't bother provisioning a beastly server with multiple cores. This will change when we get a chance to start doing performance optimizations in a more serious way. Sphinx packet decryption is CPU-bound, so once we optimise, more fast cores will be better. - -For now, see the below rough specs: - -- Processors: 2 cores are fine. Get the fastest CPUs you can afford. -- RAM: Memory requirements are very low - typically a mix node may use only a few hundred MB of RAM. -- Disks: The mixnodes require no disk space beyond a few bytes for the configuration files. - -## Mix node setup - -Now that you have built the codebase, set up your wallet, and have a VPS with the `nym-mixnode` binary, you can set up your mix node with the instructions below. - -### Viewing command help - -You can check that your binaries are properly compiled with: - -``` -./nym-mixnode --help -``` - -Which should return a list of all avaliable commands. - -~~~admonish example collapsible=true title="Console output" -``` - -``` -~~~ - -You can also check the various arguments required for individual commands with: - -``` -./nym-mixnode --help -``` - -### Initialising your mix node - -To check available configuration options for initializing your node use: - -``` -./nym-mixnode init --help -``` - -~~~admonish example collapsible=true title="Console output" -``` - -``` -~~~ - -Initalise your mixnode with the following command, replacing the value of `--id` with the moniker you wish to give your mixnode, and the `--wallet-address` with the Nym address you created earlier. Your `--host` must be publicly routable on the internet in order to mix packets, and can be either an Ipv4 or IPv6 address. The `$(curl ifconfig.me)` command returns your IP automatically using an external service. If you enter your IP address manually, enter it **without** any port information. - -``` -./nym-mixnode init --id winston-smithnode --host $(curl ifconfig.me) --wallet-address n1eufxdlgt0puwrwptgjfqne8pj4nhy2u5ft62uq -``` - -~~~admonish example collapsible=true title="Console output" -``` - -``` -~~~ - -> The `init` command will refuse to destroy existing mix node keys. - -During the `init` process you will have the option to change the `http_api`, `verloc` and `mixnode` ports from their default settings. If you wish to change these in the future you can edit their values in the `config.toml` file created by the initialization process, which is located at `~/.nym/mixnodes//`. - -### Bonding your mix node - -```admonish caution -From `v1.1.3`, if you unbond your mixnode that means you are leaving the mixnet and you will lose all your delegations (permanently). You can join again with the same identity key, however, you will start with **no delegations**. -``` - -#### Bond via the Desktop wallet (recommended) - -You can bond your mix node via the Desktop wallet. - -* Open your wallet, and head to the `Bond` page, then select the node type and input your node details. Press `continue` - -* You will be asked to run a the `sign` command with your `gateway` - copy and paste the long signature as the value of `--contract-msg` and run it. It will look something like this: - -~~~admonish example collapsible=true title="Console output" -``` -./nym-mixnode sign --id upgrade_test --contract-msg 5XrvVEMzRJk2AcT2h1o6ErZNb8z1ZzD3h7teipBW3NUtrtYq7vu4DRMgzZRTPVPnyr2YWCxpmKCMFaEXvksnJ -4jt7np3NMLxsLMrFjEBhh67Crtjy4868vCzAivUqzdc365RiqxQQKtv4r9eTk9mTbE9JY8U3TxzKJCSGcBqbrb9JX3HrZVWm6tqbUYbsnku9pqnfeyeUiaYKY44Lm72TYrkZfRrMAZLMATiXT1ntmiKqT37HzRxNZjiH8qHeQEoRHkgDsmXDXRbfppGTpPrN7R4sjynJzehzUBZ8Ug7ovT9FoAHb8kuVQhUiMs1js6tdwtthzQMbPi9vwxUtVvjYknN2fnJgMnckEhzJJpJDCNdH7YhpPaWQnGVVS334mskiuqkbRVrFPJN2nnwArHr3L2cLxSMk9toKfw7ViKJ2p5E5JxiSmKY1cFGZ7uRLsuQ833PJN9JE8crPtkBNefqkbFNz68S5jPmzUShSvAc4TqXKeovDASFmmhKaPqLUrfsSWm7nzuKnzJSMADF6xSuwr9cknMoirqkRkLe7ybJ2ERwSdf5cUxMjF7yjS8tW9hZudnTUb1uPNDuSmPPVrCR12XZyFzBvVgxH51ZNJTym46nqnfA881LQcmFMnCwJf39rVJ4ASLnzEzmuwXj75QoB9ce9kiLmoBNLYe4QKSB6gDd858VnBtBNQELVuCCZbrTYuSCeNdUFhvMwD4kryc1pBYUa8Ro81F3QVfiKN - - _ __ _ _ _ __ ___ - | '_ \| | | | '_ \ _ \ - | | | | |_| | | | | | | - |_| |_|\__, |_| |_| |_| - |___/ - - (nym-mixnode - version {{mix_node_release_version}}) - - ->>> attempting to sign 5XrvVEMzRJk2AcT2h1o6ErZNb8z1ZzD3h7teipBW3NUtrtYq7vu4DRMgzZRTPVPnyr2YWCxpmKCMFaEXvksnJ4jt7np3NMLxsLMrFjEBhh67Crtjy4868vCzAivUqzdc365RiqxQQKtv4r9eTk9mTbE9JY8U3TxzKJCSGcBqbrb9JX3HrZVWm6tqbUYbsnku9pqnfeyeUiaYKY44Lm72TYrkZfRrMAZLMATiXT1ntmiKqT37HzRxNZjiH8qHeQEoRHkgDsmXDXRbfppGTpPrN7R4sjynJzehzUBZ8Ug7ovT9FoAHb8kuVQhUiMs1js6tdwtthzQMbPi9vwxUtVvjYknN2fnJgMnckEhzJJpJDCNdH7YhpPaWQnGVVS334mskiuqkbRVrFPJN2nnwArHr3L2cLxSMk9toKfw7ViKJ2p5E5JxiSmKY1cFGZ7uRLsuQ833PJN9JE8crPtkBNefqkbFNz68S5jPmzUShSvAc4TqXKeovDASFmmhKaPqLUrfsSWm7nzuKnzJSMADF6xSuwr9cknMoirqkRkLe7ybJ2ERwSdf5cUxMjF7yjS8tW9hZudnTUb1uPNDuSmPPVrCR12XZyFzBvVgxH51ZNJTym46nqnfA881LQcmFMnCwJf39rVJ4ASLnzEzmuwXj75QoB9ce9kiLmoBNLYe4QKSB6gDd858VnBtBNQELVuCCZbrTYuSCeNdUFhvMwD4kryc1pBYUa8Ro81F3QVfiKN ->>> decoding the message... ->>> message to sign: {"nonce":0,"algorithm":"ed25519","message_type":"mixnode-bonding","content":{"sender":"n1eufxdlgt0puwrwptgjfqne8pj4nhy2u5ft62uq","proxy":null,"funds":[{"denom":"unym","amount":"100000000"}],"data":{"mix_node":{"host":"62.240.134.189","mix_port":1789,"verloc_port":1790,"http_api_port":8000,"sphinx_key":"CfZSy1jRfrfiVi9JYexjFWPqWkKoY72t7NdpWaq37K8Z","identity_key":"DhmUYedPZvhP9MMwXdNpPaqCxxTQgjAg78s2nqtTTiNF","version":"1.1.14"},"cost_params":{"profit_margin_percent":"0.1","interval_operating_cost":{"denom":"unym","amount":"40000000"}}}}} -``` -~~~ - -* Copy the resulting signature: - -``` ->>> The base58-encoded signature is: -2GbKcZVKFdpi3sR9xoJWzwPuGdj3bvd7yDtDYVoKfbTWdpjqAeU8KS5bSftD5giVLJC3gZiCg2kmEjNG5jkdjKUt -``` - -* And paste it into the wallet nodal, then confirm the transaction. - -![Paste Signature](../images/wallet-sign.png) - -* Your node will now be bonded and ready to mix at the beginning of the next epoch (at most 1 hour). - -> You are asked to `sign` a transaction on bonding so that the mixnet smart contract is able to map your nym address to your node. This allows us to create a nonce for each account and defend against replay attacks. - -#### Bond via the CLI (power users) -If you want to bond your mix node via the CLI, then check out the [relevant section in the Nym CLI](../tools/nym-cli.md#bond-a-mix-node) docs. - -### Running your mix node - -Now you've bonded your mix node, run it with: - -``` -./nym-mixnode run --id winston-smithnode -``` - -If everything worked, you'll see your node running on the either the [Sandbox testnet network explorer](https://sandbox-explorer.nymtech.net) or the [mainnet network explorer](https://explorer.nymtech.net), depending on which network you're running. - -Note that your node's public identity key is displayed during startup, you can use it to identify your node in the list. - -Keep reading to find out more about configuration options or troubleshooting if you're having issues. There are also some tips for running on AWS and other cloud providers, some of which require minor additional setup. - -Also have a look at the saved configuration files in `$HOME/.nym/mixnodes/` to see more configuration options. - -### Describe your mix node (optional) - -In order to easily identify your node via human-readable information later on in the development of the testnet when delegated staking is implemented, you can `describe` your mixnode with the following command: - -``` -./nym-mixnode describe --id winston-smithnode -``` - -> Remember to restart your mix node process in order for the new description to be propogated. - -### Upgrading your mix node - -Upgrading your node is a two-step process: -* Updating the binary and `config.toml` on your VPS -* Updating the node information in the [mixnet smart contract](../nyx/mixnet-contract.md). **This is the information that is present on the [mixnet explorer](https://explorer.nymtech.net)**. - -#### Step 1: upgrading your binary -Follow these steps to upgrade your mix node binary and update its config file: -* pause your mix node process. -* replace the existing binary with the newest binary (which you can either compile yourself or grab from our [releases page](https://github.com/nymtech/nym/releases)). -* re-run `init` with the same values as you used initially. **This will just update the config file, it will not overwrite existing keys**. -* restart your mix node process with the new binary. - -> Do **not** use the `upgrade` command: there is a known error with the command that will be fixed in a subsequent release. - -#### Step 2: updating your node information in the smart contract -Follow these steps to update the information about your mix node which is publically avaliable from the [Nym API](https://validator.nymtech.net/api/swagger/index.html) and information displayed on the [mixnet explorer](https://explorer.nymtech.net). - -You can either do this graphically via the Desktop Wallet, or the CLI. - -#### Updating node information via the Desktop Wallet -* Navigate to the `Bonding` page and click the `Node Settings` link in the top right corner: -![Bonding page](../images/wallet-screenshots/bonding.png) - -* Update the fields in the `Node Settings` page and click `Submit changes to the blockchain`. -![Node Settings Page](../images/wallet-screenshots/node_settings.png) - -#### Updating node information via the CLI -If you want to bond your mix node via the CLI, then check out the [relevant section in the Nym CLI](../tools/nym-cli.md#upgrade-a-mix-node) docs. - -### Displaying mix node information - -You can always check the details of your mix node with the `node-details` command: - -``` -./nym-mixnode node-details --id winston-smithnode -``` - -## VPS Setup and Automation -### Configure your firewall -The following commands will allow you to set up a firewall using `ufw`. - -``` -# check if you have ufw installed -ufw version -# if it is not installed, install with -sudo apt install ufw -y -# enable ufw -sudo ufw enable -# check the status of the firewall -sudo ufw status -``` - -Finally open your mix node's p2p port, as well as ports for ssh and ports `8000` and `1790` for verloc and measurement pings: - -``` -sudo ufw allow 1789,1790,8000,22/tcp -# check the status of the firewall -sudo ufw status -``` - -For more information about your mix node's port configuration, check the [mix node port reference table](./mix-node-setup.md#mixnode-port-reference) below. - -### Automating your mix node with systemd - -It's useful to have the mix node automatically start at system boot time. Here's a systemd service file to do that: - -```ini -[Unit] -Description=Nym Mixnode ({{mix_node_release_version}}) -StartLimitInterval=350 -StartLimitBurst=10 - -[Service] -User=nym -LimitNOFILE=65536 -ExecStart=/home/nym/nym-mixnode run --id mix0100 -KillSignal=SIGINT -Restart=on-failure -RestartSec=30 - -[Install] -WantedBy=multi-user.target -``` - -Put the above file onto your system at `/etc/systemd/system/nym-mixnode.service`. - -Change the path in `ExecStart` to point at your mix node binary (`nym-mixnode`), and the `User` so it is the user you are running as. - -If you have built nym on your server, and your username is `jetpanther`, then the start command might look like this: - -`ExecStart=/home/jetpanther/nym/target/release/nym-mixnode run --id your-id`. Basically, you want the full `/path/to/nym-mixnode run --id whatever-your-node-id-is` - -Then run: - -``` -systemctl enable nym-mixnode.service -``` - -Start your node: - -``` -service nym-mixnode start -``` - -This will cause your node to start at system boot time. If you restart your machine, the node will come back up automatically. - -You can also do `service nym-mixnode stop` or `service nym-mixnode restart`. - -Note: if you make any changes to your systemd script after you've enabled it, you will need to run: - -``` -systemctl daemon-reload -``` - -This lets your operating system know it's ok to reload the service configuration. - -#### Setting the ulimit - -Linux machines limit how many open files a user is allowed to have. This is called a `ulimit`. - -`ulimit` is 1024 by default on most systems. It needs to be set higher, because mix nodes make and receive a lot of connections to other nodes. - -If you see errors such as: - -``` -Failed to accept incoming connection - Os { code: 24, kind: Other, message: "Too many open files" } -``` - -This means that the operating system is preventing network connections from being made. - -##### Set the ulimit via `systemd` service file - -Query the `ulimit` of your mix node with: - -``` -grep -i "open files" /proc/$(ps -A -o pid,cmd|grep nym-mixnode | grep -v grep |head -n 1 | awk '{print $1}')/limits -``` - -You'll get back the hard and soft limits, which looks something like this: - -``` -Max open files 65536 65536 files -``` - -If your output is **the same as above**, your node will not encounter any `ulimit` related issues. - -However if either value is `1024`, you must raise the limit via the systemd service file. Add the line: - -``` -LimitNOFILE=65536 -``` - -Reload the daemon: - -``` -systemctl daemon-reload -``` - -or execute this as root for system-wide setting of `ulimit`: - -``` -echo "DefaultLimitNOFILE=65535" >> /etc/systemd/system.conf -``` - -Reboot your machine and restart your node. When it comes back, use `cat /proc/$(pidof nym-mixnode)/limits | grep "Max open files"` to make sure the limit has changed to 65535. - -##### Set the ulimit on `non-systemd` based distributions - -Edit `etc/security/conf` and add the following lines: - -``` -# Example hard limit for max opened files -username hard nofile 4096 -# Example soft limit for max opened files -username soft nofile 4096 -``` - -Then reboot your server and restart your mixnode. - -## Node Description -Node description is a short text that describes your node. It is displayed in the `nym-mixnode list` command and in the `nym-mixnode node-details` command. it also shows up in the node explorer to let people know what your node is about and link to your website. - -To set your node description, create a file called `description.toml` and put it in the same directory as your `config.toml` file. The file should look like this: - -```toml -name = "Winston Smith" -description = "I am the Sphinx" -link = "https://nymtech.net" -location = "Giza, Egypt" -``` - -you will need to restart your node for the changes to take effect. - -## Node Families - -Node family involves setting up a group of mix nodes that work together to provide greater privacy and security for network communications. This is achieved by having the nodes in the family share information and routes, creating a decentralized network that makes it difficult for third parties to monitor or track communication traffic. - -### Create a Node Family - -To create a Node family, you will need to install and configure multiple mix nodes, and then use the CLI to link them together into a family. Once your Node family is up and running, you can use it to route your network traffic through a series of nodes, obscuring the original source and destination of the communication. - -You can use either `nym-cli` which can be downloaded from the [release page](https://github.com/nymtech/nym/releases) or compiling `nyxd`. - - -`/path/to/the/release` and run the following on the family head to obtain the signature for the member: - -``` -./nym-mixnode sign --id winston-smithnode --text -``` - -~~~admonish example collapsible=true title="Console output" -``` - -``` -~~~ - -Using `nym-cli`: - -> `--mnemonic` is the mnemonic of the member wanting to be the head of family. - -``` -/nym-cli cosmwasm execute '{"create_family": {"signature": "","family_head": "","owner_signature":"","label": ""}}' --mnemonic -``` - -Using `nyxd`: - -> `--from` is mnemonic of the member wanting to join the family. - -``` -./nyxd tx wasm execute ${MIXNET-CONTRACT} '{"join_family": {"signature": "","family_head": ""}}' --node ${VALIDATOR-ENDPOINT} --from mix1 --chain-id nyx --gas-prices 0.025unym --gas auto --gas-adjustment 1.3 -y -b block -``` - -To get the node owner signature, use: - -`./nym-mixnode node-details --id ` - -### Joining a Node Family - -`/path/to/the/release` and run the following on the family head to obtain the signature for the member: - -``` -./nym-mixnode sign --id mixnode --text -``` - -~~~admonish example collapsible=true title="Console output" -``` - -``` -~~~ - -Using `nym-cli`: - -``` -./nym-cli cosmwasm execute '{"join_family": {"signature": "","family_head": "","owner_signautre": "", "label":""}}' --mnemonic -``` - -Using `nyxd`: - -``` -./nyxd tx wasm execute ${MIXNET-CONTRACT} '{"join_family": {"signature": "","family_head": ""}}' --node ${VALIDATOR-ENDPOINT} --from mix1 --chain-id nyx --gas-prices 0.025unym --gas auto --gas-adjustment 1.3 -y -b block -``` - - -To get the node owner signature, use: - -`./nym-mixnode node-details --id ` - - -### Leaving a family -If wanting to leave, run the same initial command as above, followed by: - -Using `nym-cli`: - -``` -./nym-cli cosmwasm execute n17srjznxl9dvzdkpwpw24gg668wc73val88a6m5ajg6ankwvz9wtst0cznr '{"leave_family": {"signature": "","family_head": "","owner_signautre": ""}}' --mnemonic -``` - -Using `nyxd`: - -``` -./nyxd tx wasm execute ${MIXNET-CONTRACT} '{"join_family": {"signature": "","family_head": ""}}' --node ${VALIDATOR-ENDPOINT} --from mix1 --chain-id nyx --gas-prices 0.025unym --gas auto --gas-adjustment 1.3 -y -b block -``` - -## Checking that your node is mixing correctly -### Network explorers -Once you've started your mix node and it connects to the validator, your node will automatically show up in the 'Mix nodes' section of either the Nym Network Explorers: - -- [Mainnet](https://explorer.nymtech.net/overview) -- [Sandbox testnet](https://sandbox-explorer.nymtech.net/) - -Enter your **identity key** to find your node. There are numerous statistics about your node on that page that are useful for checking your uptime history, packets mixed, and any delegations your node may have. - -There are also 2 community explorers which have been created by [Nodes Guru](https://nodes.guru): - -- [Mainnet](https://mixnet.explorers.guru/) -- [Sandbox testnet](https://sandbox.mixnet.explorers.guru/) - -For more details see [Troubleshooting FAQ](../nodes/troubleshooting.md) - -### Virtual IPs and hosting via Google & AWS -On some services (AWS, Google, etc), the machine's available bind address is not the same as the public IP address. In this case, bind `--host` to the local machine address returned by `ifconfig`, but also specify `--announce-host` with the public IP. Please make sure that you pass the correct, routable `--announce-host`. - -For example, on a Google machine, you may see the following output from the `ifconfig` command: - -``` -ens4: flags=4163 mtu 1460 - inet 10.126.5.7 netmask 255.255.255.255 broadcast 0.0.0.0 - ... -``` - -The `ens4` interface has the IP `10.126.5.7`. But this isn't the public IP of the machine, it's the IP of the machine on Google's internal network. Google uses virtual routing, so the public IP of this machine is something else, maybe `36.68.243.18`. - -`nym-mixnode init --host 10.126.5.7`, initalises the mix node, but no packets will be routed because `10.126.5.7` is not on the public internet. - -Trying `nym-mixnode init --host 36.68.243.18`, you'll get back a startup error saying `AddrNotAvailable`. This is because the mix node doesn't know how to bind to a host that's not in the output of `ifconfig`. - -The right thing to do in this situation is `nym-mixnode init --host 10.126.5.7 --announce-host 36.68.243.18`. - -This will bind the mix node to the available host `10.126.5.7`, but announce the mix node's public IP to the directory server as `36.68.243.18`. It's up to you as a node operator to ensure that your public and private IPs match up properly. - -## Metrics / API endpoints -The mix node binary exposes several API endpoints that can be pinged in order to gather information about the node, and the Nym API (previously 'Validator API') exposes numerous mix node related endpoints which provide network-wide information about mix nodes, the network topology (the list of avaliable mix nodes for packet routing), and information regarding uptime monitoring and rewarding history. - -### Mix node API endpoints -Since the mix node binary exposes several API endpoints itself, you can ping these easily via curl: - -| Endpoint | Description | Command | -| -------------------- | ------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------- | -| `/description` | Returns the description of the node set with the `describe` command | `curl :8000/description` | -| `/hardware` | Returns the hardware information of the node | `curl :8000/hardware` | -| `/verloc` | Returns the verloc information of the node, updated every 12 hours | `curl :8000/verloc` | - -The code for exposed API endpoints can be found [here](https://github.com/nymtech/nym/tree/release/{{platform_release_version}}/mixnode/src/node/http). - -> You can get more detailed info by appending `?debug` to the URL, like so: `curl http://:8000/stats?debug` - -### Mix node related Nym API (previously 'Validator API') endpoints -Numerous endpoints are documented on the Nym API (previously 'Validator API')'s [Swagger Documentation](https://validator.nymtech.net/api/swagger/index.html). There you can also try out various requests from your broswer, and download the response from the API. Swagger will also show you what commands it is running, so that you can run these from an app or from your CLI if you prefer. - -#### Mix node Reward Estimation API endpoint - -The Reward Estimation API endpoint allows mix node operators to estimate the rewards they could earn for running a Nym mixnode with a specific `mix_id`. - -> The `{mix_id}` can be found in the "Mix ID" column of the [Network Explorer](https://explorer.nymtech.net/network-components/mixnodes/active). - -The endpoint is a particularly common for mix node operators as it can provide an estimate of potential earnings based on factors such as the amount of traffic routed through the mixnode, the quality of the mix node's performance, and the overall demand for mix nodes in the network. This information can be useful for mix node operators in deciding whether or not to run a mix node and in optimizing its operations for maximum profitability. - -Using this API endpoint returns information about the Reward Estimation: - -``` -/status/mixnode/{mix_id}/reward-estimation -``` - -Query Response: - -``` - "estimation": { - "total_node_reward": "942035.916721770541325331", - "operator": "161666.263307386408152071", - "delegates": "780369.65341438413317326", - "operating_cost": "54444.444444444444444443" - }, -``` - -> The unit of value is measured in `uNYM`. - -- `estimated_total_node_reward` - An estimate of the total amount of rewards that a particular mix node can expect to receive during the current epoch. This value is calculated by the Nym Validator based on a number of factors, including the current state of the network, the number of mix nodes currently active in the network, and the amount of network traffic being processed by the mix node. - -- `estimated_operator_reward` - An estimate of the amount of rewards that a particular mix node operator can expect to receive. This value is calculated by the Nym Validator based on a number of factors, including the amount of traffic being processed by the mix node, the quality of service provided by the mix node, and the operator's stake in the network. - -- `estimated_delegators_reward` - An estimate of the amount of rewards that mix node delegators can expect to receive individually. This value is calculated by the Nym Validator based on a number of factors, including the amount of traffic being processed by the mix node, the quality of service provided by the mix node, and the delegator's stake in the network. - -- `estimated_node_profit` - An estimate of the profit that a particular mix node operator can expect to earn. This value is calculated by subtracting the mix node operator's `operating_costs` from their `estimated_operator_reward` for the current epoch. - -- `estimated_operator_cost` - An estimate of the total cost that a particular mix node operator can expect to incur for their participation. This value is calculated by the Nym Validator based on a number of factors, including the cost of running a mix node, such as server hosting fees, and other expenses associated with operating the mix node. - -## Ports -All mix node-specific port configuration can be found in `$HOME/.nym/mixnodes//config/config.toml`. If you do edit any port configs, remember to restart your mix node. - -### Mix node port reference -| Default port | Use | -| ------------ | ------------------------- | -| `1789` | Listen for mixnet traffic | -| `1790` | Listen for VerLoc traffic | -| `8000` | Metrics http API endpoint | diff --git a/documentation/docs/src/nodes/mixnode.md b/documentation/docs/src/nodes/mixnode.md new file mode 100644 index 0000000000..159251ad3e --- /dev/null +++ b/documentation/docs/src/nodes/mixnode.md @@ -0,0 +1,17 @@ +# Mix Node + +> The mix node setup and maintenance guide has migrated to the [Operator Guides book](TO_DO). + +Mix nodes are the backbone of the mixnet. These are the nodes that perform 'mix mining', otherwise known simply as 'mixing' or performing the 'poisson mix'. + +Mix nodes perform one task: receiving packets, decrypting their outer 'layer', and holding them for a variable amount of time before passing them to their next destination - either another mix node, or a gateway. In doing so, they 'mix' packets by sending them to their next destination in a different order than they were recieved. + +The aim of this mixing is to protect against timing-based deanonymisation attempts by a global adversary able to monitor the entire network with a 'God's Eye View'. + +## (Coming soon) Mixing: a Step-by-Step Breakdown + +## Further reading +TODO +*
+* +* diff --git a/documentation/docs/src/nodes/network-requester-setup.md b/documentation/docs/src/nodes/network-requester-setup.md deleted file mode 100644 index 6ee560b5b4..0000000000 --- a/documentation/docs/src/nodes/network-requester-setup.md +++ /dev/null @@ -1,255 +0,0 @@ -# Network Requesters - -> The Nym network requester was built in the [building nym](../binaries/building-nym.md) section. If you haven't yet built Nym and want to run the code on this page, go there first. - - -## Current version -``` - -``` - - -## Network Requester Whitelist -If you have access to a server, you can run the network requester, which allows Nym users to send outbound requests from their local machine through the mixnet to a server, which then makes the request on their behalf, shielding them (and their metadata) from clearnet, untrusted and unknown infrastructure, such as email or message client servers. - -By default the network requester is **not** an open proxy (although it can be used as one). It uses a file called `allowed.list` (located in `~/.nym/service-providers/network-requester//`) as a whitelist for outbound requests. - -Any request to a URL which is not on this list will be blocked. - -On startup, if this file is not present, the requester will grab the default whitelist from [here](https://nymtech.net/.wellknown/network-requester/standard-allowed-list.txt) automatically. - -This default whitelist is useful for knowing that the majority of network requesters are able to support certain apps 'out of the box'. - -**Operators of a network requester are of course free to edit this file and add the URLs of services they wish to support to it!** You can find instructions below on adding your own URLs or IPs to this list. - -The domains and IPs on the default whitelist can be broken down by application as follows: - -``` -# Keybase -keybaseapi.com -s3.amazonaws.com -amazonaws.com -twitter.com -keybase.io -gist.githubusercontent.com - -# Used to for uptime healthcheck (see the section on testing your requester below for more) -nymtech.net - -# Blockstream Green Bitcoin Wallet -blockstream.info -blockstream.com -greenaddress.it - -# Electrum Bitcoin Wallet -electrum.org - -# Helios Ethereum Client -alchemy.com -lightclientdata.org -p2pify.com - -# Telegram - these IPs have been copied from https://core.telegram.org/resources/cidr.txt as Telegram does -# not seem to route by domain as the other apps on this list do -91.108.56.0/22 -91.108.4.0/22 -91.108.8.0/22 -91.108.16.0/22 -91.108.12.0/22 -149.154.160.0/20 -91.105.192.0/23 -91.108.20.0/22 -185.76.151.0/24 -2001:b28:f23d::/48 -2001:b28:f23f::/48 -2001:67c:4e8::/48 -2001:b28:f23c::/48 -2a0a:f280::/32 -``` - -## Network Requester Directory -You can find a list of Network Requesters running the default whitelist [here](https://explorer.nymtech.net/network-components/service-providers). This list comprises of the NRs running as infrastructure for NymConnect. - -> We are currently working on a smart-contract based solution more in line with how Mix nodes and Gateways announce themselves to the network. - -## Viewing command help - -The `./nym-network-requester --help ` command can be used to show a list of available parameters. - -~~~admonish example collapsible=true title="Console output" -``` - -``` -~~~ - -You can check the required parameters for available commands by running: - -``` -./nym-network-requester --help -``` - -## Initializing and running your network requester - -The network-requester needs to be initialized before it can be run. This is required for the embedded nym-client to connect successfully to the mixnet. We want to specify an `id` using the `--id` command and give it a value of your choosing. The following command will achieve that: - -``` - ./nym-network-requester init --id example -``` - -~~~admonish example collapsible=true title="Console output" -``` - -``` -~~~ - - -Now that we have initialized our network-requester, we can start it with the following command: - -``` - ./nym-network-requester run --id example -``` - -## Upgrading your network requester -You can upgrade your network requester by following these steps: - -* stop your network requester service -* replace the old binary with the new binary -* restart your service using the commands in the previous section of the document - -### Upgrading to >= v1.1.10 from -KillSignal=SIGINT -Restart=on-failure -RestartSec=30 - -[Install] -WantedBy=multi-user.target -``` - -Now enable and start your requester: - -``` -systemctl enable nym-network-requester.service -systemctl start nym-network-requester.service - -# you can always check your requester has succesfully started with: -systemctl status nym-network-requester.service -``` - -## VPS Setup -### Configure your firewall -Although your requester is now ready to receive traffic, your server may not be - the following commands will allow you to set up a properly configured firewall using `ufw`: - -``` -# check if you have ufw installed -ufw version -# if it is not installed, install with -sudo apt install ufw -y -# enable ufw -sudo ufw enable -# check the status of the firewall -sudo ufw status -``` - -Finally open your requester's p2p port, as well as ports for ssh and incoming traffic connections: - -``` -sudo ufw allow 22,9000/tcp -# check the status of the firewall -sudo ufw status -``` - -For more information about your requester's port configuration, check the [requester port reference table](./network-requester-setup.md#requester-port-reference) below. - -## Using your network requester - -The next thing to do is use your requester, share its address with friends (or whoever you want to help privacy-enhance their app traffic). Is this safe to do? If it was an open proxy, this would be unsafe, because any Nym user could make network requests to any system on the internet. - -To make things a bit less stressful for administrators, the Network Requester drops all incoming requests by default. In order for it to make requests, you need to add specific domains to the `allowed.list` file at `$HOME/.nym/service-providers/network-requester/allowed.list`. - -### Supporting custom domains with your network requester -It is easy to add new domains and services to your network requester - simply find out which endpoints (both URLs and raw IP addresses are supported) you need to whitelist, and then add these endpoints to your `allowed.list`. - -How to go about this? Have a look in your nym-network-requester config directory: - -``` -ls $HOME/.nym/service-providers/network-requester/ - -# returns: allowed.list unknown.list -``` - -We already know that `allowed.list` is what lets requests go through. All unknown requests are logged to `unknown.list`. If you want to try using a new client type, just start the new application, point it at your local [socks client](../clients/socks5-client.md) (configured to use your remote `nym-network-requester`), and keep copying URLs from `unknown.list` into `allowed.list` (it may take multiple tries until you get all of them, depending on the complexity of the application). Make sure to restart your network requester! - -> If you are adding custom domains, please note that whilst they may appear in the logs of your network-requester as something like `api-0.core.keybaseapi.com:443`, you **only need** to include the main domain name, in this instance `keybaseapi.com` - -### Running an open proxy -If you *really* want to run an open proxy, perhaps for testing purposes for your own use or among a small group of trusted friends, it is possible to do so. You can disable network checks by passing the flag `--open-proxy` flag when you run it. If you run in this configuration, you do so at your own risk. - -## Testing your network requester -1. Add `nymtech.net` to your `allowed.list` (remember to restart your network requester). - -2. Ensure that your network-requester is initialized and running. - -3. In another terminal window, run the following: - -``` -curl -x socks5h://localhost:1080 https://nymtech.net/.wellknown/connect/healthcheck.json -``` - -This command should return the following: - -``` -{ "status": "ok" } -``` - - -## Ports -### Requester port reference - -All network-requester-specific port configuration can be found in `$HOME/.nym/service-providers/network-requester//config/config.toml`. If you do edit any port configs, remember to restart your client and requester processes. - -| Default port | Use | -|--------------|---------------------------| -| 9000 | Listen for Client traffic | diff --git a/documentation/docs/src/nodes/network-requester.md b/documentation/docs/src/nodes/network-requester.md new file mode 100644 index 0000000000..797dfd7795 --- /dev/null +++ b/documentation/docs/src/nodes/network-requester.md @@ -0,0 +1 @@ +# Network Requester diff --git a/documentation/docs/src/nodes/overview.md b/documentation/docs/src/nodes/overview.md new file mode 100644 index 0000000000..497a347365 --- /dev/null +++ b/documentation/docs/src/nodes/overview.md @@ -0,0 +1,9 @@ +# Node Types + +> This section was previously the node setup guides. These have been migrated to their own [**Operator Guides**](https://nymtech.net/operators) book. +> +> For setup and maintenance guides, go to the Operators book linked above. +> +> This section is a little spartan for the moment, but we will be adding detailed information about how exactly each node functions, as well as references to any literature and technical specs in the near future. + +This section contains information on the different node types of the mixnet and blockchain. \ No newline at end of file diff --git a/documentation/docs/src/nodes/setup-guides.md b/documentation/docs/src/nodes/setup-guides.md deleted file mode 100644 index 02a23ebff6..0000000000 --- a/documentation/docs/src/nodes/setup-guides.md +++ /dev/null @@ -1,8 +0,0 @@ -# Node Setup Guides - -This section contains setup guides for the following node types: -* [Mix node](./mix-node-setup.md) -* [Gateway](./gateway-setup.md) -* [Network Requester](./network-requester-setup.md) -* [Validator](./validator-setup.md) - diff --git a/documentation/docs/src/nodes/troubleshooting.md b/documentation/docs/src/nodes/troubleshooting.md deleted file mode 100644 index e832edbc39..0000000000 --- a/documentation/docs/src/nodes/troubleshooting.md +++ /dev/null @@ -1,368 +0,0 @@ -# Troubleshooting - - -## Binary Build Problems - -### I am trying to build from the GitHub archive files and the build fails - -GitHub automatically includes .zip and tar.gz files of the Nym repository in its release. You cannot extract these and build - you'll see something like this: - -``` - process didn't exit successfully: `/build/nym/src/nym-0.12.1/target/release/build/nym-socks5-client-c1d0f76a8c7d7e9a/build-script-build` (exit status: 101) - --- stderr - thread 'main' panicked at 'failed to extract build metadata: could not find repository from '/build/nym/src/nym-0.12.1/clients/socks5'; class=Repository (6); code=NotFound (-3)', clients/socks5/build.rs:7:31 - note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace -warning: build failed, waiting for other jobs to finish... -error: build failed -``` - -Why does this happen? - -We have scripts which automatically include the Git commit hash and Git tag in the binary for easier debugging later. If you download a .zip and try building from that, it's not a Git repository and build will fail as above. - -## General Node Config - -### Where can I find my private and public keys and config? - -All config and keys files are stored in a directory named after your `id` which you chose during the `init` process, and can be found at the following PATH: `$HOME/.nym//` where `$HOME` is a home directory of the user (your current user in this case) that launched the node or client. - -The directory structure for each node will be roughly as follows: - -``` -bob@nym:~$ tree /home/nym/.nym/mixnodes/ -/home/nym/.nym/mixnodes/ -|-- nym010 -| |-- config -| | `-- config.toml -| `-- data -| |-- private_identity.pem -| |-- private_sphinx.pem -| |-- public_identity.pem -| `-- public_sphinx.pem -``` - -> If you `cat` the `public_sphinx.pem key`, the output will be different from the public key you will see on Nym [dashboard](https://sandbox-explorer.nymtech.net/). The reason for this is that `.pem` files are encoded in **base64**, however on the web they are in **base58**. Don't be confused if your keys look different. They are the same keys, just with different encoding :) - - -## Mix Nodes - -### How can I tell my node is up and running and mixing traffic? - -First of all check the 'Mixnodes' section of either the Nym Network Explorers: -* [Mainnet](https://explorer.nymtech.net/overview) -* [Sandbox testnet](https://sandbox-explorer.nymtech.net/) - -Enter your **identity key** to find your node. Check the contents of the 'mixnode stats' and 'uptime story' sections. - -There are 2 community explorers currently, which have been created by [Nodes Guru](https://nodes.guru): -* [Mainnet](https://mixnet.explorers.guru/) -* [Sandbox testnet](https://sandbox.mixnet.explorers.guru/) - -If you want more information, or if your node isn't showing up on the explorer of your choice and you want to double-check, here are some examples on how to check if the node is configured properly. - -#### Check from your VPS - -Additional details can be obtained via various methods after you connect to your VPS: - -##### Socket statistics with `ss` - -``` -sudo ss -s -t | grep 1789 # if you have specified a different port in your mixnode config, change accordingly -``` - -This command should return a lot of data containing `ESTAB`. This command should work on every unix based system. - -##### List open files and reliant processes with `lsof` - -``` -# check if lsof is installed: -lsof -v -# install if not installed -sudo apt install lsof -# run against mixnode port -sudo lsof -i TCP:1789 # if you have specified a different port in your mixnode config, change accordingly -``` - -This command should return something like this: - -``` -nym-mixno 103349 root 53u IPv6 1333229972 0t0 TCP [2a03:b0c0:3:d0::ff3:f001]:57844->[2a01:4f9:c011:38ae::5]:1789 (ESTABLISHED) -nym-mixno 103349 root 54u IPv4 1333229973 0t0 TCP nym:57104->194.5.78.73:1789 (ESTABLISHED) -nym-mixno 103349 root 55u IPv4 1333229974 0t0 TCP nym:48130->static.236.109.119.168.clients.your-server.de:1789 (ESTABLISHED) -nym-mixno 103349 root 56u IPv4 1333229975 0t0 TCP nym:52548->vmi572614.contaboserver.net:1789 (ESTABLISHED) -nym-mixno 103349 root 57u IPv6 1333229976 0t0 TCP [2a03:b0c0:3:d0::ff3:f001]:43244->[2600:1f18:1031:2401:c04b:2f25:ca79:fef3]:1789 (ESTABLISHED) -``` - -##### Query `systemd` journal with `journalctl` - -``` -sudo journalctl -u nym-mixnode -o cat | grep "Since startup mixed" -``` - -If you have created `nym-mixnode.service` file (i.e. you are running your mixnode via `systemd`) then this command shows you how many packets have you mixed so far, and should return a list of messages like this: - -``` -2021-05-18T12:35:24.057Z INFO nym_mixnode::node::metrics > Since startup mixed 233639 packets! -2021-05-18T12:38:02.178Z INFO nym_mixnode::node::metrics > Since startup mixed 233739 packets! -2021-05-18T12:40:32.344Z INFO nym_mixnode::node::metrics > Since startup mixed 233837 packets! -2021-05-18T12:46:08.549Z INFO nym_mixnode::node::metrics > Since startup mixed 234081 packets! -2021-05-18T12:56:57.129Z INFO nym_mixnode::node::metrics > Since startup mixed 234491 packets! -``` - -You can add ` | tail` to the end of the command to watch for new entries in real time if needed. - -#### Check from your local machine - -##### Scan ports with `nmap`: - -``` -nmap -p 1789 -Pn -``` - -If your mixnode is configured properly it should output something like this: - -``` -bob@desktop:~$ nmap -p 1789 95.296.134.220 -Pn - -Host is up (0.053s latency). - -PORT STATE SERVICE -1789/tcp open hello -``` - -##### Query online nodes: - -``` -curl --location --request GET 'https://validator.nymtech.net/api/v1/mixnodes/' -``` - -Will return a list all nodes currently online. - -You can query gateways by replacing `mixnodes` with `gateways` in the above command, and can query for the mixnodes and gatways on the Sandbox testnet by replacing `validator` with `sandbox-validator`. - - -#### Check with Network API - -We currently have an API set up returning our metrics tests of the network. There are two endpoints to ping for information about your mixnode, `report` and `history`. Find more information about this in the [Mixnodes metrics documentation](../nodes/mix-node-setup.md#metrics--api-endpoints). - -### Why is my node not mixing any packets? - -If you are still unable to see your node on the dashboard, or your node is declaring it has not mixed any packets, there are several potential issues: - -- The firewall on your host machine is not configured properly. -- You provided incorrect information when bonding your node. -- You are running your mixnode from a VPS without IPv6 support. -- You did not use the `--announce-host` flag while running the mixnode from your local machine behind NAT. -- You did not configure your router firewall while running the mixnode from your local machine behind NAT, or you are lacking IPv6 support. -- Your mixnode is not running at all, it has either exited / panicked or you closed the session without making the node persistent. - -```admonish caution -Your mixnode **must speak both IPv4 and IPv6** in order to cooperate with other nodes and route traffic. This is a common reason behind many errors we are seeing among node operators, so check with your provider that your VPS is able to do this! -``` - -#### Incorrectly configured firewall - -The most common reason your mixnode might not be mixing packets is due to a poorly configured firewall. The following commands will allow you to set up a firewall using `ufw`. - -``` -# check if you have ufw installed -ufw version -# if it is not installed, install with -sudo apt install ufw -y -# enable ufw -sudo ufw enable -# check the status of the firewall -sudo ufw status -``` - -Finally open your mixnode's p2p port, as well as ports for ssh, http, and https connections, and ports `8000` and `1790` for verloc and measurement pings: - -``` -sudo ufw allow 1789,1790,8000,22,80,443/tcp -# check the status of the firewall -sudo ufw status -``` - -#### Incorrect bonding information - -Check that you have provided the correct information when bonding your mixnode in the web wallet [interface](https://sandbox-wallet.nymtech.net/). When in doubt, unbond and then rebond your node! - -#### Missing `announce-host` flag - -On certain cloud providers such as AWS and Google Cloud, you need to do some additional configuration of your firewall and use `--host` with your **local ip** and `--announce-host` with the **public ip** of your mixnode host. - -#### No IPv6 connectivity - -Make sure that your VPS has IPv6 connectivity available with whatever provider you are using. - -To get all ip addresses of your host, try following commands: - -``` -hostname -i -``` - -Will return your **local ip** address. - -``` -hostname -I -``` - -Will return all of the ip addresses of your host. This output should look something like this: - -``` -bob@nym:~$ hostname -I -88.36.11.23 172.18.0.1 2a01:28:ca:102::1:641 -``` - -- The first **ipv4** is the public ip you need to use for the `--announce-host` flag. -- The second **ipv4** is the local ip you need to use for the `--host` flag. -- The 3rd output should confirm if your machine has ipv6 available. - -### Running on a local machine behind NAT with no fixed IP address - -Your ISP has to be IPv6 ready if you want to run a mixnode on your local machine. Sadly, in 2020, most of them are not and you won't get an IPv6 address by default from your ISP. Usually it is a extra paid service or they simply don't offer it. - -Before you begin, check if you have IPv6 [here](https://test-ipv6.cz/). If not, then don't waste your time to run a node which won't ever be able to mix any packet due to this limitation. Call your ISP and ask for IPv6, there is a plenty of it for everyone! - -If all goes well and you have IPv6 available, then you will need to `init` the mixnode with an extra flag, `--announce-host`. You will also need to edit your `config.toml` file each time your IPv4 address changes, that could be a few days or a few weeks. - -Additional configuration on your router might also be needed to allow traffic in and out to port 1789 and IPv6 support. - -Here is a sample of the `init` command to create the mixnode config. - -``` -./target/release/nym-mixnode init --id nym-nat --host 0.0.0.0 --announce-host 85.160.12.13 --layer 3 -``` - -- `--host 0.0.0.0` should work everytime even if your local machine IPv4 address changes. For example on Monday your router gives your machine an address `192.168.0.13` and on Wednesday, the DHCP lease will end and you will be asigned `192.168.0.14`. Using `0.0.0.0` should avoid this without having to set any static ip in your router`s configuration. - -- you can get your current IPv4 address by either using `curl ipinfo.io` if you're on MacOS or Linux or visiting [whatsmyip site](https://www.whatsmyip.org/). Simply copy it and use it as `--anounce-host` address. - -Make sure you check if your node is really mixing. You will need a bit of luck to set this up from your home behind NAT. - -### Accidentally killing your node process on exiting session - -When you close your current terminal session, you need to make sure you don't kill the mixnode process! There are multiple ways on how to make it persistent even after exiting your ssh session, the easiest solution is to use `nohup`, and the more elegant solution is to run the node with `systemd`. - -### Running your mixnode as a background process with `nohup` - -`nohup` is a command with which your terminal is told to ignore the `HUP` or 'hangup' signal. This will stop the mixnode process ending if you kill your session. - -``` -nohup ./nym-mixnode run --id NYM # where `--id NYM` is the id you set during the `init` command. -``` - -### Running your mixnode as a background process with `systemd` - -The most reliable and elegant solution is to create a `systemd.service` file and run the nym-mixnode with `systemctl` command. - -Create a file with `nano` at `/etc/systemd/system/nym-mixnode.service` containing the following: - -```ini -[Unit] -Description=nym mixnode service -After=network.target - -[Service] -Type=simple -User=nym # change as appropriate -LimitNOFILE=65536 -ExecStart=/home/nym/nym-mixnode run --id nym # change as appropriate -KillSignal=SIGINT -Restart=on-failure -RestartSec=30 -Restart=on-abort -[Install] -WantedBy=multi-user.target -``` - -``` -# enable the service -sudo systemctl enable nym-mixnode -# start the service -sudo systemctl start nym-mixnode -# check if the service is running properly and mixnode is mixing -sudo systemctl status nym-mixnode -``` - -Now your node should be mixing all the time, and restart if you reboot your server! - -Anytime you change your `systemd` service file you need to `sudo systemctl daemon-reload` in order to restart the service. - -### Network configuration seems fine but log still claims `Since startup mixed 0 packets!` - -This behavior is most likely caused by a mismatch between your node configuration and the bonding information. Unbond and then rebond your node. - -Also make sure to enter all the information in the web wallet exactly as it appears in the log when you start the mixnode process. In particular, the `host` field must contain the _port_ on which your mixnode will listen: - -- correct host: `34.12.3.43:1789` -- incorrect host:`34.12.3.43` - -### Common errors and warnings - -Most of the `ERROR` and `WARN` messages in your node logs are benign - as long as your node outputs `since startup mixed X packets!` in your logs (and this number increases over time), your node is mixing packets. If you want to be sure, check the Nym [dashboard](https://sandbox-explorer.nymtech.net/) or see other ways on how to check if your node is mixing properly as outlined in the section **How can I tell my node is up and running and mixing traffic?** above. - -More specific errors and warnings are covered below. - -#### `tokio runtime worker` error - -If you are running into issues with an error including the following: - -``` -thread 'tokio-runtime-worker' panicked at 'Failed to create TCP listener: Os { code: 99, kind: AddrNotAvailable, message: "Cannot assign requested address" }' -``` - -Then you need to `--announce-host ` and ``--host ` on startup. This issue arises because of your use of a provider like AWS or Google Cloud, and the fact that your VPS' available bind address is not the same as the public IP address (see [Virtual IPs and hosting via Google and AWS](../nodes/mix-node-setup.md#virtual-ips-and-hosting-via-google--aws) for more information on this issue). - -#### `rocket::launch` warnings -These warnings are not an issue, please ignore them. Rocket is a web framework for rust which we are using to provide mixnodes with `/verloc` and `/description` http APIs. - -Find more information about this in the [Mixnodes metrics documentation](../nodes/mix-node-setup.md#metrics--api-endpoints). - -Rocket runs on port `8000` by default. Although at this stage of the testnet we need Rocket to be reachable via this port, in the future customization of the particular port it uses will be possible. - -#### `failed to receive reply to our echo packet within 1.5s. Stopping the test` - -This relates to the VerLoc implementation that appeared in `0.10.1`, which has a particularly high log sensitivity. This warning means that the echo packet sent to the mixnode was received, but not sent back. _This will not affect the rate of rewards or performance metrics of your mixnode in the testnet at this point._ - -#### `Connection to :1789 seems to be dead` - -This warning is normal at the moment, and is _nothing to do with your mixnode!_ It is simply a warning that your node is unable to connect to other peoples' mixnodes for some reason, most likely because they are offline or poorly configured. - -### Can I use a port other than 1789 ? - -Yes! Here is what you will need to do: - -Assuming you would like to use port `1337` for your mixnode, you need to open the new port (and close the old one): - -``` -sudo ufw allow 1337 -sudo ufw deny 1789 -``` - -And then edit the mixnode's config. - -> If you want to change the port for an already running node, you need to stop the process before editing your config file. - -Assuming your node name is `nym`, the config file is located at `~/.nym/mixnodes/nym/config/config.toml`. - -``` -nano ~/.nym/mixnodes/nym/config/config.toml -``` - -You will need to edit two parts of the file. `announce_address` and `listening_address` in the config.toml file. Simply replace `:1789` (the default port) with `:1337` (your new port) after your IP address. - -Finally, restart your node. You should see if the mixnode is using the port you have changed in the config.toml file right after you run the node. - -### What is `verloc` and do I have to configure my mixnode to implement it? - -`verloc` is short for _verifiable location_. Mixnodes and gateways now measure speed-of-light distances to each other, in an attempt to verify how far apart they are. In later releases, this will allow us to algorithmically verify node locations in a non-fakeable and trustworthy manner. - -You don't have to do any additional configuration for your node to implement this, it is a passive process that runs in the background of the mixnet from version `0.10.1` onwards. - - -### Where can I get more help? - -The fastest way to reach one of us or get a help from the community, visit our [Telegram help chat](https://t.me/nymchan_help_chat) or head to our [Discord](https://Discord.gg/nym) - -For more tech heavy questions join our Keybase channel. Get Keybase [here](https://keybase.io/), then click Teams -> Join a team. Type nymtech.friends into the team name and hit continue. For general chat, hang out in the #general channel. diff --git a/documentation/docs/src/nodes/validator-setup.md b/documentation/docs/src/nodes/validator-setup.md deleted file mode 100644 index 053a19253d..0000000000 --- a/documentation/docs/src/nodes/validator-setup.md +++ /dev/null @@ -1,669 +0,0 @@ -# Validators - -The validator is built using [Cosmos SDK](https://cosmos.network) and [Tendermint](https://tendermint.com), with a [CosmWasm](https://cosmwasm.com) smart contract controlling the directory service, node bonding, and delegated mixnet staking. - -## Building your validator -### Prerequisites -#### `git`, `gcc`, `jq` -* Debian-based systems: -``` -apt install git build-essential jq - -# optional additional manual pages can be installed with: -apt-get install manpages-dev -``` - -* Arch-based systems: -Install `git`, `gcc` and `jq` with the following: -``` -pacman -S git gcc jq -``` - -#### `Go` -`Go` can be installed via the following commands (taken from the [Agoric SDK docs](https://github.com/Agoric/agoric-sdk/wiki/Validator-Guide-for-Incentivized-Testnet#install-go)): - -``` -# First remove any existing old Go installation -sudo rm -rf /usr/local/go - -# Install correct Go version -curl https://dl.google.com/go/go1.20.4.linux-amd64.tar.gz | sudo tar -C/usr/local -zxvf - - -# Update environment variables to include go -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 -``` - -Verify `Go` is installed with: - -``` -go version -# Should return something like: -go version go1.20.4 linux/amd64 -``` - -### Download a precompiled validator binary -You can find pre-compiled binaries for Ubuntu `22.04` and `20.04` [here](https://github.com/nymtech/nyxd/releases). - -```admonish caution title="" -There are seperate releases for Mainnet and the Sandbox testnet - make sure to download the correct binary to avoid `bech32Prefix` mismatches. -``` - -### Manually compiling your validator binary -The codebase for the Nyx validators can be found [here](https://github.com/nymtech/nyxd). - -The validator binary can be compiled by running the following commands: -``` -git clone https://github.com/nymtech/nyxd.git -cd nyxd -git checkout release/ - -# Mainnet -make build - -# Sandbox testnet -BECH32_PREFIX=nymt make build -``` - -At this point, you will have a copy of the `nyxd` binary in your `build/` directory. Test that it's compiled properly by running: - -``` -./build/nyxd -``` - -You should see help text print out. - -### Linking `nyxd` to `libwasmvm.so` - -`libwasmvm.so` is the wasm virtual machine which is needed to execute smart contracts in `v0.26.1`. This file is renamed in `libwasmvm.x86_64.so` in `v0.31.1`. - -If you downloaded your `nyxd` binary from Github, you will have seen this file when un-`tar`-ing the `.tar.gz` file from the releases page. - -If you are seeing an error concerning this file when trying to run `nyxd`, then you need to move the `libwasmvm.so` file to correct location. - -Simply `cp` or `mv` that file to `/lib/x86_64-linux-gnu/` and re-run `nyxd`. - -### Adding `nyxd` to your `$PATH` -You'll need to set `LD_LIBRARY_PATH` in your user's `~/.bashrc` file, and add that to our path. Replace `/home/youruser/path/to/nym/binaries` in the command below to the locations of `nyxd` and `libwasmvm.so` and run it. If you have compiled these on the server, they will be in the `build/` folder: - -``` -NYX_BINARIES=/home/youruser/path/to/validator/binary - -# if you are using another shell like zsh replace '.bashrc' with the relevant config file -echo 'export LD_LIBRARY_PATH=${LD_LIBRARY_PATH}:'NYX_BINARIES >> ~/.bashrc -echo 'export PATH=$PATH:'${NYX_BINARIES} >> ~/.bashrc -source ~/.bashrc -``` - -Test everything worked: - -``` -nyxd -``` - -This should return the regular help text. - -## Initialising your validator -### Prerequisites: - -- FQDN Domain name -- IPv4 and IPv6 connectivity - -Choose a name for your validator and use it in place of `` in the following command: - -``` -# Mainnet -nyxd init --chain-id=nyx - -# Sandbox testnet -nyxd init --chain-id=sandbox -``` - -```admonish caution title="" -`init` generates `priv_validator_key.json` and `node_key.json`. - -If you have already set up a validator on a network, **make sure to back up the key located at** -`~/.nyxd/config/priv_validator_key.json`. - -If you don't save the validator key, then it can't sign blocks and will be jailed all the time, and -there is no way to deterministically (re)generate this key. -``` - -At this point, you have a new validator, with its own genesis file located at `$HOME/.nyxd/config/genesis.json`. You will need to replace the contents of that file that with either the Nyx Mainnet or Sandbox Testnet genesis file. - -You can use the following command to download them for the correct network: - -``` -# Mainnet -wget -O $HOME/.nyxd/config/genesis.json https://nymtech.net/genesis/genesis.json - -# Sandbox testnet -wget -O $HOME/.nyxd/config/genesis.json https://sandbox-validator1.nymtech.net/snapshots/genesis.json -``` - -### `config.toml` configuration - -Edit the following config options in `$HOME/.nyxd/config/config.toml` to match the information below for your network: - -``` -# Mainnet -persistent_peers = "ee03a6777fb76a2efd0106c3769daaa064a3fcb5@51.79.21.187:26656" -create_empty_blocks = false -laddr = "tcp://0.0.0.0:26656" -``` - -``` -# Sandbox testnet -cors_allowed_origins = ["*"] -persistent_peers = "8421c0a3d90d490e27e8061f2abcb1276c8358b6@sandbox-validator1.nymtech.net:26666" -create_empty_blocks = false -laddr = "tcp://0.0.0.0:26656" -``` - -These affect the following: -* `persistent_peers = "@.nymtech.net:26666"` allows your validator to start pulling blocks from other validators. **The main sandbox validator listens on `26666` instead of the default `26656` for debugging**. It is recommended you do not change your port from `26656`. -* `create_empty_blocks = false` will save space -* `laddr = "tcp://0.0.0.0:26656"` is in your p2p configuration options - -Optionally, if you want to enable [Prometheus](https://prometheus.io/) metrics then the following must also match in the `config.toml`: - -- `prometheus = true` -- `prometheus_listen_addr = ":26660"` - -> Remember to enable metrics in the 'Configuring Prometheus metrics' section below as well. - -And if you wish to add a human-readable moniker to your node: - -- `moniker = "yourname"` - -Finally, if you plan on using [Cockpit](https://cockpit-project.org/documentation.html) on your server, change the `grpc` port from `9090` as this is the port used by Cockpit. - -### `app.toml` configuration -In the file `$HOME/nyxd/config/app.toml`, set the following values: - -``` -# Mainnet -minimum-gas-prices = "0.025unym,0.025unyx" -enable = true in the `[api]` section to get the API server running -``` -``` -# Sandbox Testnet -minimum-gas-prices = "0.025unymt,0.025unyxt" -enable = true` in the `[api]` section to get the API server running -``` - -### Setting up your validator's admin user -You'll need an admin account to be in charge of your validator. Set that up with: - -``` -nyxd keys add nyxd-admin -``` - -This will add keys for your administrator account to your system's keychain and log your name, address, public key, and mnemonic. As the instructions say, remember to **write down your mnemonic**. - -You can get the admin account's address with: - -``` -nyxd keys show nyxd-admin -a -``` - -Type in your keychain **password**, not the mnemonic, when asked. - -## Starting your validator - -```admonish caution title="" -If you are running a Sandbox testnet validator, please skip the `validate-genesis` command: it will fail due to the size of the genesis file as this is a fork of an existing chain state. -``` - -Everything should now be ready to go. You've got the validator set up, all changes made in `config.toml` and `app.toml`, the Nym genesis file copied into place (replacing the initial auto-generated one). Now let's validate the whole setup: - -``` -nyxd validate-genesis -``` - -If this check passes, you should receive the following output: - -``` -File at /path/to/genesis.json is a valid genesis file -``` - -> If this test did not pass, check that you have replaced the contents of `/path/to/.nymd/config/genesis.json` with that of the correct genesis file. - -Before starting the validator, we will need to open the firewall ports: - -``` -# if ufw is not already installed: -sudo apt install ufw -sudo ufw enable -sudo ufw allow 1317,26656,26660,22,80,443/tcp -# to check everything worked -sudo ufw status -``` - -Ports `22`, `80`, and `443` are for ssh, http, and https connections respectively. The rest of the ports are documented [here](https://docs.cosmos.network/v0.42/core/grpc_rest.html). - -For more information about your validator's port configuration, check the [validator port reference table](#validator-port-reference) below. - -> If you are planning to use [Cockpit](https://cockpit-project.org/) on your validator server then you will have defined a different `grpc` port in your `config.toml` above: remember to open this port as well. - -Start the validator: - -``` -nyxd start -``` - -Once your validator starts, it will start requesting blocks from other validators. This may take several hours. Once it's up to date, you can issue a request to join the validator set with the command below. - -### Syncing from a snapshot -If you wish to sync from a snapshot on **mainnet** use Polkachu's [mainnet](https://polkachu.com/networks/nym) resources. - -If you wish to sync from a snapshot on **Sandbox testnet** use the below commands, which are a modified version of Polkachu's excellent resources. These commands assume you are running an OS with `apt` as the package manager: - -``` -# install lz4 if necessary -sudo apt install snapd -y -sudo snap install lz4 - -# download the snapshot -wget -O nyxd-sandbox-snapshot-data.tar.lz4 https://sandbox-validator1.nymtech.net/snapshots/nyxd-sandbox-snapshot-data.tar.lz4 - -# reset your validator state -nyxd tendermint unsafe-reset-all - -# unpack the snapshot -lz4 -c -d nyxd-sandbox-snapshot-data.tar.lz4 | tar -x -C $HOME/.nyxd -``` - -You can then restart `nyxd` - it should start syncing from a block > 2000000. - -### Joining Consensus -```admonish caution title="" -When joining consensus, make sure that you do not disrupt (or worse - halt) the network by coming in with a disproportionately large amount of staked tokens. - -Please initially stake a small amount of tokens compared to existing validators, then delegate to yourself in tranches over time. -``` - -Once your validator has synced and you have received tokens, you can join consensus and produce blocks. - -``` -# Mainnet -nyxd tx staking create-validator - --amount=10000000unyx - --fees=0unyx - --pubkey=$(/home/youruser/path/to/nyxd/binaries/nyxd tendermint show-validator) - --moniker="whatever you called your validator" - --chain-id=nyx - --commission-rate="0.10" - --commission-max-rate="0.20" - --commission-max-change-rate="0.01" - --min-self-delegation="1" - --gas="auto" - --gas-adjustment=1.15 - --from="KEYRING_NAME" - --node https://rpc-1.nyx.nodes.guru:443 -``` -``` -# Sandbox Testnet -nyxd tx staking create-validator - --amount=10000000unyxt - --fees=5000unyxt - --pubkey=$(/home/youruser/path/to/nym/binaries/nyxd tendermint show-validator) - --moniker="whatever you called your validator" - --chain-id=sandbox - --commission-rate="0.10" - --commission-max-rate="0.20" - --commission-max-change-rate="0.01" - --min-self-delegation="1" - --gas="auto" - --gas-adjustment=1.15 - --from="KEYRING_NAME" - --node https://sandbox-validator1.nymtech.net:443 -``` - -You'll need either `unyxt` tokens on Sandbox, or `unyx` tokens on mainnet to perform this command. - -> We are currently working towards building up a closed set of reputable validators. You can ask us for coins to get in, but please don't be offended if we say no - validators are part of our system's core security and we are starting out with people we already know or who have a solid reputation. - -If you want to edit some details for your node you will use a command like this: - -``` -# Mainnet -nyxd tx staking edit-validator - --chain-id=nyx - --moniker="whatever you called your validator" - --details="Nyx validator" - --security-contact="your email" - --identity="your identity" - --gas="auto" - --gas-adjustment=1.15 - --from="KEYRING_NAME" - --fees 2000unyx -``` -``` -# Sandbox testnet -nyxd tx staking edit-validator - --chain-id=sandbox - --moniker="whatever you called your validator" - --details="Sandbox testnet validator" - --security-contact="your email" - --identity="your identity" - --gas="auto" - --gas-adjustment=1.15 - --from="KEYRING_NAME" - --fees 2000unyxt -``` - -With above command you can specify the `gpg` key last numbers (as used in `keybase`) as well as validator details and your email for security contact. - -### Automating your validator with systemd -You will most likely want to automate your validator restarting if your server reboots. Below is a systemd unit file to place at `/etc/systemd/system/nymd.service`: - -```ini -[Unit] -Description=Nyxd -StartLimitInterval=350 -StartLimitBurst=10 - -[Service] -User=nyx # change to your user -Type=simple -Environment="LD_LIBRARY_PATH=/home/youruser/path/to/nyx/binaries" # change to correct path -ExecStart=/home/youruser/path/to/nyx/binaries/nymd start # change to correct path -Restart=on-failure -RestartSec=30 -LimitNOFILE=infinity - -[Install] -WantedBy=multi-user.target -``` - -Proceed to start it with: - -``` -systemctl daemon-reload # to pickup the new unit file -systemctl enable nymd # to enable the service -systemctl start nymd # to actually start the service -journalctl -f # to monitor system logs showing the service start -``` - -### Installing and configuring nginx for HTTPS -#### Setup -[Nginx](https://www.nginx.com/resources/glossary/nginx/#:~:text=NGINX%20is%20open%20source%20software,%2C%20media%20streaming%2C%20and%20more.&text=In%20addition%20to%20its%20HTTP,%2C%20TCP%2C%20and%20UDP%20servers.) is an open source software used for operating high-performance web servers. It allows us to set up reverse proxying on our validator server to improve performance and security. - -Install `nginx` and allow the 'Nginx Full' rule in your firewall: - -``` -sudo ufw allow 'Nginx Full' -``` - -Check nginx is running via systemctl: - -``` -systemctl status nginx -``` - -Which should return: - -``` -● nginx.service - A high performance web server and a reverse proxy server - Loaded: loaded (/lib/systemd/system/nginx.service; enabled; vendor preset: enabled) - Active: active (running) since Fri 2018-04-20 16:08:19 UTC; 3 days ago - Docs: man:nginx(8) - Main PID: 2369 (nginx) - Tasks: 2 (limit: 1153) - CGroup: /system.slice/nginx.service - ├─2369 nginx: master process /usr/sbin/nginx -g daemon on; master_process on; - └─2380 nginx: worker process -``` - -#### Configuration - -Proxying your validator's port `26657` to nginx port `80` can then be done by creating a file with the following at `/etc/nginx/conf.d/validator.conf`: - -``` -server { - listen 80; - listen [::]:80; - server_name "domain_name"; - - location / { - proxy_pass http://127.0.0.1:26657; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header Host $host; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - } -} -``` - -Followed by: - -``` -sudo apt install certbot nginx python3 -certbot --nginx -d nym-validator.yourdomain.com -m you@yourdomain.com --agree-tos --noninteractive --redirect -``` - -```admonish caution title="" -If using a VPS running Ubuntu 20: replace `certbot nginx python3` with `python3-certbot-nginx` -``` - -These commands will get you an https encrypted nginx proxy in front of the API. - -### Configuring Prometheus metrics (optional) - -Configure Prometheus with the following commands (adapted from NodesGuru's [Agoric setup guide](https://nodes.guru/agoric/setup-guide/en)): - -``` -echo 'export OTEL_EXPORTER_PROMETHEUS_PORT=9464' >> $HOME/.bashrc -source ~/.bashrc -sed -i '/\[telemetry\]/{:a;n;/enabled/s/false/true/;Ta}' $HOME/.nymd/config/app.toml -sed -i "s/prometheus-retention-time = 0/prometheus-retention-time = 60/g" $HOME/.nymd/config/app.toml -sudo ufw allow 9464 -echo 'Metrics URL: http://'$(curl -s ifconfig.me)':26660/metrics' -``` - -Your validator's metrics will be available to you at the returned 'Metrics URL'. - -~~~admonish example collapsible=true title="Console output" -``` -# HELP go_gc_duration_seconds A summary of the pause duration of garbage collection cycles. -# TYPE go_gc_duration_seconds summary -go_gc_duration_seconds{quantile="0"} 6.7969e-05 -go_gc_duration_seconds{quantile="0.25"} 7.864e-05 -go_gc_duration_seconds{quantile="0.5"} 8.4591e-05 -go_gc_duration_seconds{quantile="0.75"} 0.000115919 -go_gc_duration_seconds{quantile="1"} 0.001137591 -go_gc_duration_seconds_sum 0.356555301 -go_gc_duration_seconds_count 2448 -# HELP go_goroutines Number of goroutines that currently exist. -# TYPE go_goroutines gauge -go_goroutines 668 -# HELP go_info Information about the Go environment. -# TYPE go_info gauge -go_info{version="go1.15.7"} 1 -# HELP go_memstats_alloc_bytes Number of bytes allocated and still in use. -# TYPE go_memstats_alloc_bytes gauge -go_memstats_alloc_bytes 1.62622216e+08 -# HELP go_memstats_alloc_bytes_total Total number of bytes allocated, even if freed. -# TYPE go_memstats_alloc_bytes_total counter -go_memstats_alloc_bytes_total 2.09341707264e+11 -# HELP go_memstats_buck_hash_sys_bytes Number of bytes used by the profiling bucket hash table. -# TYPE go_memstats_buck_hash_sys_bytes gauge -go_memstats_buck_hash_sys_bytes 5.612319e+06 -# HELP go_memstats_frees_total Total number of frees. -# TYPE go_memstats_frees_total counter -go_memstats_frees_total 2.828263344e+09 -# HELP go_memstats_gc_cpu_fraction The fraction of this program's available CPU time used by the GC since the program started. -# TYPE go_memstats_gc_cpu_fraction gauge -go_memstats_gc_cpu_fraction 0.03357798610671518 -# HELP go_memstats_gc_sys_bytes Number of bytes used for garbage collection system metadata. -# TYPE go_memstats_gc_sys_bytes gauge -go_memstats_gc_sys_bytes 1.3884192e+07 -``` -~~~ - -### Setting the ulimit -Linux machines limit how many open files a user is allowed to have. This is called a `ulimit`. - -`ulimit` is 1024 by default on most systems. It needs to be set higher, because validators make and receive a lot of connections to other nodes. - -If you see errors such as: - -``` -Failed to accept incoming connection - Os { code: 24, kind: Other, message: "Too many open files" } -``` - -This means that the operating system is preventing network connections from being made. - -##### Set the ulimit via `systemd` service file -Query the `ulimit` of your validator with: - -``` -grep -i "open files" /proc/$(ps -A -o pid,cmd|grep nymd | grep -v grep |head -n 1 | awk '{print $1}')/limits -``` - -You'll get back the hard and soft limits, which looks something like this: - -``` -Max open files 65536 65536 files -``` - -If your output is **the same as above**, your node will not encounter any `ulimit` related issues. - -However if either value is `1024`, you must raise the limit via the systemd service file. Add the line: - -``` -LimitNOFILE=65536 -``` - -Reload the daemon: - -``` -systemctl daemon-reload -``` - -or execute this as root for system-wide setting of `ulimit`: - -``` -echo "DefaultLimitNOFILE=65535" >> /etc/systemd/system.conf -``` - -Reboot your machine and restart your node. When it comes back, use `cat /proc/$(pidof nym-validator)/limits | grep "Max open files"` to make sure the limit has changed to 65535. - -##### Set the ulimit on `non-systemd` based distributions -Edit `etc/security/conf` and add the following lines: - -``` -# Example hard limit for max opened files -username hard nofile 4096 -# Example soft limit for max opened files -username soft nofile 4096 -``` - -Then reboot your server and restart your validator. - -## Using your validator -### Unjailing your validator -If your validator gets jailed, you can fix it with the following command: - -``` -# Mainnet -nyxd tx slashing unjail - --broadcast-mode=block - --from="KEYRING_NAME" - --chain-id=nyx - --gas=auto - --gas-adjustment=1.4 - --fees=7000unyx -``` -``` -# Sandbox Testnet -nyxd tx slashing unjail - --broadcast-mode=block - --from="KEYRING_NAME" - --chain-id=sandbox - --gas=auto - --gas-adjustment=1.4 - --fees=7000unyxt -``` - -### Upgrading your validator - -Upgrading from `v0.31.1` -> `v0.32.0` process is fairly simple. Grab the `v0.32.0` release tarball from the [`nyxd` releases page](https://github.com/nymtech/nyxd/releases), and untar it. Inside are two files: - -- the new validator (`nyxd`) v0.32.0 -- the new wasmvm (it depends on your platform, but most common filename is `libwasmvm.x86_64.so`) - -Wait for the upgrade height to be reached and the chain to halt awaiting upgrade, then: - -* copy `libwasmvm.x86_64.so` to the default LD_LIBRARY_PATH on your system (on Ubuntu 20.04 this is `/lib/x86_64-linux-gnu/`) replacing your existing file with the same name. -* swap in your new `nyxd` binary and restart. - -You can also use something like [Cosmovisor](https://github.com/cosmos/cosmos-sdk/tree/main/tools/cosmovisor) - grab the relevant information from the current upgrade proposal [here](https://nym.explorers.guru/proposal/9). - -Note: Cosmovisor will swap the `nyxd` binary, but you'll need to already have the `libwasmvm.x86_64.so` in place. - -#### Common reasons for your validator being jailed - -The most common reason for your validator being jailed is that your validator is out of memory because of bloated syslogs. - -Running the command `df -H` will return the size of the various partitions of your VPS. - -If the `/dev/sda` partition is almost full, try pruning some of the `.gz` syslog archives and restart your validator process. - -### Day 2 operations with your validator - -You can check your current balances with: - -``` -nymd query bank balances ${ADDRESS} -``` - -For example, on the Sanbox testnet this would return: - -```yaml -balances: -- amount: "919376" -denom: unymt -pagination: -next_key: null -total: "0" -``` - -You can, of course, stake back the available balance to your validator with the following command. - -> Remember to save some tokens for gas costs! - -``` -# Mainnet -nyxd tx staking delegate VALOPERADDRESS AMOUNTunym - --from="KEYRING_NAME" - --keyring-backend=os - --chain-id=nyx - --gas="auto" - --gas-adjustment=1.15 - --fees 5000unyx -``` -``` -# Sandbox Testnet -nyxd tx staking delegate VALOPERADDRESS AMOUNTunymt - --from="KEYRING_NAME" - --keyring-backend=os - --chain-id=sandbox - --gas="auto" - --gas-adjustment=1.15 - --fees 5000unyxt -``` - -## Validator port reference -All validator-specific port configuration can be found in `$HOME/.nymd/config/config.toml`. If you do edit any port configs, remember to restart your validator. - -| Default port | Use | -|--------------|--------------------------------------| -| 1317 | REST API server endpoint | -| 26656 | Listen for incoming peer connections | -| 26660 | Listen for Prometheus connections | diff --git a/documentation/docs/src/nodes/validator.md b/documentation/docs/src/nodes/validator.md new file mode 100644 index 0000000000..6b7af1913a --- /dev/null +++ b/documentation/docs/src/nodes/validator.md @@ -0,0 +1,12 @@ +# Validator + +> The validator setup and maintenance guide has moved to the [Operator Guides book](TO_DO). + +Validators secure the Nyx blockchain via Proof of Stake consensus. The Nyx validators are run via the `nyxd` binary ([codebase](https://github.com/nymtech/nyxd)), maintaining a CosmWasm- and IBC-enabled blockchain. + +The blockchain plays a supporting but fundamental role in the mixnet: the `NYM` token used to incentivise node operators is one of two native tokens of the chain, and the chain is where the [Mixnet](../nyx/mixnet-contract.md) and [Vesting](../nyx/vesting-contract.md) smart contracts are deployed. + +## Further Reading +* TODO section of whitepaper +* TODO blogpost +* TODO gaiad link(s) \ No newline at end of file From 84741f20203ab2db6cd17ed3c47532f5a10ccd4e Mon Sep 17 00:00:00 2001 From: mfahampshire Date: Thu, 3 Aug 2023 19:51:03 +0200 Subject: [PATCH 064/361] cont. --- documentation/docs/src/nodes/gateway.md | 13 ++++++++++++- documentation/docs/src/nodes/mixnode.md | 7 +++++-- documentation/docs/src/nodes/validator.md | 2 +- 3 files changed, 18 insertions(+), 4 deletions(-) diff --git a/documentation/docs/src/nodes/gateway.md b/documentation/docs/src/nodes/gateway.md index f4ee68e242..ad2da8e8ca 100644 --- a/documentation/docs/src/nodes/gateway.md +++ b/documentation/docs/src/nodes/gateway.md @@ -1 +1,12 @@ -# Gateway +# Gateways + +> The gateway setup and maintenance guide has migrated to the [Operator Guides book](TO_DO). + +Gateways are key to both the usability of the mixnet, and the operation of the mixnet's tokenomics. They serve two main functions: +* (When the mixnet is no longer in 'free' mode) to act literally as gateways; checking for zkNym credentials (previously referred to as [Coconut Credentials](../bandwidth-credentials.md)) that prove a user has paid to send traffic through the mixnet. A % of the worth of these credentials will be distributed to the operator of the gateway periodically as payment for providing their service. The more credentials user clients 'spend' with them (because of their quality of service) the higher the rewards. The rest of this value will be sent to the Mixmining Pool, a pool of tokens from which `NYM` rewards are distributed to mix node operators. +* Act as a mailbox for connected clients. Clients create a lasting relationship with a gateway on initialisation, binding themselves to always use a particular gateway as their egress point for mixnet traffic, and always receive mixnet traffic from it (see the [mixnet traffic flow page](../architecture/traffic-flow.md) for further details). If a client is offline and the Gateway can't deliver packets addressed to it, they will hold these packets until the client comes back online. + +## Further Reading +* TODO whitepaper section +* TODO loopix paper section +* TODO blogpost \ No newline at end of file diff --git a/documentation/docs/src/nodes/mixnode.md b/documentation/docs/src/nodes/mixnode.md index 159251ad3e..14e42a54b9 100644 --- a/documentation/docs/src/nodes/mixnode.md +++ b/documentation/docs/src/nodes/mixnode.md @@ -1,4 +1,4 @@ -# Mix Node +# Mix Nodes > The mix node setup and maintenance guide has migrated to the [Operator Guides book](TO_DO). @@ -8,10 +8,13 @@ Mix nodes perform one task: receiving packets, decrypting their outer 'layer', a The aim of this mixing is to protect against timing-based deanonymisation attempts by a global adversary able to monitor the entire network with a 'God's Eye View'. +Mix nodes are rewarded according to their quality of service, and their inclusion in the active set (i.e. the nodes that mix traffic for the next epoch) affected by this (as well as their delegation-based reputation - see [further reading](#further-reading) for more on this). + ## (Coming soon) Mixing: a Step-by-Step Breakdown ## Further reading TODO -*
+* TODO SECTION OF WHITEPAPER +* TODO LOOPIX PAPER SECTION * * diff --git a/documentation/docs/src/nodes/validator.md b/documentation/docs/src/nodes/validator.md index 6b7af1913a..0ef86744a8 100644 --- a/documentation/docs/src/nodes/validator.md +++ b/documentation/docs/src/nodes/validator.md @@ -1,4 +1,4 @@ -# Validator +# Validators > The validator setup and maintenance guide has moved to the [Operator Guides book](TO_DO). From a8ddaddaf4beee8fa05d5b359e80b13b78ce920e Mon Sep 17 00:00:00 2001 From: mfahampshire Date: Thu, 3 Aug 2023 21:15:58 +0200 Subject: [PATCH 065/361] skeleton of NR page --- documentation/docs/src/nodes/network-requester.md | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/documentation/docs/src/nodes/network-requester.md b/documentation/docs/src/nodes/network-requester.md index 797dfd7795..747efbe62a 100644 --- a/documentation/docs/src/nodes/network-requester.md +++ b/documentation/docs/src/nodes/network-requester.md @@ -1 +1,9 @@ -# Network Requester +# Network Requesters + +> The network requester setup and maintenance guide has moved to the [Operator Guides book](TO_DO). + +Network requesters are the first instance of the catch-all term 'service', or 'service providers'. In essence, think of services as being the part of the mixnet infrastructure that let you _do_ something, such as access emails, messaging service backends, or blockchains via the mixnet. + +Network requesters, in essence, act as a form of proxy (somewhat analagous to a Tor exit node) + + From f26f159bb0e22d1b0c45ea96c62898a43efadac8 Mon Sep 17 00:00:00 2001 From: serinko <97586125+serinko@users.noreply.github.com> Date: Fri, 4 Aug 2023 13:18:12 +0200 Subject: [PATCH 066/361] filled/docs/validators.md --- .../docs/src/images/nym-token-flow.png | Bin 0 -> 174671 bytes documentation/docs/src/nodes/gateway.md | 4 +-- documentation/docs/src/nodes/mixnode.md | 2 +- .../docs/src/nodes/network-requester.md | 2 +- documentation/docs/src/nodes/validator.md | 23 ++++++++++++++---- .../operators/src/nodes/maintenance.md | 3 ++- .../operators/src/preliminary-steps.md | 2 +- 7 files changed, 25 insertions(+), 11 deletions(-) create mode 100644 documentation/docs/src/images/nym-token-flow.png diff --git a/documentation/docs/src/images/nym-token-flow.png b/documentation/docs/src/images/nym-token-flow.png new file mode 100644 index 0000000000000000000000000000000000000000..eb026e23258efb6830022aef1b5af8566c8ecec1 GIT binary patch literal 174671 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

rAqPF^d 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 U$$MSzw1{bxnWbsjCAZ)H1LKK-6#xJL literal 0 HcmV?d00001 diff --git a/documentation/docs/src/nodes/gateway.md b/documentation/docs/src/nodes/gateway.md index ad2da8e8ca..ebdae64574 100644 --- a/documentation/docs/src/nodes/gateway.md +++ b/documentation/docs/src/nodes/gateway.md @@ -1,6 +1,6 @@ # Gateways -> The gateway setup and maintenance guide has migrated to the [Operator Guides book](TO_DO). +> The gateway setup and maintenance guide has migrated to the [Operator Guides book](https://nymtech.net/operators/nodes/gateway-setup.html). Gateways are key to both the usability of the mixnet, and the operation of the mixnet's tokenomics. They serve two main functions: * (When the mixnet is no longer in 'free' mode) to act literally as gateways; checking for zkNym credentials (previously referred to as [Coconut Credentials](../bandwidth-credentials.md)) that prove a user has paid to send traffic through the mixnet. A % of the worth of these credentials will be distributed to the operator of the gateway periodically as payment for providing their service. The more credentials user clients 'spend' with them (because of their quality of service) the higher the rewards. The rest of this value will be sent to the Mixmining Pool, a pool of tokens from which `NYM` rewards are distributed to mix node operators. @@ -9,4 +9,4 @@ Gateways are key to both the usability of the mixnet, and the operation of the m ## Further Reading * TODO whitepaper section * TODO loopix paper section -* TODO blogpost \ No newline at end of file +* TODO blogpost diff --git a/documentation/docs/src/nodes/mixnode.md b/documentation/docs/src/nodes/mixnode.md index 14e42a54b9..2d1911a990 100644 --- a/documentation/docs/src/nodes/mixnode.md +++ b/documentation/docs/src/nodes/mixnode.md @@ -1,6 +1,6 @@ # Mix Nodes -> The mix node setup and maintenance guide has migrated to the [Operator Guides book](TO_DO). +> The mix node setup and maintenance guide has migrated to the [Operator Guides book](https://nymtech.net/developers/nodes/mix-node-setup.html). Mix nodes are the backbone of the mixnet. These are the nodes that perform 'mix mining', otherwise known simply as 'mixing' or performing the 'poisson mix'. diff --git a/documentation/docs/src/nodes/network-requester.md b/documentation/docs/src/nodes/network-requester.md index 747efbe62a..83778e18a4 100644 --- a/documentation/docs/src/nodes/network-requester.md +++ b/documentation/docs/src/nodes/network-requester.md @@ -1,6 +1,6 @@ # Network Requesters -> The network requester setup and maintenance guide has moved to the [Operator Guides book](TO_DO). +> The network requester setup and maintenance guide has moved to the [Operator Guides book](https://nymtech.net/developers/nodes/network-requester-setup.html). Network requesters are the first instance of the catch-all term 'service', or 'service providers'. In essence, think of services as being the part of the mixnet infrastructure that let you _do_ something, such as access emails, messaging service backends, or blockchains via the mixnet. diff --git a/documentation/docs/src/nodes/validator.md b/documentation/docs/src/nodes/validator.md index 0ef86744a8..ac3d896bca 100644 --- a/documentation/docs/src/nodes/validator.md +++ b/documentation/docs/src/nodes/validator.md @@ -1,12 +1,25 @@ +- [x] TODO section of whitepaper +- [x] TODO blogpost +- [ ] TODO gaias link(s) + # Validators -> The validator setup and maintenance guide has moved to the [Operator Guides book](TO_DO). +> The validator setup and maintenance guide has moved to the [Operator Guides book](https://nymtech.net/operators/nodes/validator-setup.html). -Validators secure the Nyx blockchain via Proof of Stake consensus. The Nyx validators are run via the `nyxd` binary ([codebase](https://github.com/nymtech/nyxd)), maintaining a CosmWasm- and IBC-enabled blockchain. +> We are currently working towards building up a closed set of reputable validators. You can ask us for coins to get in, but please don't be offended if we say no - validators are part of our system's core security and we are starting out with people we already know or who have a solid reputation. + + +Validators secure the Nyx blockchain via Proof of Stake consensus. The Nyx blockchain records the ledger of NYM transactions and executes the smart contracts for distributing NYM rewards. The Nyx validators are run via the `nyxd` binary ([codebase](https://github.com/nymtech/nyxd)), maintaining a CosmWasm- and IBC-enabled blockchain. The blockchain plays a supporting but fundamental role in the mixnet: the `NYM` token used to incentivise node operators is one of two native tokens of the chain, and the chain is where the [Mixnet](../nyx/mixnet-contract.md) and [Vesting](../nyx/vesting-contract.md) smart contracts are deployed. +![Nym Token Flow](../images/nym-token-flow.png) + ## Further Reading -* TODO section of whitepaper -* TODO blogpost -* TODO gaiad link(s) \ No newline at end of file + +* Detailed info on Nyx Validators and token flow can be found in [Nym Reward Sharing for Mixnets document](https://nymtech.net/nym-cryptoecon-paper.pdf) in section 2.3 and 2.4. +* For more detailed calculation, read our blog post [Nym Token Economics update](https://blog.nymtech.net/nym-token-economics-update-fedff0ed5267). +* More info on staking can be found [here](https://blog.nymtech.net/staking-in-nym-introducing-mainnet-mixmining-f9bb1cbc7c36). +* [Here](https://blog.nymtech.net/want-to-stake-in-nym-here-is-how-to-choose-a-mix-node-to-delegate-nym-to-c3b862add165) is more info on how to choose a mix node for delegation. +* Our [quarterly update](https://blog.nymtech.net/quarterly-token-economic-parameter-update-b2862948710f) on token economics from July 2023. + diff --git a/documentation/operators/src/nodes/maintenance.md b/documentation/operators/src/nodes/maintenance.md index 7ffe1d6d58..05723461f2 100644 --- a/documentation/operators/src/nodes/maintenance.md +++ b/documentation/operators/src/nodes/maintenance.md @@ -7,7 +7,8 @@ TODO - copy that dir over to the new vps - re-run init - remember that init doesn't overwrite existing keys - to generate a config with the new listening address etc - change the node info via the wallet - - Since otherwise their keys will point to the old IP address in the smart contract, and their node will (a) not be able to be connected to, and (b) fail uptime checks + - Since otherwise their keys will point to the old IP address in the smart contract, and their node will (a) not be able to be connected to, and (b) fail uptime checks +- [ ] Test the scp -3 command in the moving a node section ---> ## Useful commands diff --git a/documentation/operators/src/preliminary-steps.md b/documentation/operators/src/preliminary-steps.md index d36ca76da4..ec13e7ea59 100644 --- a/documentation/operators/src/preliminary-steps.md +++ b/documentation/operators/src/preliminary-steps.md @@ -1,6 +1,6 @@ # Preliminary Steps -> The Nym `mix node`, `gateway` and `network-requester` binaries were built in the [building nym](../binaries/building-nym.md) section. If you haven't yet built Nym and want to run the code, go there first. +> The Nym `mix node`, `gateway` and `network-requester` binaries were built in the [building nym](./binaries/building-nym.md) section. If you haven't yet built Nym and want to run the code, go there first. ## Preliminary steps From 0b29e28e3f094ca571a6d9594d244f1af7ea18a6 Mon Sep 17 00:00:00 2001 From: serinko <97586125+serinko@users.noreply.github.com> Date: Fri, 4 Aug 2023 13:19:29 +0200 Subject: [PATCH 067/361] remove reduntant nym/docs/ --- docs/src/binaries/building-nym.html | 242 ---------------------- docs/src/binaries/building-nym.md | 1 - docs/src/binaries/init-and-config.html | 242 ---------------------- docs/src/binaries/init-and-config.md | 1 - docs/src/binaries/pre-built-binaries.html | 242 ---------------------- docs/src/binaries/pre-built-binaries.md | 1 - 6 files changed, 729 deletions(-) delete mode 100644 docs/src/binaries/building-nym.html delete mode 100644 docs/src/binaries/building-nym.md delete mode 100644 docs/src/binaries/init-and-config.html delete mode 100644 docs/src/binaries/init-and-config.md delete mode 100644 docs/src/binaries/pre-built-binaries.html delete mode 100644 docs/src/binaries/pre-built-binaries.md diff --git a/docs/src/binaries/building-nym.html b/docs/src/binaries/building-nym.html deleted file mode 100644 index 90e9c937cc..0000000000 --- a/docs/src/binaries/building-nym.html +++ /dev/null @@ -1,242 +0,0 @@ - - - - - - Building from Source - Nym Operators Guides - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- - - - - - - - - - - - - - -
- -
- - - - - - - - -
-
- {comment} -
- - {comment} -
- - {comment} -
- - {comment} -
- - {comment} -
- - {comment} -
- - {comment} -
- -

Building from Source

- -
- - -
-
- - - -
- - - - - - - - - - - - - - - - - - - - - -
- - diff --git a/docs/src/binaries/building-nym.md b/docs/src/binaries/building-nym.md deleted file mode 100644 index f195be32f9..0000000000 --- a/docs/src/binaries/building-nym.md +++ /dev/null @@ -1 +0,0 @@ -# Building from Source diff --git a/docs/src/binaries/init-and-config.html b/docs/src/binaries/init-and-config.html deleted file mode 100644 index 2a9390a316..0000000000 --- a/docs/src/binaries/init-and-config.html +++ /dev/null @@ -1,242 +0,0 @@ - - - - - - Binary Initialisation and Configuration - Nym Operators Guides - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - -
- -
- - - - - - - - -
-
- {comment} -
- - {comment} -
- - {comment} -
- - {comment} -
- - {comment} -
- - {comment} -
- - {comment} -
- -

Binary Initialisation and Configuration

- -
- - -
-
- - - -
- - - - - - - - - - - - - - - - - - - - - -
- - diff --git a/docs/src/binaries/init-and-config.md b/docs/src/binaries/init-and-config.md deleted file mode 100644 index 9087d5cf00..0000000000 --- a/docs/src/binaries/init-and-config.md +++ /dev/null @@ -1 +0,0 @@ -# Binary Initialisation and Configuration diff --git a/docs/src/binaries/pre-built-binaries.html b/docs/src/binaries/pre-built-binaries.html deleted file mode 100644 index a02f51d2bd..0000000000 --- a/docs/src/binaries/pre-built-binaries.html +++ /dev/null @@ -1,242 +0,0 @@ - - - - - - Pre-built Binaries - Nym Operators Guides - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - -
- -
- - - - - - - - -
-
- {comment} -
- - {comment} -
- - {comment} -
- - {comment} -
- - {comment} -
- - {comment} -
- - {comment} -
- -

Pre-built Binaries

- -
- - -
-
- - - -
- - - - - - - - - - - - - - - - - - - - - -
- - diff --git a/docs/src/binaries/pre-built-binaries.md b/docs/src/binaries/pre-built-binaries.md deleted file mode 100644 index 8c52595ce9..0000000000 --- a/docs/src/binaries/pre-built-binaries.md +++ /dev/null @@ -1 +0,0 @@ -# Pre-built Binaries From 1b1f0293ec13e5db3078854750cb081dae0e52d1 Mon Sep 17 00:00:00 2001 From: pierre Date: Fri, 4 Aug 2023 15:36:06 +0200 Subject: [PATCH 068/361] fix(nc-android): text colors --- .../main/java/net/nymtech/nyms5/MainActivity.kt | 14 ++++++++++++-- .../main/java/net/nymtech/nyms5/ui/theme/Color.kt | 10 +++++++++- 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/nym-connect/native/android/app/src/main/java/net/nymtech/nyms5/MainActivity.kt b/nym-connect/native/android/app/src/main/java/net/nymtech/nyms5/MainActivity.kt index 3530d6db81..d01672cc4d 100644 --- a/nym-connect/native/android/app/src/main/java/net/nymtech/nyms5/MainActivity.kt +++ b/nym-connect/native/android/app/src/main/java/net/nymtech/nyms5/MainActivity.kt @@ -6,6 +6,7 @@ import android.util.Log import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import androidx.activity.viewModels +import androidx.compose.foundation.isSystemInDarkTheme import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer @@ -66,6 +67,8 @@ import androidx.work.WorkInfo import androidx.work.WorkManager import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.map +import net.nymtech.nyms5.ui.theme.darkYellow +import net.nymtech.nyms5.ui.theme.lightYellow class MainActivity : ComponentActivity() { private val tag = "MainActivity" @@ -309,6 +312,10 @@ fun Monitoring( onSwitch: (value: Boolean) -> Unit, ) { var monitoring by remember { mutableStateOf(initialValue) } + val yellowColor = when (isSystemInDarkTheme()) { + true -> darkYellow + false -> lightYellow + } Column( modifier = modifier @@ -330,10 +337,13 @@ fun Monitoring( Icon( painter = painterResource(R.drawable.warning_24), contentDescription = "copy to clipboard", - tint = Color.Yellow + tint = yellowColor ) Spacer(modifier = modifier.width(16.dp)) - Text(stringResource(R.string.monitoring_desc_2), color = Color.Yellow) + Text( + stringResource(R.string.monitoring_desc_2), + color = yellowColor + ) } Spacer(modifier = modifier.height(18.dp)) Text(stringResource(R.string.monitoring_desc_1)) diff --git a/nym-connect/native/android/app/src/main/java/net/nymtech/nyms5/ui/theme/Color.kt b/nym-connect/native/android/app/src/main/java/net/nymtech/nyms5/ui/theme/Color.kt index 1667a76208..85daf1ecf4 100644 --- a/nym-connect/native/android/app/src/main/java/net/nymtech/nyms5/ui/theme/Color.kt +++ b/nym-connect/native/android/app/src/main/java/net/nymtech/nyms5/ui/theme/Color.kt @@ -8,4 +8,12 @@ val Pink80 = Color(0xFFEFB8C8) val Purple40 = Color(0xFF6650a4) val PurpleGrey40 = Color(0xFF625b71) -val Pink40 = Color(0xFF7D5260) \ No newline at end of file +val Pink40 = Color(0xFF7D5260) + +// Custom colors +// generated from https://m3.material.io/theme-builder#/custom +val yellow = Color(0xFFD3B919) +val lightYellow = Color(0xFF6D5E00) +val lightOnYellow = Color(0xFFFFFFFF) +val darkYellow = Color(0xFFE1C62A) +val darkOnYellow = Color(0xFF393000) From 5711bbd0c1c3e2fe003b3be5968bd5e1e66106d6 Mon Sep 17 00:00:00 2001 From: pierre Date: Fri, 4 Aug 2023 17:15:01 +0200 Subject: [PATCH 069/361] feat(nc-android): add runtime permission request --- .../java/net/nymtech/nyms5/MainActivity.kt | 320 ++---------------- .../nyms5/ui/composables/HomeScreen.kt | 113 +++++++ .../nyms5/ui/composables/Monitoring.kt | 93 +++++ .../nyms5/ui/composables/S5ClientSwitch.kt | 142 ++++++++ 4 files changed, 376 insertions(+), 292 deletions(-) create mode 100644 nym-connect/native/android/app/src/main/java/net/nymtech/nyms5/ui/composables/HomeScreen.kt create mode 100644 nym-connect/native/android/app/src/main/java/net/nymtech/nyms5/ui/composables/Monitoring.kt create mode 100644 nym-connect/native/android/app/src/main/java/net/nymtech/nyms5/ui/composables/S5ClientSwitch.kt diff --git a/nym-connect/native/android/app/src/main/java/net/nymtech/nyms5/MainActivity.kt b/nym-connect/native/android/app/src/main/java/net/nymtech/nyms5/MainActivity.kt index d01672cc4d..962e2c3760 100644 --- a/nym-connect/native/android/app/src/main/java/net/nymtech/nyms5/MainActivity.kt +++ b/nym-connect/native/android/app/src/main/java/net/nymtech/nyms5/MainActivity.kt @@ -1,74 +1,23 @@ package net.nymtech.nyms5 +import android.Manifest.permission.POST_NOTIFICATIONS +import android.content.pm.PackageManager import android.os.Build import android.os.Bundle import android.util.Log import androidx.activity.ComponentActivity import androidx.activity.compose.setContent +import androidx.activity.result.contract.ActivityResultContracts import androidx.activity.viewModels -import androidx.compose.foundation.isSystemInDarkTheme -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.width -import androidx.compose.foundation.rememberScrollState -import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.foundation.verticalScroll -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.ArrowBack -import androidx.compose.material.icons.filled.Menu -import androidx.compose.material3.CenterAlignedTopAppBar -import androidx.compose.material3.DropdownMenu -import androidx.compose.material3.DropdownMenuItem -import androidx.compose.material3.ExperimentalMaterial3Api -import androidx.compose.material3.Icon -import androidx.compose.material3.IconButton -import androidx.compose.material3.LinearProgressIndicator -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Scaffold -import androidx.compose.material3.Surface -import androidx.compose.material3.Switch -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.setValue -import androidx.compose.ui.Modifier -import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.unit.dp import androidx.lifecycle.Lifecycle import androidx.lifecycle.lifecycleScope import androidx.lifecycle.repeatOnLifecycle import kotlinx.coroutines.launch import net.nymtech.nyms5.ui.theme.NymTheme -import androidx.compose.runtime.getValue -import androidx.compose.runtime.remember -import androidx.compose.runtime.rememberCoroutineScope -import androidx.compose.runtime.saveable.rememberSaveable -import androidx.compose.ui.Alignment -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.platform.LocalClipboardManager -import androidx.compose.ui.platform.testTag -import androidx.compose.ui.res.painterResource -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.text.AnnotatedString -import androidx.compose.ui.text.font.FontStyle -import androidx.datastore.core.DataStore -import androidx.datastore.preferences.core.Preferences -import androidx.datastore.preferences.core.edit -import androidx.navigation.compose.NavHost -import androidx.navigation.compose.composable -import androidx.navigation.compose.currentBackStackEntryAsState -import androidx.navigation.compose.rememberNavController import androidx.work.WorkInfo import androidx.work.WorkManager -import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.map -import net.nymtech.nyms5.ui.theme.darkYellow -import net.nymtech.nyms5.ui.theme.lightYellow +import net.nymtech.nyms5.ui.composables.HomeScreen class MainActivity : ComponentActivity() { private val tag = "MainActivity" @@ -80,11 +29,24 @@ class MainActivity : ComponentActivity() { ) } + private val requestPermissionLauncher = + registerForActivityResult( + ActivityResultContracts.RequestPermission() + ) { isGranted: Boolean -> + if (isGranted) { + Log.d(tag, "permission POST_NOTIFICATIONS has been granted") + } else { + Log.d(tag, "permission POST_NOTIFICATIONS has NOT been granted") + } + } + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) Log.d(tag, "____onCreate") Log.i(tag, "device SDK [${Build.VERSION.SDK_INT}]") + checkPermission() + // observe proxy work progress WorkManager.getInstance(applicationContext) .getWorkInfoByIdLiveData(ProxyWorker.workId) @@ -142,248 +104,22 @@ class MainActivity : ComponentActivity() { super.onStart() viewModel.checkStateSync() } -} -@OptIn(ExperimentalMaterial3Api::class) -@Composable -fun HomeScreen( - proxyState: MainViewModel.ProxyState, - monitoring: Boolean, - dataStore: DataStore, - onSwitch: (value: Boolean) -> Unit, -) { - val navController = rememberNavController() - var expanded by remember { mutableStateOf(false) } - val scope = rememberCoroutineScope() - - Scaffold(topBar = { - CenterAlignedTopAppBar( - title = { - Text(stringResource(R.string.app_name)) - }, - navigationIcon = { - val navBackStackEntry by navController.currentBackStackEntryAsState() - val currentRoute = navBackStackEntry?.destination?.route - - if (currentRoute === "proxy") { - IconButton(onClick = { expanded = true }) { - Icon( - imageVector = Icons.Filled.Menu, - contentDescription = "Main menu" - ) - } - DropdownMenu( - expanded = expanded, - onDismissRequest = { expanded = false } - ) { - DropdownMenuItem(onClick = { - navController.navigate("monitoring") { - popUpTo("proxy") - } - expanded = false - }, text = { - Text("Error reporting") - }) - } - } else { - IconButton(onClick = { - navController.navigate("proxy") { - popUpTo("proxy") - } - }) { - Icon( - imageVector = Icons.Filled.ArrowBack, - contentDescription = "Back home" - ) - } + private fun checkPermission() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + // POST_NOTIFICATIONS is needed for the notification displayed + // when socks5 proxy client is running + when (checkSelfPermission(POST_NOTIFICATIONS)) { + PackageManager.PERMISSION_GRANTED -> { + Log.d(tag, "check permission POST_NOTIFICATIONS: granted") } - }, - ) - }) { contentPadding -> - NavHost( - navController = navController, - startDestination = "proxy", - modifier = Modifier.padding(contentPadding) - ) { - composable("proxy") { - S5ClientSwitch( - connected = proxyState.connected, - loading = proxyState.loading, - onSwitch = onSwitch - ) - } - composable("monitoring") { - Monitoring(initialValue = monitoring) { - scope.launch(Dispatchers.IO) { - dataStore.edit { settings -> settings[monitoringKey] = it } - } + + else -> { + Log.d(tag, "check permission POST_NOTIFICATIONS: not granted") + requestPermissionLauncher.launch(POST_NOTIFICATIONS) } } } } } -@OptIn(ExperimentalMaterial3Api::class) -@Composable -fun S5ClientSwitch( - connected: Boolean, - loading: Boolean, - onSwitch: (value: Boolean) -> Unit, - modifier: Modifier = Modifier -) { - val clipboardManager = LocalClipboardManager.current - val host = stringResource(R.string.proxy_host) - val port = stringResource(R.string.proxy_port) - if (loading) { - Row { - LinearProgressIndicator( - modifier = modifier.fillMaxWidth(), - color = MaterialTheme.colorScheme.secondary - ) - } - } else { - Spacer(modifier = modifier.height(2.dp)) - } - Column(modifier = modifier.padding(16.dp)) { - Row( - modifier = modifier.padding(16.dp), - verticalAlignment = Alignment.CenterVertically - ) { - Text("Nym proxy") - Spacer(modifier = modifier.width(14.dp)) - Switch(checked = connected, enabled = !loading, onCheckedChange = { - onSwitch(!connected) - }, modifier = Modifier.testTag("switch_connect")) - } - if (connected && !loading) { - Column(modifier = modifier.padding(16.dp)) { - Text( - color = Color.Green, - fontStyle = FontStyle.Italic, - text = stringResource(R.string.connected_text) - ) - Spacer(modifier = modifier.height(10.dp)) - Row( - verticalAlignment = Alignment.CenterVertically - ) { - Surface( - onClick = { clipboardManager.setText(AnnotatedString(host)) }, - shape = RoundedCornerShape(6.dp), - ) { - Row( - modifier = modifier.padding(8.dp), - verticalAlignment = Alignment.CenterVertically, - ) { - Text(host) - Spacer(modifier = modifier.width(8.dp)) - Icon( - painter = painterResource(R.drawable.copy_24), - contentDescription = "copy to clipboard" - ) - } - } - Spacer(modifier = modifier.width(12.dp)) - Surface( - onClick = { clipboardManager.setText(AnnotatedString(port)) }, - shape = RoundedCornerShape(6.dp), - ) { - Row( - modifier = modifier.padding(8.dp), - verticalAlignment = Alignment.CenterVertically, - ) { - Text(port) - Spacer(modifier = modifier.width(8.dp)) - Icon( - painter = painterResource(R.drawable.copy_24), - contentDescription = "copy to clipboard" - ) - } - } - } - } - } - } -} - -@Composable -fun Monitoring( - modifier: Modifier = Modifier, - initialValue: Boolean, - onSwitch: (value: Boolean) -> Unit, -) { - var monitoring by remember { mutableStateOf(initialValue) } - val yellowColor = when (isSystemInDarkTheme()) { - true -> darkYellow - false -> lightYellow - } - - Column( - modifier = modifier - .padding(16.dp) - .verticalScroll(rememberScrollState()) - ) { - Row( - verticalAlignment = Alignment.CenterVertically - ) { - Text("Enable error reporting") - Spacer(modifier = modifier.width(16.dp)) - Switch(checked = monitoring, onCheckedChange = { - monitoring = it - onSwitch(it) - }) - } - Spacer(modifier = modifier.height(18.dp)) - Row(verticalAlignment = Alignment.CenterVertically) { - Icon( - painter = painterResource(R.drawable.warning_24), - contentDescription = "copy to clipboard", - tint = yellowColor - ) - Spacer(modifier = modifier.width(16.dp)) - Text( - stringResource(R.string.monitoring_desc_2), - color = yellowColor - ) - } - Spacer(modifier = modifier.height(18.dp)) - Text(stringResource(R.string.monitoring_desc_1)) - } -} - -@Preview -@Composable -fun PreviewSocks5Client() { - val tag = "UI" - var connected by rememberSaveable { mutableStateOf(false) } - var loading by rememberSaveable { mutableStateOf(false) } - NymTheme { - Surface( - modifier = Modifier.fillMaxSize(), - color = MaterialTheme.colorScheme.background - ) { - S5ClientSwitch(connected, loading, { - when { - it -> Log.d(tag, "switch ON") - else -> Log.d(tag, "switch OFF") - } - connected = it - loading = false - }) - } - } -} - -@Preview -@Composable -fun PreviewMonitoring() { - NymTheme { - Surface( - modifier = Modifier.fillMaxSize(), - color = MaterialTheme.colorScheme.background - ) { - Monitoring(initialValue = false) { - Log.d("Monitoring", "switch $it") - } - } - } -} diff --git a/nym-connect/native/android/app/src/main/java/net/nymtech/nyms5/ui/composables/HomeScreen.kt b/nym-connect/native/android/app/src/main/java/net/nymtech/nyms5/ui/composables/HomeScreen.kt new file mode 100644 index 0000000000..898c7613fe --- /dev/null +++ b/nym-connect/native/android/app/src/main/java/net/nymtech/nyms5/ui/composables/HomeScreen.kt @@ -0,0 +1,113 @@ +package net.nymtech.nyms5.ui.composables + +import androidx.compose.foundation.layout.padding +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.ArrowBack +import androidx.compose.material.icons.filled.Menu +import androidx.compose.material3.CenterAlignedTopAppBar +import androidx.compose.material3.DropdownMenu +import androidx.compose.material3.DropdownMenuItem +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.Scaffold +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import androidx.datastore.core.DataStore +import androidx.datastore.preferences.core.Preferences +import androidx.datastore.preferences.core.edit +import androidx.navigation.compose.NavHost +import androidx.navigation.compose.composable +import androidx.navigation.compose.currentBackStackEntryAsState +import androidx.navigation.compose.rememberNavController +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import net.nymtech.nyms5.MainViewModel +import net.nymtech.nyms5.R +import net.nymtech.nyms5.monitoringKey + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun HomeScreen( + proxyState: MainViewModel.ProxyState, + monitoring: Boolean, + dataStore: DataStore, + onSwitch: (value: Boolean) -> Unit, +) { + val navController = rememberNavController() + var expanded by remember { mutableStateOf(false) } + val scope = rememberCoroutineScope() + + Scaffold(topBar = { + CenterAlignedTopAppBar( + title = { + Text(stringResource(R.string.app_name)) + }, + navigationIcon = { + val navBackStackEntry by navController.currentBackStackEntryAsState() + val currentRoute = navBackStackEntry?.destination?.route + + if (currentRoute === "proxy") { + IconButton(onClick = { expanded = true }) { + Icon( + imageVector = Icons.Filled.Menu, + contentDescription = "Main menu" + ) + } + DropdownMenu( + expanded = expanded, + onDismissRequest = { expanded = false } + ) { + DropdownMenuItem(onClick = { + navController.navigate("monitoring") { + popUpTo("proxy") + } + expanded = false + }, text = { + Text("Error reporting") + }) + } + } else { + IconButton(onClick = { + navController.navigate("proxy") { + popUpTo("proxy") + } + }) { + Icon( + imageVector = Icons.Filled.ArrowBack, + contentDescription = "Back home" + ) + } + } + }, + ) + }) { contentPadding -> + NavHost( + navController = navController, + startDestination = "proxy", + modifier = Modifier.padding(contentPadding) + ) { + composable("proxy") { + S5ClientSwitch( + connected = proxyState.connected, + loading = proxyState.loading, + onSwitch = onSwitch + ) + } + composable("monitoring") { + Monitoring(initialValue = monitoring) { + scope.launch(Dispatchers.IO) { + dataStore.edit { settings -> settings[monitoringKey] = it } + } + } + } + } + } +} \ No newline at end of file diff --git a/nym-connect/native/android/app/src/main/java/net/nymtech/nyms5/ui/composables/Monitoring.kt b/nym-connect/native/android/app/src/main/java/net/nymtech/nyms5/ui/composables/Monitoring.kt new file mode 100644 index 0000000000..4195647891 --- /dev/null +++ b/nym-connect/native/android/app/src/main/java/net/nymtech/nyms5/ui/composables/Monitoring.kt @@ -0,0 +1,93 @@ +package net.nymtech.nyms5.ui.composables + +import android.util.Log +import androidx.compose.foundation.isSystemInDarkTheme +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll +import androidx.compose.material3.Icon +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Surface +import androidx.compose.material3.Switch +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import net.nymtech.nyms5.R +import net.nymtech.nyms5.ui.theme.NymTheme +import net.nymtech.nyms5.ui.theme.darkYellow +import net.nymtech.nyms5.ui.theme.lightYellow + +@Composable +fun Monitoring( + modifier: Modifier = Modifier, + initialValue: Boolean, + onSwitch: (value: Boolean) -> Unit, +) { + var monitoring by remember { mutableStateOf(initialValue) } + val yellowColor = when (isSystemInDarkTheme()) { + true -> darkYellow + false -> lightYellow + } + + Column( + modifier = modifier + .padding(16.dp) + .verticalScroll(rememberScrollState()) + ) { + Row( + verticalAlignment = Alignment.CenterVertically + ) { + Text("Enable error reporting") + Spacer(modifier = modifier.width(16.dp)) + Switch(checked = monitoring, onCheckedChange = { + monitoring = it + onSwitch(it) + }) + } + Spacer(modifier = modifier.height(18.dp)) + Row(verticalAlignment = Alignment.CenterVertically) { + Icon( + painter = painterResource(R.drawable.warning_24), + contentDescription = "copy to clipboard", + tint = yellowColor + ) + Spacer(modifier = modifier.width(16.dp)) + Text( + stringResource(R.string.monitoring_desc_2), + color = yellowColor + ) + } + Spacer(modifier = modifier.height(18.dp)) + Text(stringResource(R.string.monitoring_desc_1)) + } +} + +@Preview +@Composable +fun PreviewMonitoring() { + NymTheme { + Surface( + modifier = Modifier.fillMaxSize(), + color = MaterialTheme.colorScheme.background + ) { + Monitoring(initialValue = false) { + Log.d("Monitoring", "switch $it") + } + } + } +} \ No newline at end of file diff --git a/nym-connect/native/android/app/src/main/java/net/nymtech/nyms5/ui/composables/S5ClientSwitch.kt b/nym-connect/native/android/app/src/main/java/net/nymtech/nyms5/ui/composables/S5ClientSwitch.kt new file mode 100644 index 0000000000..3361e6fc8a --- /dev/null +++ b/nym-connect/native/android/app/src/main/java/net/nymtech/nyms5/ui/composables/S5ClientSwitch.kt @@ -0,0 +1,142 @@ +package net.nymtech.nyms5.ui.composables + +import android.util.Log +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.Icon +import androidx.compose.material3.LinearProgressIndicator +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Surface +import androidx.compose.material3.Switch +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.platform.LocalClipboardManager +import androidx.compose.ui.platform.testTag +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.AnnotatedString +import androidx.compose.ui.text.font.FontStyle +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import net.nymtech.nyms5.R +import net.nymtech.nyms5.ui.theme.NymTheme + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun S5ClientSwitch( + connected: Boolean, + loading: Boolean, + onSwitch: (value: Boolean) -> Unit, + modifier: Modifier = Modifier +) { + val clipboardManager = LocalClipboardManager.current + val host = stringResource(R.string.proxy_host) + val port = stringResource(R.string.proxy_port) + if (loading) { + Row { + LinearProgressIndicator( + modifier = modifier.fillMaxWidth(), + color = MaterialTheme.colorScheme.secondary + ) + } + } else { + Spacer(modifier = modifier.height(2.dp)) + } + Column(modifier = modifier.padding(16.dp)) { + Row( + modifier = modifier.padding(16.dp), + verticalAlignment = Alignment.CenterVertically + ) { + Text("Nym proxy") + Spacer(modifier = modifier.width(14.dp)) + Switch(checked = connected, enabled = !loading, onCheckedChange = { + onSwitch(!connected) + }, modifier = Modifier.testTag("switch_connect")) + } + if (connected && !loading) { + Column(modifier = modifier.padding(16.dp)) { + Text( + color = Color.Green, + fontStyle = FontStyle.Italic, + text = stringResource(R.string.connected_text) + ) + Spacer(modifier = modifier.height(10.dp)) + Row( + verticalAlignment = Alignment.CenterVertically + ) { + Surface( + onClick = { clipboardManager.setText(AnnotatedString(host)) }, + shape = RoundedCornerShape(6.dp), + ) { + Row( + modifier = modifier.padding(8.dp), + verticalAlignment = Alignment.CenterVertically, + ) { + Text(host) + Spacer(modifier = modifier.width(8.dp)) + Icon( + painter = painterResource(R.drawable.copy_24), + contentDescription = "copy to clipboard" + ) + } + } + Spacer(modifier = modifier.width(12.dp)) + Surface( + onClick = { clipboardManager.setText(AnnotatedString(port)) }, + shape = RoundedCornerShape(6.dp), + ) { + Row( + modifier = modifier.padding(8.dp), + verticalAlignment = Alignment.CenterVertically, + ) { + Text(port) + Spacer(modifier = modifier.width(8.dp)) + Icon( + painter = painterResource(R.drawable.copy_24), + contentDescription = "copy to clipboard" + ) + } + } + } + } + } + } +} + +@Preview +@Composable +fun PreviewSocks5Client() { + val tag = "UI" + var connected by rememberSaveable { mutableStateOf(false) } + var loading by rememberSaveable { mutableStateOf(false) } + NymTheme { + Surface( + modifier = Modifier.fillMaxSize(), + color = MaterialTheme.colorScheme.background + ) { + S5ClientSwitch(connected, loading, { + when { + it -> Log.d(tag, "switch ON") + else -> Log.d(tag, "switch OFF") + } + connected = it + loading = false + }) + } + } +} \ No newline at end of file From 4a23729a6ad968f539252c070763277437d83a22 Mon Sep 17 00:00:00 2001 From: Mark Sinclair <14054343+mmsinclair@users.noreply.github.com> Date: Fri, 4 Aug 2023 16:31:21 +0100 Subject: [PATCH 070/361] Add GitHub Action to build Typescript SDK docs --- .github/workflows/sdk-docs-typescript.yml | 50 +++++++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100644 .github/workflows/sdk-docs-typescript.yml diff --git a/.github/workflows/sdk-docs-typescript.yml b/.github/workflows/sdk-docs-typescript.yml new file mode 100644 index 0000000000..2df60dcc0b --- /dev/null +++ b/.github/workflows/sdk-docs-typescript.yml @@ -0,0 +1,50 @@ +name: Typescript SDK docs + +on: + push: + paths: + - "sdk/typescript/**" + pull_request: + paths: + - "sdk/typescript/**" + +jobs: + build: + runs-on: custom-runner-linux + steps: + - uses: actions/checkout@v2 + - name: Install rsync + run: sudo apt-get install rsync + continue-on-error: true + - uses: rlespinasse/github-slug-action@v3.x + - uses: actions/setup-node@v3 + with: + node-version: 18 + - name: Install Rust stable + uses: actions-rs/toolchain@v1 + with: + toolchain: stable + - name: Setup yarn + run: npm install -g yarn + - name: Install + run: yarn + - name: Matrix - Node Install + run: npm install + working-directory: .github/workflows/support-files + - name: Matrix - Send Notification + env: + NYM_NOTIFICATION_KIND: ts-packages + NYM_PROJECT_NAME: "ts-packages" + NYM_CI_WWW_BASE: "${{ secrets.NYM_CI_WWW_BASE }}" + NYM_CI_WWW_LOCATION: "ts-${{ env.GITHUB_REF_SLUG }}" + GIT_COMMIT_MESSAGE: "${{ github.event.head_commit.message }}" + GIT_BRANCH: "${GITHUB_REF##*/}" + IS_SUCCESS: "${{ job.status == 'success' }}" + MATRIX_SERVER: "${{ secrets.MATRIX_SERVER }}" + MATRIX_ROOM: "${{ secrets.MATRIX_ROOM }}" + MATRIX_USER_ID: "${{ secrets.MATRIX_USER_ID }}" + MATRIX_TOKEN: "${{ secrets.MATRIX_TOKEN }}" + MATRIX_DEVICE_ID: "${{ secrets.MATRIX_DEVICE_ID }}" + uses: docker://keybaseio/client:stable-node + with: + args: .github/workflows/support-files/notifications/entry_point.sh From b3b43b56bef751705897c392baf801d4676f9c34 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C4=99drzej=20Stuczy=C5=84ski?= Date: Mon, 7 Aug 2023 10:15:15 +0100 Subject: [PATCH 071/361] added --version command to explorer-api --- Cargo.lock | 1 + explorer-api/Cargo.toml | 1 + explorer-api/src/commands/mod.rs | 15 +++++++++++++-- 3 files changed, 15 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 43b8e6eff2..c2eadc94b5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1904,6 +1904,7 @@ dependencies = [ "humantime-serde", "isocountry", "itertools", + "lazy_static", "log", "maxminddb", "nym-bin-common", diff --git a/explorer-api/Cargo.toml b/explorer-api/Cargo.toml index 5e1dec9f48..ab0a56612b 100644 --- a/explorer-api/Cargo.toml +++ b/explorer-api/Cargo.toml @@ -11,6 +11,7 @@ clap = { version = "4.0", features = ["cargo", "derive"] } humantime-serde = "1.0" isocountry = "0.3.2" itertools = "0.10.3" +lazy_static = "1.4.0" log = { workspace = true } okapi = { version = "0.7.0-rc.1", features = ["impl_json_schema"] } pretty_env_logger = "0.4.0" diff --git a/explorer-api/src/commands/mod.rs b/explorer-api/src/commands/mod.rs index ee20748fd0..1e19d2efce 100644 --- a/explorer-api/src/commands/mod.rs +++ b/explorer-api/src/commands/mod.rs @@ -1,10 +1,21 @@ -// Copyright 2022 - Nym Technologies SA +// Copyright 2022-2023 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 use clap::Parser; +use lazy_static::lazy_static; +use nym_bin_common::bin_info; + +lazy_static! { + pub static ref PRETTY_BUILD_INFORMATION: String = bin_info!().pretty_print(); +} + +// Helper for passing LONG_VERSION to clap +fn pretty_build_info_static() -> &'static str { + &PRETTY_BUILD_INFORMATION +} #[derive(Parser)] -#[clap(author = "Nymtech", version, about)] +#[clap(author = "Nymtech", version, about, long_version = pretty_build_info_static())] pub(crate) struct Cli { /// Path pointing to an env file that configures the explorer api. #[clap(short, long)] From 49440bf60e752a1b30219103474055f2ff6f13cc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bogdan-=C8=98tefan=20Neac=C5=9Fu?= Date: Mon, 7 Aug 2023 12:27:31 +0300 Subject: [PATCH 072/361] Apply fix from feature/ephemera to develop too (#3698) (#3742) --- common/crypto/Cargo.toml | 4 +- .../crypto/src/asymmetric/encryption/mod.rs | 6 + common/crypto/src/asymmetric/identity/mod.rs | 2 + common/nymsphinx/acknowledgements/Cargo.toml | 2 +- common/nymsphinx/addressing/Cargo.toml | 2 +- common/nymsphinx/forwarding/Cargo.toml | 2 +- common/nymsphinx/params/Cargo.toml | 4 + common/nymsphinx/params/src/packet_sizes.rs | 43 +- common/nymsphinx/routing/Cargo.toml | 4 +- common/nymsphinx/types/Cargo.toml | 8 +- common/nymsphinx/types/src/lib.rs | 28 ++ common/topology/Cargo.toml | 4 +- common/topology/src/error.rs | 1 + contracts/Cargo.lock | 448 +----------------- nym-connect/desktop/Cargo.lock | 2 - nym-wallet/Cargo.lock | 137 +----- 16 files changed, 112 insertions(+), 585 deletions(-) diff --git a/common/crypto/Cargo.toml b/common/crypto/Cargo.toml index c6521d9056..27702da016 100644 --- a/common/crypto/Cargo.toml +++ b/common/crypto/Cargo.toml @@ -27,7 +27,7 @@ thiserror = "1.0.37" zeroize = { workspace = true, optional = true, features = ["zeroize_derive"] } # internal -nym-sphinx-types = { path = "../nymsphinx/types", version = "0.2.0" } +nym-sphinx-types = { path = "../nymsphinx/types", version = "0.2.0", default-features = false } nym-pemstore = { path = "../../common/pemstore", version = "0.3.0" } [dev-dependencies] @@ -38,3 +38,5 @@ serde = ["serde_crate", "serde_bytes", "ed25519-dalek/serde", "x25519-dalek/serd asymmetric = ["x25519-dalek", "ed25519-dalek", "zeroize"] hashing = ["blake3", "digest", "hkdf", "hmac", "generic-array"] symmetric = ["aes", "ctr", "cipher", "generic-array"] +sphinx = ["nym-sphinx-types/sphinx"] +outfox = ["nym-sphinx-types/outfox"] diff --git a/common/crypto/src/asymmetric/encryption/mod.rs b/common/crypto/src/asymmetric/encryption/mod.rs index 5052e785a0..6c45d41d60 100644 --- a/common/crypto/src/asymmetric/encryption/mod.rs +++ b/common/crypto/src/asymmetric/encryption/mod.rs @@ -273,36 +273,42 @@ impl PemStorableKey for PrivateKey { } // compatibility with sphinx keys: +#[cfg(feature = "sphinx")] impl From for nym_sphinx_types::PublicKey { fn from(key: PublicKey) -> Self { nym_sphinx_types::PublicKey::from(key.to_bytes()) } } +#[cfg(feature = "sphinx")] impl<'a> From<&'a PublicKey> for nym_sphinx_types::PublicKey { fn from(key: &'a PublicKey) -> Self { nym_sphinx_types::PublicKey::from((*key).to_bytes()) } } +#[cfg(feature = "sphinx")] impl From for PublicKey { fn from(pub_key: nym_sphinx_types::PublicKey) -> Self { Self(x25519_dalek::PublicKey::from(*pub_key.as_bytes())) } } +#[cfg(feature = "sphinx")] impl From for nym_sphinx_types::PrivateKey { fn from(key: PrivateKey) -> Self { nym_sphinx_types::PrivateKey::from(key.to_bytes()) } } +#[cfg(feature = "sphinx")] impl<'a> From<&'a PrivateKey> for nym_sphinx_types::PrivateKey { fn from(key: &'a PrivateKey) -> Self { nym_sphinx_types::PrivateKey::from(key.to_bytes()) } } +#[cfg(feature = "sphinx")] impl From for PrivateKey { fn from(private_key: nym_sphinx_types::PrivateKey) -> Self { let private_key_bytes = private_key.to_bytes(); diff --git a/common/crypto/src/asymmetric/identity/mod.rs b/common/crypto/src/asymmetric/identity/mod.rs index c552206da3..efe11acf4f 100644 --- a/common/crypto/src/asymmetric/identity/mod.rs +++ b/common/crypto/src/asymmetric/identity/mod.rs @@ -5,6 +5,7 @@ pub use ed25519_dalek::ed25519::signature::Signature as SignatureTrait; pub use ed25519_dalek::SignatureError; pub use ed25519_dalek::{Verifier, PUBLIC_KEY_LENGTH, SECRET_KEY_LENGTH, SIGNATURE_LENGTH}; use nym_pemstore::traits::{PemStorableKey, PemStorableKeyPair}; +#[cfg(feature = "sphinx")] use nym_sphinx_types::{DestinationAddressBytes, DESTINATION_ADDRESS_LENGTH}; use std::fmt::{self, Display, Formatter}; use std::str::FromStr; @@ -113,6 +114,7 @@ impl Display for PublicKey { } impl PublicKey { + #[cfg(feature = "sphinx")] pub fn derive_destination_address(&self) -> DestinationAddressBytes { let mut temporary_address = [0u8; DESTINATION_ADDRESS_LENGTH]; let public_key_bytes = self.to_bytes(); diff --git a/common/nymsphinx/acknowledgements/Cargo.toml b/common/nymsphinx/acknowledgements/Cargo.toml index 95b3d8b30c..da9977d1f3 100644 --- a/common/nymsphinx/acknowledgements/Cargo.toml +++ b/common/nymsphinx/acknowledgements/Cargo.toml @@ -19,7 +19,7 @@ nym-pemstore = { path = "../../pemstore" } nym-sphinx-addressing = { path = "../addressing" } nym-sphinx-params = { path = "../params" } nym-sphinx-routing = { path = "../routing" } -nym-sphinx-types = { path = "../types" } +nym-sphinx-types = { path = "../types", features = ["sphinx"] } nym-topology = { path = "../../topology" } [features] diff --git a/common/nymsphinx/addressing/Cargo.toml b/common/nymsphinx/addressing/Cargo.toml index f9c646e04b..c6bc2856e3 100644 --- a/common/nymsphinx/addressing/Cargo.toml +++ b/common/nymsphinx/addressing/Cargo.toml @@ -9,7 +9,7 @@ repository = { workspace = true } [dependencies] nym-crypto = { path = "../../crypto", features = ["asymmetric"] } # all addresses are expressed in terms on their crypto keys -nym-sphinx-types = { path = "../types" } # we need to be able to refer to some types defined inside sphinx crate +nym-sphinx-types = { path = "../types", features = ["sphinx"] } # we need to be able to refer to some types defined inside sphinx crate serde = "1.0" # implementing serialization/deserialization for some types, like `Recipient` thiserror = "1.0.37" diff --git a/common/nymsphinx/forwarding/Cargo.toml b/common/nymsphinx/forwarding/Cargo.toml index 6509098398..4060cbf076 100644 --- a/common/nymsphinx/forwarding/Cargo.toml +++ b/common/nymsphinx/forwarding/Cargo.toml @@ -10,6 +10,6 @@ repository = { workspace = true } [dependencies] nym-sphinx-addressing = { path = "../addressing" } nym-sphinx-params = { path = "../params" } -nym-sphinx-types = { path = "../types" } +nym-sphinx-types = { path = "../types", features = ["sphinx", "outfox"] } nym-outfox = { path = "../../../nym-outfox" } thiserror = "1" diff --git a/common/nymsphinx/params/Cargo.toml b/common/nymsphinx/params/Cargo.toml index bc9b40b9a8..57872bb0ca 100644 --- a/common/nymsphinx/params/Cargo.toml +++ b/common/nymsphinx/params/Cargo.toml @@ -13,3 +13,7 @@ serde = { workspace = true, features = ["derive"] } nym-crypto = { path = "../../crypto", features = ["hashing", "symmetric"] } nym-sphinx-types = { path = "../types" } + +[features] +sphinx = ["nym-crypto/sphinx", "nym-sphinx-types/outfox"] +outfox = ["nym-crypto/outfox", "nym-sphinx-types/outfox"] diff --git a/common/nymsphinx/params/src/packet_sizes.rs b/common/nymsphinx/params/src/packet_sizes.rs index a6613f3024..58d9681efc 100644 --- a/common/nymsphinx/params/src/packet_sizes.rs +++ b/common/nymsphinx/params/src/packet_sizes.rs @@ -1,11 +1,11 @@ // Copyright 2021-2023 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use crate::{PacketType, FRAG_ID_LEN}; -use nym_sphinx_types::header::HEADER_SIZE; -use nym_sphinx_types::{ - MIN_PACKET_SIZE, MIX_PARAMS_LEN, OUTFOX_PACKET_OVERHEAD, PAYLOAD_OVERHEAD_SIZE, -}; +use crate::PacketType; +#[cfg(feature = "sphinx")] +use nym_sphinx_types::{header::HEADER_SIZE, PAYLOAD_OVERHEAD_SIZE}; +#[cfg(feature = "outfox")] +use nym_sphinx_types::{MIN_PACKET_SIZE, MIX_PARAMS_LEN, OUTFOX_PACKET_OVERHEAD}; use serde::{Deserialize, Serialize}; use std::cmp::Ordering; use std::convert::TryFrom; @@ -14,6 +14,7 @@ use std::str::FromStr; use thiserror::Error; // each sphinx packet contains mandatory header and payload padding + markers +#[cfg(feature = "sphinx")] const SPHINX_PACKET_OVERHEAD: usize = HEADER_SIZE + PAYLOAD_OVERHEAD_SIZE; // it's up to the smart people to figure those values out : ) @@ -22,15 +23,23 @@ const SPHINX_PACKET_OVERHEAD: usize = HEADER_SIZE + PAYLOAD_OVERHEAD_SIZE; // TODO: I'm not entirely sure if we can easily extract `::NonceSize` // into a const usize before relevant stuff is stabilised in rust... +#[cfg(feature = "sphinx")] const ACK_IV_SIZE: usize = 16; -const ACK_PACKET_SIZE: usize = ACK_IV_SIZE + FRAG_ID_LEN + SPHINX_PACKET_OVERHEAD; +#[cfg(feature = "sphinx")] +const ACK_PACKET_SIZE: usize = ACK_IV_SIZE + crate::FRAG_ID_LEN + SPHINX_PACKET_OVERHEAD; +#[cfg(feature = "sphinx")] const REGULAR_PACKET_SIZE: usize = 2 * 1024 + SPHINX_PACKET_OVERHEAD; +#[cfg(feature = "sphinx")] const EXTENDED_PACKET_SIZE_8: usize = 8 * 1024 + SPHINX_PACKET_OVERHEAD; +#[cfg(feature = "sphinx")] const EXTENDED_PACKET_SIZE_16: usize = 16 * 1024 + SPHINX_PACKET_OVERHEAD; +#[cfg(feature = "sphinx")] const EXTENDED_PACKET_SIZE_32: usize = 32 * 1024 + SPHINX_PACKET_OVERHEAD; +#[cfg(feature = "outfox")] const OUTFOX_ACK_PACKET_SIZE: usize = MIN_PACKET_SIZE + OUTFOX_PACKET_OVERHEAD; +#[cfg(feature = "outfox")] const OUTFOX_REGULAR_PACKET_SIZE: usize = 2 * 1024 + OUTFOX_PACKET_OVERHEAD; #[derive(Debug, Error)] @@ -153,38 +162,55 @@ impl TryFrom for PacketSize { impl PacketSize { pub const fn size(self) -> usize { + #[allow(unreachable_patterns)] match self { + #[cfg(feature = "sphinx")] PacketSize::RegularPacket => REGULAR_PACKET_SIZE, + #[cfg(feature = "sphinx")] PacketSize::AckPacket => ACK_PACKET_SIZE, + #[cfg(feature = "sphinx")] PacketSize::ExtendedPacket8 => EXTENDED_PACKET_SIZE_8, + #[cfg(feature = "sphinx")] PacketSize::ExtendedPacket16 => EXTENDED_PACKET_SIZE_16, + #[cfg(feature = "sphinx")] PacketSize::ExtendedPacket32 => EXTENDED_PACKET_SIZE_32, + #[cfg(feature = "outfox")] PacketSize::OutfoxRegularPacket => OUTFOX_REGULAR_PACKET_SIZE, + #[cfg(feature = "outfox")] PacketSize::OutfoxAckPacket => OUTFOX_ACK_PACKET_SIZE, + _ => 0, } } pub const fn header_size(&self) -> usize { + #[allow(unreachable_patterns)] match self { + #[cfg(feature = "sphinx")] PacketSize::RegularPacket | PacketSize::AckPacket | PacketSize::ExtendedPacket8 | PacketSize::ExtendedPacket16 | PacketSize::ExtendedPacket32 => HEADER_SIZE, + #[cfg(feature = "outfox")] PacketSize::OutfoxRegularPacket | PacketSize::OutfoxAckPacket => MIX_PARAMS_LEN, + _ => 0, } } pub const fn payload_overhead(&self) -> usize { + #[allow(unreachable_patterns)] match self { + #[cfg(feature = "sphinx")] PacketSize::RegularPacket | PacketSize::AckPacket | PacketSize::ExtendedPacket8 | PacketSize::ExtendedPacket16 | PacketSize::ExtendedPacket32 => PAYLOAD_OVERHEAD_SIZE, + #[cfg(feature = "outfox")] PacketSize::OutfoxRegularPacket | PacketSize::OutfoxAckPacket => { OUTFOX_PACKET_OVERHEAD - MIX_PARAMS_LEN // Mix params are calculated into the total overhead so we take them out here } + _ => 0, } } @@ -242,11 +268,16 @@ impl PacketSize { plaintext_size: usize, packet_type: PacketType, ) -> Result { + #[allow(unreachable_patterns)] let overhead = match packet_type { + #[cfg(feature = "sphinx")] PacketType::Mix => SPHINX_PACKET_OVERHEAD, #[allow(deprecated)] + #[cfg(feature = "sphinx")] PacketType::Vpn => SPHINX_PACKET_OVERHEAD, + #[cfg(feature = "outfox")] PacketType::Outfox => OUTFOX_PACKET_OVERHEAD, + _ => 0, }; let packet_size = plaintext_size + overhead; Self::get_type(packet_size) diff --git a/common/nymsphinx/routing/Cargo.toml b/common/nymsphinx/routing/Cargo.toml index c0f7cf7628..37b8d5a4dc 100644 --- a/common/nymsphinx/routing/Cargo.toml +++ b/common/nymsphinx/routing/Cargo.toml @@ -11,4 +11,6 @@ repository = { workspace = true } thiserror = { workspace = true } nym-sphinx-addressing = { path = "../addressing" } -nym-sphinx-types = { path = "../types" } \ No newline at end of file +nym-sphinx-types = { path = "../types", features = ["sphinx"] } + +[features] \ No newline at end of file diff --git a/common/nymsphinx/types/Cargo.toml b/common/nymsphinx/types/Cargo.toml index 2c52ef03d9..0569ef1406 100644 --- a/common/nymsphinx/types/Cargo.toml +++ b/common/nymsphinx/types/Cargo.toml @@ -8,6 +8,10 @@ license = { workspace = true } repository = { workspace = true } [dependencies] -sphinx-packet = { version = "0.1.0" } -nym-outfox = { path = "../../../nym-outfox" } +sphinx-packet = { version = "0.1.0", optional = true } +nym-outfox = { path = "../../../nym-outfox", optional = true } thiserror = "1" + +[features] +sphinx = ["sphinx-packet"] +outfox = ["nym-outfox"] diff --git a/common/nymsphinx/types/src/lib.rs b/common/nymsphinx/types/src/lib.rs index bd3063f939..d75060e9b0 100644 --- a/common/nymsphinx/types/src/lib.rs +++ b/common/nymsphinx/types/src/lib.rs @@ -1,12 +1,15 @@ // Copyright 2021 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 +#[cfg(feature = "outfox")] pub use nym_outfox::{ constants::MIN_PACKET_SIZE, constants::MIX_PARAMS_LEN, constants::OUTFOX_PACKET_OVERHEAD, error::OutfoxError, }; // re-exporting types and constants available in sphinx +#[cfg(feature = "outfox")] use nym_outfox::packet::{OutfoxPacket, OutfoxProcessedPacket}; +#[cfg(feature = "sphinx")] pub use sphinx_packet::{ constants::{ self, DESTINATION_ADDRESS_LENGTH, IDENTIFIER_LENGTH, MAX_PATH_LENGTH, NODE_ADDRESS_LENGTH, @@ -20,6 +23,7 @@ pub use sphinx_packet::{ surb::{SURBMaterial, SURB}, Error as SphinxError, ProcessedPacket, }; +#[cfg(feature = "sphinx")] use sphinx_packet::{SphinxPacket, SphinxPacketBuilder}; use std::{array::TryFromSliceError, fmt}; use thiserror::Error; @@ -27,9 +31,11 @@ use thiserror::Error; #[derive(Error, Debug)] pub enum NymPacketError { #[error("Sphinx error: {0}")] + #[cfg(feature = "sphinx")] Sphinx(#[from] sphinx_packet::Error), #[error("Outfox error: {0}")] + #[cfg(feature = "outfox")] Outfox(#[from] nym_outfox::error::OutfoxError), #[error("{0}")] @@ -38,31 +44,40 @@ pub enum NymPacketError { #[allow(clippy::large_enum_variant)] pub enum NymPacket { + #[cfg(feature = "sphinx")] Sphinx(SphinxPacket), + #[cfg(feature = "outfox")] Outfox(OutfoxPacket), } pub enum NymProcessedPacket { + #[cfg(feature = "sphinx")] Sphinx(ProcessedPacket), + #[cfg(feature = "outfox")] Outfox(OutfoxProcessedPacket), } impl fmt::Debug for NymPacket { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + #[allow(unreachable_patterns)] match &self { + #[cfg(feature = "sphinx")] NymPacket::Sphinx(packet) => f .debug_struct("NymPacket::Sphinx") .field("len", &packet.len()) .finish(), + #[cfg(feature = "outfox")] NymPacket::Outfox(packet) => f .debug_struct("NymPacket::Outfox") .field("len", &packet.len()) .finish(), + _ => write!(f, ""), } } } impl NymPacket { + #[cfg(feature = "sphinx")] pub fn sphinx_build>( size: usize, message: M, @@ -76,10 +91,12 @@ impl NymPacket { .build_packet(message, route, destination, delays)?, )) } + #[cfg(feature = "sphinx")] pub fn sphinx_from_bytes(bytes: &[u8]) -> Result { Ok(NymPacket::Sphinx(SphinxPacket::from_bytes(bytes)?)) } + #[cfg(feature = "outfox")] pub fn outfox_build>( payload: M, route: &[Node], @@ -94,14 +111,19 @@ impl NymPacket { )?)) } + #[cfg(feature = "outfox")] pub fn outfox_from_bytes(bytes: &[u8]) -> Result { Ok(NymPacket::Outfox(OutfoxPacket::try_from(bytes)?)) } pub fn len(&self) -> usize { + #[allow(unreachable_patterns)] match self { + #[cfg(feature = "sphinx")] NymPacket::Sphinx(packet) => packet.len(), + #[cfg(feature = "outfox")] NymPacket::Outfox(packet) => packet.len(), + _ => 0, } } @@ -110,12 +132,17 @@ impl NymPacket { } pub fn to_bytes(&self) -> Result, NymPacketError> { + #[allow(unreachable_patterns)] match self { + #[cfg(feature = "sphinx")] NymPacket::Sphinx(packet) => Ok(packet.to_bytes()), + #[cfg(feature = "outfox")] NymPacket::Outfox(packet) => Ok(packet.to_bytes()?), + _ => Ok(vec![]), } } + #[cfg(feature = "sphinx")] pub fn process( self, node_secret_key: &PrivateKey, @@ -124,6 +151,7 @@ impl NymPacket { NymPacket::Sphinx(packet) => { Ok(NymProcessedPacket::Sphinx(packet.process(node_secret_key)?)) } + #[cfg(feature = "outfox")] NymPacket::Outfox(mut packet) => { let next_address = packet.decode_next_layer(node_secret_key)?; Ok(NymProcessedPacket::Outfox(OutfoxProcessedPacket::new( diff --git a/common/topology/Cargo.toml b/common/topology/Cargo.toml index 1d41a93ee4..bfe2c98573 100644 --- a/common/topology/Cargo.toml +++ b/common/topology/Cargo.toml @@ -19,10 +19,10 @@ thiserror = "1.0.37" async-trait = { workspace = true, optional = true } ## internal -nym-crypto = { path = "../crypto" } +nym-crypto = { path = "../crypto", features = ["sphinx", "outfox"] } nym-mixnet-contract-common = { path = "../cosmwasm-smart-contracts/mixnet-contract" } nym-sphinx-addressing = { path = "../nymsphinx/addressing" } -nym-sphinx-types = { path = "../nymsphinx/types" } +nym-sphinx-types = { path = "../nymsphinx/types", features = ["sphinx", "outfox"] } nym-sphinx-routing = { path = "../nymsphinx/routing" } nym-bin-common = { path = "../bin-common" } diff --git a/common/topology/src/error.rs b/common/topology/src/error.rs index bb36b42886..3f2523576f 100644 --- a/common/topology/src/error.rs +++ b/common/topology/src/error.rs @@ -41,6 +41,7 @@ pub enum NymTopologyError { PayloadBuilder, #[error("Outfox: {0}")] + #[cfg(feature = "outfox")] Outfox(#[from] nym_sphinx_types::OutfoxError), #[error("{0}")] diff --git a/contracts/Cargo.lock b/contracts/Cargo.lock index 0c3efd0a24..3d719dd6dc 100644 --- a/contracts/Cargo.lock +++ b/contracts/Cargo.lock @@ -2,29 +2,6 @@ # It is not intended for manual editing. version = 3 -[[package]] -name = "aead" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0" -dependencies = [ - "crypto-common", - "generic-array 0.14.6", -] - -[[package]] -name = "aes" -version = "0.7.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e8b47f52ea9bae42228d07ec09eb676433d7c4ed1ebdf0f1d1c29ed446f1ab8" -dependencies = [ - "cfg-if", - "cipher 0.3.0", - "cpufeatures", - "ctr", - "opaque-debug 0.3.0", -] - [[package]] name = "ahash" version = "0.7.6" @@ -42,18 +19,6 @@ version = "1.0.70" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7de8ce5e0f9f8d88245311066a578d72b7af3e7088f32783804676302df237e4" -[[package]] -name = "arrayref" -version = "0.3.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b4930d2cb77ce62f89ee5d5289b4ac049559b1c45539271f5ed4fdc7db34545" - -[[package]] -name = "arrayvec" -version = "0.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711" - [[package]] name = "autocfg" version = "1.1.0" @@ -90,39 +55,13 @@ version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" -[[package]] -name = "blake2" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94cb07b0da6a73955f8fb85d24c466778e70cda767a568229b104f0264089330" -dependencies = [ - "byte-tools", - "crypto-mac 0.7.0", - "digest 0.8.1", - "opaque-debug 0.2.3", -] - -[[package]] -name = "blake3" -version = "1.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "199c42ab6972d92c9f8995f086273d25c42fc0f7b2a1fcefba465c1352d25ba5" -dependencies = [ - "arrayref", - "arrayvec", - "cc", - "cfg-if", - "constant_time_eq", - "digest 0.10.7", -] - [[package]] name = "block-buffer" version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" dependencies = [ - "generic-array 0.14.6", + "generic-array", ] [[package]] @@ -131,7 +70,7 @@ version = "0.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" dependencies = [ - "generic-array 0.14.6", + "generic-array", ] [[package]] @@ -146,12 +85,6 @@ version = "3.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b1ce199063694f33ffb7dd4e0ee620741495c32833cde5aa08f02a0bf96f0c8" -[[package]] -name = "byte-tools" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3b5ca7a04898ad4bcd41c90c5285445ff5b791899bb1b0abdd2a2aa791211d7" - [[package]] name = "byteorder" version = "1.4.3" @@ -179,60 +112,6 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" -[[package]] -name = "chacha" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ddf3c081b5fba1e5615640aae998e0fbd10c24cbd897ee39ed754a77601a4862" -dependencies = [ - "byteorder", - "keystream", -] - -[[package]] -name = "chacha20" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3613f74bd2eac03dad61bd53dbe620703d4371614fe0bc3b9f04dd36fe4e818" -dependencies = [ - "cfg-if", - "cipher 0.4.4", - "cpufeatures", -] - -[[package]] -name = "chacha20poly1305" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10cd79432192d1c0f4e1a0fef9527696cc039165d729fb41b3f4f4f354c2dc35" -dependencies = [ - "aead", - "chacha20", - "cipher 0.4.4", - "poly1305", - "zeroize", -] - -[[package]] -name = "cipher" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ee52072ec15386f770805afd189a01c8841be8696bed250fa2f13c4c0d6dfb7" -dependencies = [ - "generic-array 0.14.6", -] - -[[package]] -name = "cipher" -version = "0.4.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" -dependencies = [ - "crypto-common", - "inout", - "zeroize", -] - [[package]] name = "coconut-test" version = "0.1.0" @@ -264,12 +143,6 @@ version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "520fbf3c07483f94e3e3ca9d0cfd913d7718ef2483d2cfd91c0d9e91474ab913" -[[package]] -name = "constant_time_eq" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7144d30dcf0fafbce74250a3963025d8d52177934239851c917d29f1df280c2" - [[package]] name = "cosmwasm-crypto" version = "1.2.5" @@ -355,49 +228,6 @@ dependencies = [ "libc", ] -[[package]] -name = "crossbeam-channel" -version = "0.5.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a33c2bf77f2df06183c3aa30d1e96c0695a313d4f9c453cc3762a6db39f99200" -dependencies = [ - "cfg-if", - "crossbeam-utils", -] - -[[package]] -name = "crossbeam-deque" -version = "0.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce6fd6f855243022dcecf8702fef0c297d4338e226845fe067f6341ad9fa0cef" -dependencies = [ - "cfg-if", - "crossbeam-epoch", - "crossbeam-utils", -] - -[[package]] -name = "crossbeam-epoch" -version = "0.9.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae211234986c545741a7dc064309f67ee1e5ad243d0e48335adc0484d960bcc7" -dependencies = [ - "autocfg", - "cfg-if", - "crossbeam-utils", - "memoffset", - "scopeguard", -] - -[[package]] -name = "crossbeam-utils" -version = "0.8.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a22b2d63d4d1dc0b7f1b6b2747dd0088008a9be28b6ddf0b1e7d335e3037294" -dependencies = [ - "cfg-if", -] - [[package]] name = "crunchy" version = "0.2.2" @@ -410,9 +240,9 @@ version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ef2b4b23cddf68b89b8f8069890e8c270d54e2d5fe1b143820234805e4cb17ef" dependencies = [ - "generic-array 0.14.6", + "generic-array", "rand_core 0.6.4", - "subtle 2.4.1", + "subtle", "zeroize", ] @@ -422,40 +252,10 @@ version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" dependencies = [ - "generic-array 0.14.6", - "rand_core 0.6.4", + "generic-array", "typenum", ] -[[package]] -name = "crypto-mac" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4434400df11d95d556bac068ddfedd482915eb18fe8bea89bc80b6e4b1c179e5" -dependencies = [ - "generic-array 0.12.4", - "subtle 1.0.0", -] - -[[package]] -name = "crypto-mac" -version = "0.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1d1a86f49236c215f271d40892d5fc950490551400b02ef360692c29815c714" -dependencies = [ - "generic-array 0.14.6", - "subtle 2.4.1", -] - -[[package]] -name = "ctr" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "049bb91fb4aaf0e3c7efa6cd5ef877dbbbd15b39dad06d9948de4ec8a75761ea" -dependencies = [ - "cipher 0.3.0", -] - [[package]] name = "curve25519-dalek" version = "3.2.0" @@ -465,7 +265,7 @@ dependencies = [ "byteorder", "digest 0.9.0", "rand_core 0.5.1", - "subtle 2.4.1", + "subtle", "zeroize", ] @@ -676,22 +476,13 @@ dependencies = [ "syn 1.0.109", ] -[[package]] -name = "digest" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3d0c8c8752312f9713efd397ff63acb9f85585afbf179282e720e7704954dd5" -dependencies = [ - "generic-array 0.12.4", -] - [[package]] name = "digest" version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" dependencies = [ - "generic-array 0.14.6", + "generic-array", ] [[package]] @@ -702,7 +493,7 @@ checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ "block-buffer 0.10.4", "crypto-common", - "subtle 2.4.1", + "subtle", ] [[package]] @@ -778,12 +569,12 @@ dependencies = [ "der", "digest 0.10.7", "ff", - "generic-array 0.14.6", + "generic-array", "group", "pkcs8", "rand_core 0.6.4", "sec1", - "subtle 2.4.1", + "subtle", "zeroize", ] @@ -844,7 +635,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d013fc25338cc558c5c2cfbad646908fb23591e2404481826742b651c9af7160" dependencies = [ "rand_core 0.6.4", - "subtle 2.4.1", + "subtle", ] [[package]] @@ -963,15 +754,6 @@ dependencies = [ "slab", ] -[[package]] -name = "generic-array" -version = "0.12.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffdf9f34f1447443d37393cc6c2b8313aebddcd96906caf34e54c68d8e57d7bd" -dependencies = [ - "typenum", -] - [[package]] name = "generic-array" version = "0.14.6" @@ -1002,10 +784,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "be4136b2a15dd319360be1c07d9933517ccf0be8f16bf62a3bee4f0d618df427" dependencies = [ "cfg-if", - "js-sys", "libc", "wasi 0.11.0+wasi-snapshot-preview1", - "wasm-bindgen", ] [[package]] @@ -1041,7 +821,7 @@ checksum = "5dfbfb3a6cfbd390d5c9564ab283a0349b9b9fcd46a706c1eb10e0db70bfbac7" dependencies = [ "ff", "rand_core 0.6.4", - "subtle 2.4.1", + "subtle", ] [[package]] @@ -1065,26 +845,6 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" -[[package]] -name = "hkdf" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01706d578d5c281058480e673ae4086a9f4710d8df1ad80a5b03e39ece5f886b" -dependencies = [ - "digest 0.9.0", - "hmac 0.11.0", -] - -[[package]] -name = "hmac" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a2a2320eb7ec0ebe8da8f744d7812d9fc4cb4d09344ac01898dbcb6a20ae69b" -dependencies = [ - "crypto-mac 0.11.1", - "digest 0.9.0", -] - [[package]] name = "hmac" version = "0.12.1" @@ -1120,15 +880,6 @@ dependencies = [ "unicode-normalization", ] -[[package]] -name = "inout" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5" -dependencies = [ - "generic-array 0.14.6", -] - [[package]] name = "instant" version = "0.1.12" @@ -1194,12 +945,6 @@ dependencies = [ "sha2 0.10.6", ] -[[package]] -name = "keystream" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c33070833c9ee02266356de0c43f723152bd38bd96ddf52c82b3af10c9138b28" - [[package]] name = "lazy_static" version = "1.4.0" @@ -1224,12 +969,6 @@ dependencies = [ "pkg-config", ] -[[package]] -name = "libm" -version = "0.2.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "348108ab3fba42ec82ff6e9564fc4ca0247bdccdc68dd8af9764bbc79c3c8ffb" - [[package]] name = "libz-sys" version = "1.1.8" @@ -1248,18 +987,6 @@ version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ece97ea872ece730aed82664c424eb4c8291e1ff2480247ccf7409044bc6479f" -[[package]] -name = "lioness" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ae926706ba42c425c9457121178330d75e273df2e82e28b758faf3de3a9acb9" -dependencies = [ - "arrayref", - "blake2", - "chacha", - "keystream", -] - [[package]] name = "log" version = "0.4.17" @@ -1275,15 +1002,6 @@ version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" -[[package]] -name = "memoffset" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a634b1c61a95585bd15607c6ab0c4e5b226e695ff2800ba0cdccddf208c406c" -dependencies = [ - "autocfg", -] - [[package]] name = "mixnet-vesting-integration-tests" version = "0.1.0" @@ -1300,26 +1018,6 @@ dependencies = [ "rand_chacha 0.2.2", ] -[[package]] -name = "num-traits" -version = "0.2.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" -dependencies = [ - "autocfg", - "libm", -] - -[[package]] -name = "num_cpus" -version = "1.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" -dependencies = [ - "hermit-abi", - "libc", -] - [[package]] name = "nym-coconut-bandwidth" version = "0.1.0" @@ -1507,23 +1205,6 @@ dependencies = [ "thiserror", ] -[[package]] -name = "nym-outfox" -version = "0.1.0" -dependencies = [ - "blake3", - "chacha20", - "chacha20poly1305", - "curve25519-dalek", - "getrandom 0.2.10", - "log", - "rand 0.7.3", - "rayon", - "sphinx-packet", - "thiserror", - "zeroize", -] - [[package]] name = "nym-pemstore" version = "0.3.0" @@ -1571,8 +1252,6 @@ dependencies = [ name = "nym-sphinx-types" version = "0.2.0" dependencies = [ - "nym-outfox", - "sphinx-packet", "thiserror", ] @@ -1616,12 +1295,6 @@ version = "1.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3" -[[package]] -name = "opaque-debug" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2839e79665f131bdb5782e51f2c6c9599c133c6098982a54c794358bf432529c" - [[package]] name = "opaque-debug" version = "0.3.0" @@ -1673,17 +1346,6 @@ version = "0.3.26" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ac9a59f73473f1b8d852421e59e64809f025994837ef743615c6d0c5b305160" -[[package]] -name = "poly1305" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8159bd90725d2df49889a078b54f4f79e87f1f8a8444194cdca81d38f5393abf" -dependencies = [ - "cpufeatures", - "opaque-debug 0.3.0", - "universal-hash", -] - [[package]] name = "ppv-lite86" version = "0.2.17" @@ -1823,16 +1485,6 @@ dependencies = [ "getrandom 0.2.10", ] -[[package]] -name = "rand_distr" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9e9532ada3929fb8b2e9dbe28d1e06c9b2cc65813f074fcb6bd5fbefeff9d56" -dependencies = [ - "num-traits", - "rand 0.7.3", -] - [[package]] name = "rand_hc" version = "0.2.0" @@ -1842,28 +1494,6 @@ dependencies = [ "rand_core 0.5.1", ] -[[package]] -name = "rayon" -version = "1.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d2df5196e37bcc87abebc0053e20787d73847bb33134a69841207dd0a47f03b" -dependencies = [ - "either", - "rayon-core", -] - -[[package]] -name = "rayon-core" -version = "1.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b8f95bd6966f5c87776639160a66bd8ab9895d9d4ab01ddba9fc60661aebe8d" -dependencies = [ - "crossbeam-channel", - "crossbeam-deque", - "crossbeam-utils", - "num_cpus", -] - [[package]] name = "redox_syscall" version = "0.3.5" @@ -1895,7 +1525,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7743f17af12fa0b03b803ba12cd6a8d9483a587e89c69445e3909655c0b9fabb" dependencies = [ "crypto-bigint", - "hmac 0.12.1", + "hmac", "zeroize", ] @@ -1996,12 +1626,6 @@ dependencies = [ "syn 1.0.109", ] -[[package]] -name = "scopeguard" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" - [[package]] name = "sec1" version = "0.3.0" @@ -2010,9 +1634,9 @@ checksum = "3be24c1842290c45df0a7bf069e0c268a747ad05a192f2fd7dcfdbc1cba40928" dependencies = [ "base16ct", "der", - "generic-array 0.14.6", + "generic-array", "pkcs8", - "subtle 2.4.1", + "subtle", "zeroize", ] @@ -2103,7 +1727,7 @@ dependencies = [ "cfg-if", "cpufeatures", "digest 0.9.0", - "opaque-debug 0.3.0", + "opaque-debug", ] [[package]] @@ -2136,30 +1760,6 @@ dependencies = [ "autocfg", ] -[[package]] -name = "sphinx-packet" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc43eda802856ee82a7555c7b75ceb9e07451741c7a2f5f23d036020e01189d4" -dependencies = [ - "aes", - "arrayref", - "blake2", - "bs58", - "byteorder", - "chacha", - "curve25519-dalek", - "digest 0.9.0", - "hkdf", - "hmac 0.11.0", - "lioness", - "log", - "rand 0.7.3", - "rand_distr", - "sha2 0.9.9", - "subtle 2.4.1", -] - [[package]] name = "spki" version = "0.6.0" @@ -2176,12 +1776,6 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" -[[package]] -name = "subtle" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d67a5a62ba6e01cb2192ff309324cb4875d0c451d55fe2319433abe7a05a8ee" - [[package]] name = "subtle" version = "2.4.1" @@ -2333,16 +1927,6 @@ dependencies = [ "tinyvec", ] -[[package]] -name = "universal-hash" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea" -dependencies = [ - "crypto-common", - "subtle 2.4.1", -] - [[package]] name = "url" version = "2.3.1" diff --git a/nym-connect/desktop/Cargo.lock b/nym-connect/desktop/Cargo.lock index 3f657d5852..fc3dba557f 100644 --- a/nym-connect/desktop/Cargo.lock +++ b/nym-connect/desktop/Cargo.lock @@ -4186,8 +4186,6 @@ dependencies = [ name = "nym-sphinx-types" version = "0.2.0" dependencies = [ - "nym-outfox", - "sphinx-packet", "thiserror", ] diff --git a/nym-wallet/Cargo.lock b/nym-wallet/Cargo.lock index 2da872c4fc..5768438108 100644 --- a/nym-wallet/Cargo.lock +++ b/nym-wallet/Cargo.lock @@ -120,12 +120,6 @@ version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a4c527152e37cf757a3f78aae5a06fbeefdb07ccc535c980a3208ee3060dd544" -[[package]] -name = "arrayvec" -version = "0.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711" - [[package]] name = "async-trait" version = "0.1.64" @@ -301,20 +295,6 @@ dependencies = [ "digest 0.10.7", ] -[[package]] -name = "blake3" -version = "1.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "199c42ab6972d92c9f8995f086273d25c42fc0f7b2a1fcefba465c1352d25ba5" -dependencies = [ - "arrayref", - "arrayvec", - "cc", - "cfg-if", - "constant_time_eq", - "digest 0.10.7", -] - [[package]] name = "block" version = "0.1.6" @@ -525,30 +505,6 @@ dependencies = [ "keystream", ] -[[package]] -name = "chacha20" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3613f74bd2eac03dad61bd53dbe620703d4371614fe0bc3b9f04dd36fe4e818" -dependencies = [ - "cfg-if", - "cipher 0.4.4", - "cpufeatures", -] - -[[package]] -name = "chacha20poly1305" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10cd79432192d1c0f4e1a0fef9527696cc039165d729fb41b3f4f4f354c2dc35" -dependencies = [ - "aead", - "chacha20", - "cipher 0.4.4", - "poly1305", - "zeroize", -] - [[package]] name = "cipher" version = "0.3.0" @@ -566,7 +522,6 @@ checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" dependencies = [ "crypto-common", "inout", - "zeroize", ] [[package]] @@ -700,12 +655,6 @@ version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "520fbf3c07483f94e3e3ca9d0cfd913d7718ef2483d2cfd91c0d9e91474ab913" -[[package]] -name = "constant_time_eq" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7144d30dcf0fafbce74250a3963025d8d52177934239851c917d29f1df280c2" - [[package]] name = "convert_case" version = "0.4.0" @@ -879,30 +828,6 @@ dependencies = [ "crossbeam-utils", ] -[[package]] -name = "crossbeam-deque" -version = "0.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce6fd6f855243022dcecf8702fef0c297d4338e226845fe067f6341ad9fa0cef" -dependencies = [ - "cfg-if", - "crossbeam-epoch", - "crossbeam-utils", -] - -[[package]] -name = "crossbeam-epoch" -version = "0.9.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae211234986c545741a7dc064309f67ee1e5ad243d0e48335adc0484d960bcc7" -dependencies = [ - "autocfg", - "cfg-if", - "crossbeam-utils", - "memoffset 0.9.0", - "scopeguard", -] - [[package]] name = "crossbeam-utils" version = "0.8.14" @@ -1640,7 +1565,7 @@ version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e1c54951450cbd39f3dbcf1005ac413b49487dabf18a720ad2383eccfeffb92" dependencies = [ - "memoffset 0.6.5", + "memoffset", "rustc_version 0.3.3", ] @@ -2915,15 +2840,6 @@ dependencies = [ "autocfg", ] -[[package]] -name = "memoffset" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a634b1c61a95585bd15607c6ab0c4e5b226e695ff2800ba0cdccddf208c406c" -dependencies = [ - "autocfg", -] - [[package]] name = "mime" version = "0.3.16" @@ -3328,23 +3244,6 @@ dependencies = [ "url", ] -[[package]] -name = "nym-outfox" -version = "0.1.0" -dependencies = [ - "blake3", - "chacha20", - "chacha20poly1305", - "curve25519-dalek", - "getrandom 0.2.10", - "log", - "rand 0.7.3", - "rayon", - "sphinx-packet", - "thiserror", - "zeroize", -] - [[package]] name = "nym-pemstore" version = "0.3.0" @@ -3369,7 +3268,6 @@ dependencies = [ name = "nym-sphinx-types" version = "0.2.0" dependencies = [ - "nym-outfox", "sphinx-packet", "thiserror", ] @@ -4074,17 +3972,6 @@ dependencies = [ "miniz_oxide", ] -[[package]] -name = "poly1305" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8159bd90725d2df49889a078b54f4f79e87f1f8a8444194cdca81d38f5393abf" -dependencies = [ - "cpufeatures", - "opaque-debug 0.3.0", - "universal-hash", -] - [[package]] name = "polyval" version = "0.6.0" @@ -4336,28 +4223,6 @@ dependencies = [ "cty", ] -[[package]] -name = "rayon" -version = "1.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d2df5196e37bcc87abebc0053e20787d73847bb33134a69841207dd0a47f03b" -dependencies = [ - "either", - "rayon-core", -] - -[[package]] -name = "rayon-core" -version = "1.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b8f95bd6966f5c87776639160a66bd8ab9895d9d4ab01ddba9fc60661aebe8d" -dependencies = [ - "crossbeam-channel", - "crossbeam-deque", - "crossbeam-utils", - "num_cpus", -] - [[package]] name = "redox_syscall" version = "0.2.16" From 7ad2865250e8216754c6af0749207b2e6aa6775b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bogdan-=C8=98tefan=20Neac=C5=9Fu?= Date: Mon, 7 Aug 2023 12:32:20 +0300 Subject: [PATCH 073/361] Add empty migration to group contract (#3740) --- .../group-contract/src/msg.rs | 2 + .../multisig/cw4-group/schema/cw4-group.json | 7 +++- .../cw4-group/schema/raw/migrate.json | 6 +++ .../multisig/cw4-group/src/bin/schema.rs | 3 +- contracts/multisig/cw4-group/src/contract.rs | 7 +++- nym-connect/desktop/Cargo.lock | 39 ++++++++----------- 6 files changed, 39 insertions(+), 25 deletions(-) create mode 100644 contracts/multisig/cw4-group/schema/raw/migrate.json diff --git a/common/cosmwasm-smart-contracts/group-contract/src/msg.rs b/common/cosmwasm-smart-contracts/group-contract/src/msg.rs index 3f95227492..84674946b1 100644 --- a/common/cosmwasm-smart-contracts/group-contract/src/msg.rs +++ b/common/cosmwasm-smart-contracts/group-contract/src/msg.rs @@ -46,3 +46,5 @@ pub enum QueryMsg { #[returns(cw_controllers::HooksResponse)] Hooks {}, } +#[cw_serde] +pub struct MigrateMsg {} diff --git a/contracts/multisig/cw4-group/schema/cw4-group.json b/contracts/multisig/cw4-group/schema/cw4-group.json index 5e0f5af99b..b6c6f5df44 100644 --- a/contracts/multisig/cw4-group/schema/cw4-group.json +++ b/contracts/multisig/cw4-group/schema/cw4-group.json @@ -286,7 +286,12 @@ } ] }, - "migrate": null, + "migrate": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "MigrateMsg", + "type": "object", + "additionalProperties": false + }, "sudo": null, "responses": { "admin": { diff --git a/contracts/multisig/cw4-group/schema/raw/migrate.json b/contracts/multisig/cw4-group/schema/raw/migrate.json new file mode 100644 index 0000000000..7fbe8c5708 --- /dev/null +++ b/contracts/multisig/cw4-group/schema/raw/migrate.json @@ -0,0 +1,6 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "MigrateMsg", + "type": "object", + "additionalProperties": false +} diff --git a/contracts/multisig/cw4-group/src/bin/schema.rs b/contracts/multisig/cw4-group/src/bin/schema.rs index a46ff6cf2e..7340a28e6a 100644 --- a/contracts/multisig/cw4-group/src/bin/schema.rs +++ b/contracts/multisig/cw4-group/src/bin/schema.rs @@ -2,12 +2,13 @@ // SPDX-License-Identifier: Apache-2.0 use cosmwasm_schema::write_api; -use nym_group_contract_common::msg::{ExecuteMsg, InstantiateMsg, QueryMsg}; +use nym_group_contract_common::msg::{ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg}; fn main() { write_api! { instantiate: InstantiateMsg, query: QueryMsg, execute: ExecuteMsg, + migrate: MigrateMsg, } } diff --git a/contracts/multisig/cw4-group/src/contract.rs b/contracts/multisig/cw4-group/src/contract.rs index a6069f3b23..c5c039658c 100644 --- a/contracts/multisig/cw4-group/src/contract.rs +++ b/contracts/multisig/cw4-group/src/contract.rs @@ -15,7 +15,7 @@ use cw_utils::maybe_addr; use crate::error::ContractError; use crate::helpers::validate_unique_members; use crate::state::{ADMIN, HOOKS, MEMBERS, TOTAL}; -use nym_group_contract_common::msg::{ExecuteMsg, InstantiateMsg, QueryMsg}; +use nym_group_contract_common::msg::{ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg}; // version info for migration info const CONTRACT_NAME: &str = "crates.io:cw4-group"; @@ -219,3 +219,8 @@ pub fn query_list_members( Ok(MemberListResponse { members }) } + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn migrate(_deps: DepsMut<'_>, _env: Env, _msg: MigrateMsg) -> Result { + Ok(Default::default()) +} diff --git a/nym-connect/desktop/Cargo.lock b/nym-connect/desktop/Cargo.lock index c1acf4ee4e..5897f51672 100644 --- a/nym-connect/desktop/Cargo.lock +++ b/nym-connect/desktop/Cargo.lock @@ -2868,9 +2868,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" dependencies = [ "cfg-if", - "js-sys", - "wasm-bindgen", - "web-sys", ] [[package]] @@ -3586,8 +3583,8 @@ dependencies = [ "url", "wasm-bindgen", "wasm-bindgen-futures", - "wasm-timer", "wasm-utils", + "wasmtimer", "zeroize", ] @@ -3810,8 +3807,8 @@ dependencies = [ "url", "wasm-bindgen", "wasm-bindgen-futures", - "wasm-timer", "wasm-utils", + "wasmtimer", ] [[package]] @@ -3909,12 +3906,10 @@ dependencies = [ name = "nym-nonexhaustive-delayqueue" version = "0.1.0" dependencies = [ - "futures-core", - "slab", "tokio", "tokio-stream", "tokio-util", - "wasm-timer", + "wasmtimer", ] [[package]] @@ -7423,20 +7418,6 @@ version = "0.2.83" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1c38c045535d93ec4f0b4defec448e4291638ee608530863b1e2ba115d4fff7f" -[[package]] -name = "wasm-timer" -version = "0.2.5" -source = "git+https://github.com/mmsinclair/wasm-timer?rev=b9d1a54ad514c2f230a026afe0dde341e98cd7b6#b9d1a54ad514c2f230a026afe0dde341e98cd7b6" -dependencies = [ - "futures", - "js-sys", - "parking_lot 0.11.2", - "pin-utils", - "wasm-bindgen", - "wasm-bindgen-futures", - "web-sys", -] - [[package]] name = "wasm-utils" version = "0.1.0" @@ -7450,6 +7431,20 @@ dependencies = [ "web-sys", ] +[[package]] +name = "wasmtimer" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f656cd8858a5164932d8a90f936700860976ec21eb00e0fe2aa8cab13f6b4cf" +dependencies = [ + "futures", + "js-sys", + "parking_lot 0.12.1", + "pin-utils", + "slab", + "wasm-bindgen", +] + [[package]] name = "web-sys" version = "0.3.60" From a67092ad172058437eb4bda328f2d78eda4a62c8 Mon Sep 17 00:00:00 2001 From: serinko <97586125+serinko@users.noreply.github.com> Date: Mon, 7 Aug 2023 12:08:18 +0200 Subject: [PATCH 074/361] correct go installation - guide finished --- .../operators/src/nodes/validator-setup.md | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/documentation/operators/src/nodes/validator-setup.md b/documentation/operators/src/nodes/validator-setup.md index 9e233808d7..4726b4c11c 100644 --- a/documentation/operators/src/nodes/validator-setup.md +++ b/documentation/operators/src/nodes/validator-setup.md @@ -41,19 +41,6 @@ rm -rf /usr/local/go && tar -C /usr/local -xzf go1.20.6.linux-amd64.tar.gz export PATH=$PATH:/usr/local/go/bin source $HOME/.profile ``` - Verify `Go` is installed with: From d44b88842e579268e8ebebe3821539db52a9d338 Mon Sep 17 00:00:00 2001 From: serinko <97586125+serinko@users.noreply.github.com> Date: Mon, 7 Aug 2023 12:19:34 +0200 Subject: [PATCH 075/361] scp automation tested - doc finished --- documentation/operators/src/nodes/maintenance.md | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/documentation/operators/src/nodes/maintenance.md b/documentation/operators/src/nodes/maintenance.md index 05723461f2..a9a90ccaa6 100644 --- a/documentation/operators/src/nodes/maintenance.md +++ b/documentation/operators/src/nodes/maintenance.md @@ -1,15 +1,5 @@ # Maintenance - ## Useful commands > Adding `--no-banner` startup flag will prevent Nym banner being printed even if run in tty environment. From 913095ee6061a4576ff2c0e1fde961752d3105d6 Mon Sep 17 00:00:00 2001 From: serinko <97586125+serinko@users.noreply.github.com> Date: Mon, 7 Aug 2023 12:36:19 +0200 Subject: [PATCH 076/361] old typos edits --- documentation/docs/src/sdk/rust.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/documentation/docs/src/sdk/rust.md b/documentation/docs/src/sdk/rust.md index 1408d85faa..e82cfc7b99 100644 --- a/documentation/docs/src/sdk/rust.md +++ b/documentation/docs/src/sdk/rust.md @@ -66,12 +66,12 @@ You can read more about how SURBs function under the hood [here](../architecture ### Importing and using a custom network topology -If you want to send traffic through a sub-set of nodes (for instance, ones you control, or a small test setup) when developing, debugging, or peforming research, you will need to import these nodes as a custom network topology, instead of grabbing it from the [`Mainnet Nym-API`](https://validator.nymtech.net/api/swagger/index.html) (`examples/custom_topology_provider.rs`). +If you want to send traffic through a sub-set of nodes (for instance, ones you control, or a small test setup) when developing, debugging, or performing research, you will need to import these nodes as a custom network topology, instead of grabbing it from the [`Mainnet Nym-API`](https://validator.nymtech.net/api/swagger/index.html) (`examples/custom_topology_provider.rs`). There are two ways to do this: #### Import a custom Nym API endpoint -If you are also running a Validator and Nym API for your network, you can specify that endpoint as such and interact with it as clients ususally do (under the hood): +If you are also running a Validator and Nym API for your network, you can specify that endpoint as such and interact with it as clients usually do (under the hood): ```rust,noplayground {{#include ../../../../sdk/rust/nym-sdk/examples/custom_topology_provider.rs}} @@ -102,4 +102,4 @@ The following code shows how you can use the SDK to create and use a [credential {{#include ../../../../sdk/rust/nym-sdk/examples/bandwidth.rs}} ``` -You can read more about Coconut credentials (also referred to as `zk-Nym`) [here](../cococnut.md). +You can read more about Coconut credentials (also referred to as `zk-Nym`) [here](../coconut.md). From f8b9bb7e78d0167acda62426242db111624e0baf Mon Sep 17 00:00:00 2001 From: serinko <97586125+serinko@users.noreply.github.com> Date: Mon, 7 Aug 2023 12:38:10 +0200 Subject: [PATCH 077/361] correcting links, typos - doc finished --- documentation/operators/src/nodes/troubleshooting.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/documentation/operators/src/nodes/troubleshooting.md b/documentation/operators/src/nodes/troubleshooting.md index d8fb566b58..50f9406ff3 100644 --- a/documentation/operators/src/nodes/troubleshooting.md +++ b/documentation/operators/src/nodes/troubleshooting.md @@ -183,7 +183,7 @@ Your mix node **must speak both IPv4 and IPv6** in order to cooperate with other #### Incorrect bonding information -Check that you have provided the correct information when bonding your mix node in the web wallet [interface](https://sandbox-wallet.nymtech.net/). When in doubt, un-bond and then re-bond your node! +Check that you have provided the correct information when bonding your mix node in the web wallet interface. When in doubt, un-bond and then re-bond your node! > All delegated stake will be lost when un-bonding! However the mix node must be operational in the first place for the delegation to have any effect. @@ -222,7 +222,7 @@ bob@nym:~$ hostname -I ### Running on a local machine behind NAT with no fixed IP address -Your ISP has to be IPv6 ready if you want to run a mix node on your local machine. Sadly, in 2020, most of them are not and you won't get an IPv6 address by default from your ISP. Usually it is a extra paid service or they simply don't offer it. +Your ISP has to be IPv6 ready if you want to run a mix node on your local machine. Sadly, in 2020, most of them are not and you won't get an IPv6 address by default from your ISP. Usually it is an extra paid service or they simply don't offer it. Before you begin, check if you have IPv6 [here](https://test-ipv6.cz/) or by running command explained in the [section above](./troubleshooting.md#no-ipv6-connectivity). If not, then don't waste your time to run a node which won't ever be able to mix any packet due to this limitation. Call your ISP and ask for IPv6, there is a plenty of it for everyone! From bff806e5f568743c1d80602f0ab7559124d2f087 Mon Sep 17 00:00:00 2001 From: serinko <97586125+serinko@users.noreply.github.com> Date: Mon, 7 Aug 2023 12:40:28 +0200 Subject: [PATCH 078/361] correction of typos --- documentation/docs/src/tools/nym-cli.md | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/documentation/docs/src/tools/nym-cli.md b/documentation/docs/src/tools/nym-cli.md index f516190002..b6ed36bfa7 100644 --- a/documentation/docs/src/tools/nym-cli.md +++ b/documentation/docs/src/tools/nym-cli.md @@ -4,14 +4,14 @@ This is a CLI tool for interacting with: * the Nyx blockchain (account management, querying the chain state, etc) -* the smart contracts deployed on Nyx (bonding and unbonding mixnodes, collecting rewards, etc) +* the smart contracts deployed on Nyx (bonding and un-bonding mixnodes, collecting rewards, etc) It provides a convenient wrapper around the `nymd` client, and has similar functionality to the `nyxd` binary for querying the chain or executing smart contract methods. ## Building The `nym-cli` binary can be built by running `cargo build --release` in the `nym/tools/nym-cli` directory. -### Useage +### Usage You can see all available commands with: ``` @@ -225,9 +225,9 @@ Bonding a mix node is a process that takes a few steps due to the need to sign a ./nym-cli --mnemonic mixnet operators gateway bond --amount 100000000 --mix-port 1789 --version "1.1.13" --host "85.163.111.99" --identity-key "B6pWscxYb8sPAdKTci8zPy5AgMzn5Zx8KpWwQNCyUSU7" --location "nym-town" --sphinx-key "o6MmKHzRewpNzVwaV37ZX9G3BfK4AmfYvsQfyoyAFRk" --signature "2TujBZfer8r5QM639Yb8coD9xH6f5eXzjAT5dD7wMom9fH8D1u36d7UpPdVaaZrWsCynmYpobwMWqiMKr5kM6CprD" ``` -### Unbond a node +### Un-bond a node -Unbond a mix node or gateway. +Un-bond a mix node or gateway. ``` ./nym-cli mixnet operators gateway unbound --mnemonic ``` @@ -270,7 +270,7 @@ Delegate to a mix node. ./nym-cli mixnet delegators delegate --amount –mix-id --mnemonic ``` -### Undelegate Stake +### Un-delegate Stake Remove stake from a mix node. ``` @@ -321,10 +321,10 @@ Query for vesting schedule in the [vesting smart contract](../nyx/vesting-contra ## Staking on someone's behalf (for custodians) -There is a limitation the the staking address can only perform the following actions (and are visible via the Nym Wallet: +There is a limitation the staking address can only perform the following actions (and are visible via the Nym Wallet: -- Bond on the gaƒteway's or mix node's behalf. -- Delegate or Undelegate (to a mix node in order to begin receiving rewards) +- Bond on the gateway's or mix node's behalf. +- Delegate or Un-delegate (to a mix node in order to begin receiving rewards) - Claiming the rewards on the account ```admonish note title="" @@ -333,7 +333,7 @@ The staking address has no ability to withdraw any coins from the parent's accou The staking address must maintain the same level of security as the parent mnemonic; while the parent mnemonic's delegations and bonding events will be visible to the parent owner, the staking address will be the only account capable of undoing the bonding and delegating from the mix nodes or gateway. -Query for staking on behlaf of someone else +Query for staking on behalf of someone else ``` ./nym-cli --mnemonic mixnet delegators delegate --mix-id --identity-key --amount ``` From 860ec74b596706674552905a165758c4188af1c3 Mon Sep 17 00:00:00 2001 From: serinko <97586125+serinko@users.noreply.github.com> Date: Mon, 7 Aug 2023 12:47:29 +0200 Subject: [PATCH 079/361] operators-guides final review of links and typos --- documentation/operators/src/faq.md | 2 +- documentation/operators/src/introduction.md | 2 +- documentation/operators/src/nodes/mix-node-setup.md | 8 +------- .../operators/src/nodes/network-requester-setup.md | 4 ++-- documentation/operators/src/nodes/troubleshooting.md | 2 +- documentation/operators/src/nodes/validator-setup.md | 4 ++-- 6 files changed, 8 insertions(+), 14 deletions(-) diff --git a/documentation/operators/src/faq.md b/documentation/operators/src/faq.md index a42af22825..9bb2c6dec9 100644 --- a/documentation/operators/src/faq.md +++ b/documentation/operators/src/faq.md @@ -14,7 +14,7 @@ For more detailed calculation, read our blog post [Nym Token Economics update](h ### Which VPS providers would you recommend? -Consider in which jurisdiction you reside and where do you want to run a mix node. Do you want to pay by crypto or not and what are the other important particularities for your case? We always recommend operators to try to chose smaller and decentralized VPS providers over the most known ones controlling a majority of the internet. We recieve some good feedback on these: Hertzner, DigitalOcean, Linode, Ghandi and Exoscale. Do your own research and share with the community. +Consider in which jurisdiction you reside and where do you want to run a mix node. Do you want to pay by crypto or not and what are the other important particularities for your case? We always recommend operators to try to chose smaller and decentralized VPS providers over the most known ones controlling a majority of the internet. We receive some good feedback on these: Hertzner, DigitalOcean, Linode, Ghandi and Exoscale. Do your own research and share with the community. diff --git a/documentation/operators/src/introduction.md b/documentation/operators/src/introduction.md index dc160825d6..4050fe141c 100644 --- a/documentation/operators/src/introduction.md +++ b/documentation/operators/src/introduction.md @@ -2,7 +2,7 @@ This is Nym's Operators guide, containing information and setup guides for the various pieces of Nym mixnet infrastructure (mix node, gateway and network requester) and Nyx blockchain validators. -If you are new to Nym and want to learn about the mixnet, explore quickstart options and demos, learn how to integrate with the network, and follow developer tutorials check out the [Developer Portal](https://nymtech.net/developers/). +If you are new to Nym and want to learn about the mixnet, explore kickstart options and demos, learn how to integrate with the network, and follow developer tutorials check out the [Developer Portal](https://nymtech.net/developers/). If you want to dive deeper into Nym's architecture, clients, nodes, and SDK examples visit the [technical docs](https://nymtech.net/docs/). diff --git a/documentation/operators/src/nodes/mix-node-setup.md b/documentation/operators/src/nodes/mix-node-setup.md index 2dccc52258..1c70383a61 100644 --- a/documentation/operators/src/nodes/mix-node-setup.md +++ b/documentation/operators/src/nodes/mix-node-setup.md @@ -173,12 +173,6 @@ If everything worked, you'll see your node running on the either the [Sandbox te Note that your node's public identity key is displayed during startup, you can use it to identify your node in the list. - - Have a look at the saved configuration files in `$HOME/.nym/mixnodes/` to see more configuration options. ## Node Description (optional) @@ -298,7 +292,7 @@ Once you've started your mix node and it connects to the validator, your node wi - [Mainnet](https://explorer.nymtech.net/overview) - [Sandbox testnet](https://sandbox-explorer.nymtech.net/) -Enter your **identity key** to find your node. There are numerous statistics about your node on that page that are useful for checking your uptime history, packets mixed, and any delegations your node may have. +Enter your **identity key** to find your node. There are numerous statistics about your node on that page that are useful for checking your up-time history, packets mixed, and any delegations your node may have. There are also 2 community explorers which have been created by [Nodes Guru](https://nodes.guru): diff --git a/documentation/operators/src/nodes/network-requester-setup.md b/documentation/operators/src/nodes/network-requester-setup.md index 049b139de6..15cacd3266 100644 --- a/documentation/operators/src/nodes/network-requester-setup.md +++ b/documentation/operators/src/nodes/network-requester-setup.md @@ -22,7 +22,7 @@ Any request to a URL which is not on this list will be blocked. On startup, if this file is not present, the requester will grab the default whitelist from [Nym's default list](https://nymtech.net/.wellknown/network-requester/standard-allowed-list.txt) automatically. -This default whitelist is useful for knowing that the majority of network requesters are able to support certain apps 'out of the box'. +This default whitelist is useful for knowing that the majority of Network requesters are able to support certain apps 'out of the box'. **Operators of a network requester are of course free to edit this file and add the URLs of services they wish to support to it!** You can find instructions below on adding your own URLs or IPs to this list. @@ -77,7 +77,7 @@ matrix.org ``` ## Network Requester Directory -You can find a list of Network Requesters running the default whitelist in the [explorer](https://explorer.nymtech.net/network-components/service-providers). This list comprises of the NRs running as infrastructure for NymConnect. +You can find a list of Network requesters running the default whitelist in the [explorer](https://explorer.nymtech.net/network-components/service-providers). This list comprises of the NRs running as infrastructure for NymConnect. > We are currently working on a smart-contract based solution more in line with how Mix nodes and Gateways announce themselves to the network. diff --git a/documentation/operators/src/nodes/troubleshooting.md b/documentation/operators/src/nodes/troubleshooting.md index 50f9406ff3..20b98970df 100644 --- a/documentation/operators/src/nodes/troubleshooting.md +++ b/documentation/operators/src/nodes/troubleshooting.md @@ -236,7 +236,7 @@ Here is a sample of the `init` command example to create the mix node config. ./nym-mixnode init --id --host 0.0.0.0 --announce-host 85.160.12.13 ``` -- `--host 0.0.0.0` should work every time even if your local machine IPv4 address changes. For example on Monday your router gives your machine an address `192.168.0.13` and on Wednesday, the DHCP lease will end and you will be asigned `192.168.0.14`. Using `0.0.0.0` should avoid this without having to set any static ip in your router`s configuration. +- `--host 0.0.0.0` should work every time even if your local machine IPv4 address changes. For example on Monday your router gives your machine an address `192.168.0.13` and on Wednesday, the DHCP lease will end and you will be assigned `192.168.0.14`. Using `0.0.0.0` should avoid this without having to set any static IP in your router`s configuration. - you can get your current IPv4 address by either using `curl ipinfo.io` if you're on MacOS or Linux or visiting [whatsmyip site](https://www.whatsmyip.org/). Simply copy it and use it as `--anounce-host` address. diff --git a/documentation/operators/src/nodes/validator-setup.md b/documentation/operators/src/nodes/validator-setup.md index 4726b4c11c..ca35c0b2b9 100644 --- a/documentation/operators/src/nodes/validator-setup.md +++ b/documentation/operators/src/nodes/validator-setup.md @@ -455,7 +455,7 @@ If you want to set up a reverse proxying on the validator server to improve secu Linux machines limit how many open files a user is allowed to have. This is called a `ulimit`. We need to set it to a higher value than the default 1024. Follow the instructions in the [maintenance page](./maintenance.md#Setting-the-ulimit) to change the `ulimit` value for validators. ## Using your validator -### Unjailing your validator +### Un-jailing your validator If your validator gets jailed, you can fix it with the following command: ``` @@ -499,7 +499,7 @@ You can check your current balances with: nymd query bank balances ${ADDRESS} ``` -For example, on the Sanbox testnet this would return: +For example, on the Sandbox testnet this would return: ```yaml balances: From 2dd3e031bd51aead056911e765cebfa4fab17519 Mon Sep 17 00:00:00 2001 From: serinko <97586125+serinko@users.noreply.github.com> Date: Mon, 7 Aug 2023 13:13:11 +0200 Subject: [PATCH 080/361] socks5: new commands from #3699 --- documentation/docs/src/clients/socks5-client.md | 12 +++++++++++- documentation/operators/src/nodes/maintenance.md | 2 +- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/documentation/docs/src/clients/socks5-client.md b/documentation/docs/src/clients/socks5-client.md index e7163347cb..e8bce0e948 100644 --- a/documentation/docs/src/clients/socks5-client.md +++ b/documentation/docs/src/clients/socks5-client.md @@ -82,7 +82,7 @@ You can check that your binaries are properly compiled with: You can check the necessary parameters for the available commands by running: ``` -./nym-client --help +./nym-client --help ``` ### Initialising a new client instance @@ -169,3 +169,13 @@ Here is an example of setting the proxy connecting in Blockstream Green: Most wallets and other applications will work basically the same way: find the network proxy settings, enter the proxy url (host: **localhost**, port: **1080**). In some other applications, this might be written as **localhost:1080** if there's only one proxy entry field. + +## Useful Commands + +**no-banner** + +Adding `--no-banner` startup flag will prevent Nym banner being printed even if run in tty environment. + +**build-info** + +A `build-info` command prints the build information like commit hash, rust version, binary version just like what command `--version` does. However, you can also specify an `--output=json` flag that will format the whole output as a json, making it an order of magnitude easier to parse. diff --git a/documentation/operators/src/nodes/maintenance.md b/documentation/operators/src/nodes/maintenance.md index a9a90ccaa6..911a6825af 100644 --- a/documentation/operators/src/nodes/maintenance.md +++ b/documentation/operators/src/nodes/maintenance.md @@ -661,7 +661,7 @@ All ``-specific port configuration can be found in `$HOME/.nym// Date: Mon, 7 Aug 2023 15:52:03 +0300 Subject: [PATCH 081/361] Set sphinx as default packet type --- common/crypto/Cargo.toml | 1 + common/nymsphinx/Cargo.toml | 5 + common/nymsphinx/framing/Cargo.toml | 5 +- common/nymsphinx/params/Cargo.toml | 1 + common/nymsphinx/types/Cargo.toml | 1 + contracts/Cargo.lock | 236 ++++++++++++++++++++++++++-- nym-connect/desktop/Cargo.lock | 2 + 7 files changed, 233 insertions(+), 18 deletions(-) diff --git a/common/crypto/Cargo.toml b/common/crypto/Cargo.toml index 27702da016..992005f2d4 100644 --- a/common/crypto/Cargo.toml +++ b/common/crypto/Cargo.toml @@ -34,6 +34,7 @@ nym-pemstore = { path = "../../common/pemstore", version = "0.3.0" } rand_chacha = "0.2" [features] +default = ["sphinx"] serde = ["serde_crate", "serde_bytes", "ed25519-dalek/serde", "x25519-dalek/serde"] asymmetric = ["x25519-dalek", "ed25519-dalek", "zeroize"] hashing = ["blake3", "digest", "hkdf", "hmac", "generic-array"] diff --git a/common/nymsphinx/Cargo.toml b/common/nymsphinx/Cargo.toml index df93301876..955051b267 100644 --- a/common/nymsphinx/Cargo.toml +++ b/common/nymsphinx/Cargo.toml @@ -40,3 +40,8 @@ path = "framing" [target."cfg(not(target_arch = \"wasm32\"))".dependencies.tokio] version = "1.24.1" features = ["sync"] + +[features] +default = ["sphinx"] +sphinx = ["nym-crypto/sphinx", "nym-sphinx-params/sphinx", "nym-sphinx-types/sphinx"] +outfox = ["nym-crypto/outfox", "nym-sphinx-params/outfox", "nym-sphinx-types/outfox"] diff --git a/common/nymsphinx/framing/Cargo.toml b/common/nymsphinx/framing/Cargo.toml index b510926555..3915f204a2 100644 --- a/common/nymsphinx/framing/Cargo.toml +++ b/common/nymsphinx/framing/Cargo.toml @@ -12,5 +12,6 @@ bytes = "1.0" tokio-util = { version = "0.7.4", features = ["codec"] } thiserror = "1.0.37" -nym-sphinx-types = { path = "../types" } -nym-sphinx-params = { path = "../params" } +nym-sphinx-types = { path = "../types", features = ["sphinx", "outfox"] } +nym-sphinx-params = { path = "../params", features = ["sphinx", "outfox"] } + diff --git a/common/nymsphinx/params/Cargo.toml b/common/nymsphinx/params/Cargo.toml index 57872bb0ca..9d9a5a24e2 100644 --- a/common/nymsphinx/params/Cargo.toml +++ b/common/nymsphinx/params/Cargo.toml @@ -15,5 +15,6 @@ nym-crypto = { path = "../../crypto", features = ["hashing", "symmetric"] } nym-sphinx-types = { path = "../types" } [features] +default = ["sphinx"] sphinx = ["nym-crypto/sphinx", "nym-sphinx-types/outfox"] outfox = ["nym-crypto/outfox", "nym-sphinx-types/outfox"] diff --git a/common/nymsphinx/types/Cargo.toml b/common/nymsphinx/types/Cargo.toml index 0569ef1406..ae348113a0 100644 --- a/common/nymsphinx/types/Cargo.toml +++ b/common/nymsphinx/types/Cargo.toml @@ -13,5 +13,6 @@ nym-outfox = { path = "../../../nym-outfox", optional = true } thiserror = "1" [features] +default = ["sphinx"] sphinx = ["sphinx-packet"] outfox = ["nym-outfox"] diff --git a/contracts/Cargo.lock b/contracts/Cargo.lock index 3d719dd6dc..c30bb959e8 100644 --- a/contracts/Cargo.lock +++ b/contracts/Cargo.lock @@ -2,6 +2,19 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "aes" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e8b47f52ea9bae42228d07ec09eb676433d7c4ed1ebdf0f1d1c29ed446f1ab8" +dependencies = [ + "cfg-if", + "cipher", + "cpufeatures", + "ctr", + "opaque-debug 0.3.0", +] + [[package]] name = "ahash" version = "0.7.6" @@ -19,6 +32,12 @@ version = "1.0.70" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7de8ce5e0f9f8d88245311066a578d72b7af3e7088f32783804676302df237e4" +[[package]] +name = "arrayref" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b4930d2cb77ce62f89ee5d5289b4ac049559b1c45539271f5ed4fdc7db34545" + [[package]] name = "autocfg" version = "1.1.0" @@ -55,13 +74,25 @@ version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" +[[package]] +name = "blake2" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94cb07b0da6a73955f8fb85d24c466778e70cda767a568229b104f0264089330" +dependencies = [ + "byte-tools", + "crypto-mac 0.7.0", + "digest 0.8.1", + "opaque-debug 0.2.3", +] + [[package]] name = "block-buffer" version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" dependencies = [ - "generic-array", + "generic-array 0.14.6", ] [[package]] @@ -70,7 +101,7 @@ version = "0.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" dependencies = [ - "generic-array", + "generic-array 0.14.6", ] [[package]] @@ -85,6 +116,12 @@ version = "3.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b1ce199063694f33ffb7dd4e0ee620741495c32833cde5aa08f02a0bf96f0c8" +[[package]] +name = "byte-tools" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3b5ca7a04898ad4bcd41c90c5285445ff5b791899bb1b0abdd2a2aa791211d7" + [[package]] name = "byteorder" version = "1.4.3" @@ -112,6 +149,25 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "chacha" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddf3c081b5fba1e5615640aae998e0fbd10c24cbd897ee39ed754a77601a4862" +dependencies = [ + "byteorder", + "keystream", +] + +[[package]] +name = "cipher" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ee52072ec15386f770805afd189a01c8841be8696bed250fa2f13c4c0d6dfb7" +dependencies = [ + "generic-array 0.14.6", +] + [[package]] name = "coconut-test" version = "0.1.0" @@ -240,9 +296,9 @@ version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ef2b4b23cddf68b89b8f8069890e8c270d54e2d5fe1b143820234805e4cb17ef" dependencies = [ - "generic-array", + "generic-array 0.14.6", "rand_core 0.6.4", - "subtle", + "subtle 2.4.1", "zeroize", ] @@ -252,10 +308,39 @@ version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" dependencies = [ - "generic-array", + "generic-array 0.14.6", "typenum", ] +[[package]] +name = "crypto-mac" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4434400df11d95d556bac068ddfedd482915eb18fe8bea89bc80b6e4b1c179e5" +dependencies = [ + "generic-array 0.12.4", + "subtle 1.0.0", +] + +[[package]] +name = "crypto-mac" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1d1a86f49236c215f271d40892d5fc950490551400b02ef360692c29815c714" +dependencies = [ + "generic-array 0.14.6", + "subtle 2.4.1", +] + +[[package]] +name = "ctr" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "049bb91fb4aaf0e3c7efa6cd5ef877dbbbd15b39dad06d9948de4ec8a75761ea" +dependencies = [ + "cipher", +] + [[package]] name = "curve25519-dalek" version = "3.2.0" @@ -265,7 +350,7 @@ dependencies = [ "byteorder", "digest 0.9.0", "rand_core 0.5.1", - "subtle", + "subtle 2.4.1", "zeroize", ] @@ -476,13 +561,22 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "digest" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3d0c8c8752312f9713efd397ff63acb9f85585afbf179282e720e7704954dd5" +dependencies = [ + "generic-array 0.12.4", +] + [[package]] name = "digest" version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" dependencies = [ - "generic-array", + "generic-array 0.14.6", ] [[package]] @@ -493,7 +587,7 @@ checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ "block-buffer 0.10.4", "crypto-common", - "subtle", + "subtle 2.4.1", ] [[package]] @@ -569,12 +663,12 @@ dependencies = [ "der", "digest 0.10.7", "ff", - "generic-array", + "generic-array 0.14.6", "group", "pkcs8", "rand_core 0.6.4", "sec1", - "subtle", + "subtle 2.4.1", "zeroize", ] @@ -635,7 +729,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d013fc25338cc558c5c2cfbad646908fb23591e2404481826742b651c9af7160" dependencies = [ "rand_core 0.6.4", - "subtle", + "subtle 2.4.1", ] [[package]] @@ -754,6 +848,15 @@ dependencies = [ "slab", ] +[[package]] +name = "generic-array" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffdf9f34f1447443d37393cc6c2b8313aebddcd96906caf34e54c68d8e57d7bd" +dependencies = [ + "typenum", +] + [[package]] name = "generic-array" version = "0.14.6" @@ -821,7 +924,7 @@ checksum = "5dfbfb3a6cfbd390d5c9564ab283a0349b9b9fcd46a706c1eb10e0db70bfbac7" dependencies = [ "ff", "rand_core 0.6.4", - "subtle", + "subtle 2.4.1", ] [[package]] @@ -845,6 +948,26 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" +[[package]] +name = "hkdf" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01706d578d5c281058480e673ae4086a9f4710d8df1ad80a5b03e39ece5f886b" +dependencies = [ + "digest 0.9.0", + "hmac 0.11.0", +] + +[[package]] +name = "hmac" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a2a2320eb7ec0ebe8da8f744d7812d9fc4cb4d09344ac01898dbcb6a20ae69b" +dependencies = [ + "crypto-mac 0.11.1", + "digest 0.9.0", +] + [[package]] name = "hmac" version = "0.12.1" @@ -945,6 +1068,12 @@ dependencies = [ "sha2 0.10.6", ] +[[package]] +name = "keystream" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c33070833c9ee02266356de0c43f723152bd38bd96ddf52c82b3af10c9138b28" + [[package]] name = "lazy_static" version = "1.4.0" @@ -969,6 +1098,12 @@ dependencies = [ "pkg-config", ] +[[package]] +name = "libm" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7012b1bbb0719e1097c47611d3898568c546d597c2e74d66f6087edd5233ff4" + [[package]] name = "libz-sys" version = "1.1.8" @@ -987,6 +1122,18 @@ version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ece97ea872ece730aed82664c424eb4c8291e1ff2480247ccf7409044bc6479f" +[[package]] +name = "lioness" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ae926706ba42c425c9457121178330d75e273df2e82e28b758faf3de3a9acb9" +dependencies = [ + "arrayref", + "blake2", + "chacha", + "keystream", +] + [[package]] name = "log" version = "0.4.17" @@ -1018,6 +1165,16 @@ dependencies = [ "rand_chacha 0.2.2", ] +[[package]] +name = "num-traits" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f30b0abd723be7e2ffca1272140fac1a2f084c77ec3e123c192b66af1ee9e6c2" +dependencies = [ + "autocfg", + "libm", +] + [[package]] name = "nym-coconut-bandwidth" version = "0.1.0" @@ -1252,6 +1409,7 @@ dependencies = [ name = "nym-sphinx-types" version = "0.2.0" dependencies = [ + "sphinx-packet", "thiserror", ] @@ -1295,6 +1453,12 @@ version = "1.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3" +[[package]] +name = "opaque-debug" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2839e79665f131bdb5782e51f2c6c9599c133c6098982a54c794358bf432529c" + [[package]] name = "opaque-debug" version = "0.3.0" @@ -1485,6 +1649,16 @@ dependencies = [ "getrandom 0.2.10", ] +[[package]] +name = "rand_distr" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9e9532ada3929fb8b2e9dbe28d1e06c9b2cc65813f074fcb6bd5fbefeff9d56" +dependencies = [ + "num-traits", + "rand 0.7.3", +] + [[package]] name = "rand_hc" version = "0.2.0" @@ -1525,7 +1699,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7743f17af12fa0b03b803ba12cd6a8d9483a587e89c69445e3909655c0b9fabb" dependencies = [ "crypto-bigint", - "hmac", + "hmac 0.12.1", "zeroize", ] @@ -1634,9 +1808,9 @@ checksum = "3be24c1842290c45df0a7bf069e0c268a747ad05a192f2fd7dcfdbc1cba40928" dependencies = [ "base16ct", "der", - "generic-array", + "generic-array 0.14.6", "pkcs8", - "subtle", + "subtle 2.4.1", "zeroize", ] @@ -1727,7 +1901,7 @@ dependencies = [ "cfg-if", "cpufeatures", "digest 0.9.0", - "opaque-debug", + "opaque-debug 0.3.0", ] [[package]] @@ -1760,6 +1934,30 @@ dependencies = [ "autocfg", ] +[[package]] +name = "sphinx-packet" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc43eda802856ee82a7555c7b75ceb9e07451741c7a2f5f23d036020e01189d4" +dependencies = [ + "aes", + "arrayref", + "blake2", + "bs58", + "byteorder", + "chacha", + "curve25519-dalek", + "digest 0.9.0", + "hkdf", + "hmac 0.11.0", + "lioness", + "log", + "rand 0.7.3", + "rand_distr", + "sha2 0.9.9", + "subtle 2.4.1", +] + [[package]] name = "spki" version = "0.6.0" @@ -1776,6 +1974,12 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" +[[package]] +name = "subtle" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d67a5a62ba6e01cb2192ff309324cb4875d0c451d55fe2319433abe7a05a8ee" + [[package]] name = "subtle" version = "2.4.1" diff --git a/nym-connect/desktop/Cargo.lock b/nym-connect/desktop/Cargo.lock index fc3dba557f..3f657d5852 100644 --- a/nym-connect/desktop/Cargo.lock +++ b/nym-connect/desktop/Cargo.lock @@ -4186,6 +4186,8 @@ dependencies = [ name = "nym-sphinx-types" version = "0.2.0" dependencies = [ + "nym-outfox", + "sphinx-packet", "thiserror", ] From baebbe4e4ddd6a952591238907b1f3f4e0d7f4e1 Mon Sep 17 00:00:00 2001 From: mfahampshire Date: Mon, 7 Aug 2023 15:52:48 +0200 Subject: [PATCH 082/361] added links --- documentation/docs/src/nodes/gateway.md | 9 ++++----- documentation/docs/src/nodes/mixnode.md | 15 ++++++--------- documentation/docs/src/nodes/validator.md | 6 +----- 3 files changed, 11 insertions(+), 19 deletions(-) diff --git a/documentation/docs/src/nodes/gateway.md b/documentation/docs/src/nodes/gateway.md index ebdae64574..814c550d4c 100644 --- a/documentation/docs/src/nodes/gateway.md +++ b/documentation/docs/src/nodes/gateway.md @@ -3,10 +3,9 @@ > The gateway setup and maintenance guide has migrated to the [Operator Guides book](https://nymtech.net/operators/nodes/gateway-setup.html). Gateways are key to both the usability of the mixnet, and the operation of the mixnet's tokenomics. They serve two main functions: -* (When the mixnet is no longer in 'free' mode) to act literally as gateways; checking for zkNym credentials (previously referred to as [Coconut Credentials](../bandwidth-credentials.md)) that prove a user has paid to send traffic through the mixnet. A % of the worth of these credentials will be distributed to the operator of the gateway periodically as payment for providing their service. The more credentials user clients 'spend' with them (because of their quality of service) the higher the rewards. The rest of this value will be sent to the Mixmining Pool, a pool of tokens from which `NYM` rewards are distributed to mix node operators. -* Act as a mailbox for connected clients. Clients create a lasting relationship with a gateway on initialisation, binding themselves to always use a particular gateway as their egress point for mixnet traffic, and always receive mixnet traffic from it (see the [mixnet traffic flow page](../architecture/traffic-flow.md) for further details). If a client is offline and the Gateway can't deliver packets addressed to it, they will hold these packets until the client comes back online. +* In the future (when the mixnet is no longer running in 'free to use' mode), to check for zkNym credentials (previously referred to as [Coconut Credentials](../bandwidth-credentials.md)) with which users can anonymously prove they have paid to send traffic through the mixnet. A % of the worth of these credentials will be distributed to the operator of the gateway periodically as payment for providing their service. The more credentials user clients 'spend' with them the higher the rewards for the gateway operator and their delegators. The rest of this value will be sent to the Mixmining Pool, a pool of tokens from which `NYM` rewards are distributed to mix node operators. +* Act as a mailbox for connected clients. Clients create a lasting relationship with a gateway on initialisation, binding themselves to always use a particular gateway as their egress point for mixnet traffic, and always receive mixnet traffic from it (see the [mixnet traffic flow page](../architecture/traffic-flow.md) and the [addressing scheme](../clients/addressing-system.md) for further details). If a client is offline and the Gateway can't deliver packets addressed to it, they will hold these packets until the client comes back online. ## Further Reading -* TODO whitepaper section -* TODO loopix paper section -* TODO blogpost +* [Nym Whitepaper](https://nymtech.net/nym-whitepaper.pdf) section 4.2 +* [Nym Blog: Gateways to Privacy](https://blog.nymtech.net/gateways-to-privacy-51196005bf5) diff --git a/documentation/docs/src/nodes/mixnode.md b/documentation/docs/src/nodes/mixnode.md index 2d1911a990..e849b141b1 100644 --- a/documentation/docs/src/nodes/mixnode.md +++ b/documentation/docs/src/nodes/mixnode.md @@ -2,19 +2,16 @@ > The mix node setup and maintenance guide has migrated to the [Operator Guides book](https://nymtech.net/developers/nodes/mix-node-setup.html). -Mix nodes are the backbone of the mixnet. These are the nodes that perform 'mix mining', otherwise known simply as 'mixing' or performing the 'poisson mix'. +Mix nodes are the backbone of the mixnet. These are the nodes that perform 'mix mining', otherwise known simply as 'mixing'. -Mix nodes perform one task: receiving packets, decrypting their outer 'layer', and holding them for a variable amount of time before passing them to their next destination - either another mix node, or a gateway. In doing so, they 'mix' packets by sending them to their next destination in a different order than they were recieved. - -The aim of this mixing is to protect against timing-based deanonymisation attempts by a global adversary able to monitor the entire network with a 'God's Eye View'. +Mix nodes, after receiving a packet, decrypt its outer 'layer', and hold them for a variable amount of time before passing them to their next destination - either another mix node, or a gateway. In doing so, they 'mix' packets by sending them to their next destination in a different order than they were received. Mix nodes are rewarded according to their quality of service, and their inclusion in the active set (i.e. the nodes that mix traffic for the next epoch) affected by this (as well as their delegation-based reputation - see [further reading](#further-reading) for more on this). ## (Coming soon) Mixing: a Step-by-Step Breakdown ## Further reading -TODO -* TODO SECTION OF WHITEPAPER -* TODO LOOPIX PAPER SECTION -* -* +* [Nym Whitepaper](https://nymtech.net/nym-whitepaper.pdf) section 4 +* [Nym Blog: Mix node deepdive](https://blog.nymtech.net/nym-mixnodes-deep-dive-d2b91917f097) +* [Mixnet Traffic Flow overview](../architecture/traffic-flow.md) +* [Reward Sharing for Mixnets](https://nymtech.net/nym-cryptoecon-paper.pdf) \ No newline at end of file diff --git a/documentation/docs/src/nodes/validator.md b/documentation/docs/src/nodes/validator.md index ac3d896bca..ceb0abdf8a 100644 --- a/documentation/docs/src/nodes/validator.md +++ b/documentation/docs/src/nodes/validator.md @@ -1,6 +1,4 @@ - [x] TODO section of whitepaper -- [x] TODO blogpost -- [ ] TODO gaias link(s) # Validators @@ -8,7 +6,6 @@ > We are currently working towards building up a closed set of reputable validators. You can ask us for coins to get in, but please don't be offended if we say no - validators are part of our system's core security and we are starting out with people we already know or who have a solid reputation. - Validators secure the Nyx blockchain via Proof of Stake consensus. The Nyx blockchain records the ledger of NYM transactions and executes the smart contracts for distributing NYM rewards. The Nyx validators are run via the `nyxd` binary ([codebase](https://github.com/nymtech/nyxd)), maintaining a CosmWasm- and IBC-enabled blockchain. The blockchain plays a supporting but fundamental role in the mixnet: the `NYM` token used to incentivise node operators is one of two native tokens of the chain, and the chain is where the [Mixnet](../nyx/mixnet-contract.md) and [Vesting](../nyx/vesting-contract.md) smart contracts are deployed. @@ -16,10 +13,9 @@ The blockchain plays a supporting but fundamental role in the mixnet: the `NYM` ![Nym Token Flow](../images/nym-token-flow.png) ## Further Reading - * Detailed info on Nyx Validators and token flow can be found in [Nym Reward Sharing for Mixnets document](https://nymtech.net/nym-cryptoecon-paper.pdf) in section 2.3 and 2.4. * For more detailed calculation, read our blog post [Nym Token Economics update](https://blog.nymtech.net/nym-token-economics-update-fedff0ed5267). * More info on staking can be found [here](https://blog.nymtech.net/staking-in-nym-introducing-mainnet-mixmining-f9bb1cbc7c36). * [Here](https://blog.nymtech.net/want-to-stake-in-nym-here-is-how-to-choose-a-mix-node-to-delegate-nym-to-c3b862add165) is more info on how to choose a mix node for delegation. * Our [quarterly update](https://blog.nymtech.net/quarterly-token-economic-parameter-update-b2862948710f) on token economics from July 2023. - +* [Nym Whitepaper](https://nymtech.net/nym-whitepaper.pdf) section 3.1 \ No newline at end of file From f954ce5c97fc4a113b6ad9790c258d85ad502ac9 Mon Sep 17 00:00:00 2001 From: mfahampshire Date: Mon, 7 Aug 2023 16:06:39 +0200 Subject: [PATCH 083/361] more links, fleshed out intro --- documentation/docs/src/introduction.md | 23 +++++++++++++-------- documentation/docs/src/wallet/cli-wallet.md | 2 +- 2 files changed, 15 insertions(+), 10 deletions(-) diff --git a/documentation/docs/src/introduction.md b/documentation/docs/src/introduction.md index 9e245497f4..9964ec5b75 100644 --- a/documentation/docs/src/introduction.md +++ b/documentation/docs/src/introduction.md @@ -2,24 +2,29 @@ This is Nym's technical documentation, containing information and setup guides about the various pieces of Nym software such as different mixnet infrastructure nodes, application clients, and existing applications like the desktop wallet and mixnet explorer. -If you are new to Nym and want to learn about the mixnet, how to integrate with the network, developer tutorials and quickstart guides, check out the [Developer Portal](https://nymtech.net/developers/). +If you are looking for information and setup guides for the various pieces of Nym mixnet infrastructure (mix nodes, gateways and network requesters) and Nyx blockchain validators see the **new [Operators Guides](https://nymtech.net/operators)** book. + +If you are new to Nym and want to learn about the mixnet, explore kickstart options and demos, learn how to integrate with the network, and follow developer tutorials check out the [Developer Portal](https://nymtech.net/developers/). ## Popular pages **Network Architecture:** * [Network Overview](./architecture/network-overview.md) * [Mixnet Traffic Flow](./architecture/traffic-flow.md) -**Node setup and usage guides:** -* [Mix nodes](./nodes/mix-node-setup.md) -* [Gateways](./nodes/gateway-setup.md) -* [Network requesters](./nodes/network-requester-setup.md) -* [Validators](./nodes/validator-setup.md) +**SDK examples:** +* [Typescript SDK](./sdk/typescript.md) +* [Rust SDK](./sdk/rust.md) + +**Nyx** +* [Interacting with the Nyx chain](./nyx/interacting-with-chain.md) +* [Ledger Live setup](./nyx/ledger-live.md) +* [RPC Node](./nyx/rpc-node.md) **Client setup and usage guides:** * [Websocket client](./clients/websocket-client.md) * [Socks5 client](./clients/socks5-client.md) * [Webassembly client](./clients/webassembly-client.md) -**SDK guides:** -* [Typescript SDK](./sdk/typescript.md) -* [Rust SDK](./sdk/rust.md) +**Wallets** +* [Desktop](./wallet/desktop-wallet.md) +* [CLI](./wallet/cli-wallet.md) \ No newline at end of file diff --git a/documentation/docs/src/wallet/cli-wallet.md b/documentation/docs/src/wallet/cli-wallet.md index e4d2468068..3f713b1937 100644 --- a/documentation/docs/src/wallet/cli-wallet.md +++ b/documentation/docs/src/wallet/cli-wallet.md @@ -5,4 +5,4 @@ If you have already read our validator setup and maintenance [documentation](../ ### Using `nyxd` binary as a CLI wallet You can use the `nyxd` as a minimal CLI wallet if you want to set up an account (or multiple accounts). Just compile the binary as per the documentation, **stopping after** the [building your validator](../nodes/validator-setup.md#building-your-validator) step is complete. You can then run `nyxd keys --help` to see how you can set up and store different keypairs with which to interact with the Nyx blockchain. - +For more on interacting with the chain, see the [Interacting with Nyx Chain and Smart Contracts](../nyx/interacting-with-chain.md) page. From 2565716816c21829b4cd9255a4097fbace18a192 Mon Sep 17 00:00:00 2001 From: mfahampshire Date: Mon, 7 Aug 2023 16:38:02 +0200 Subject: [PATCH 084/361] typo fix --- documentation/operators/src/nodes/network-requester-setup.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/documentation/operators/src/nodes/network-requester-setup.md b/documentation/operators/src/nodes/network-requester-setup.md index 15cacd3266..e840bb357a 100644 --- a/documentation/operators/src/nodes/network-requester-setup.md +++ b/documentation/operators/src/nodes/network-requester-setup.md @@ -83,7 +83,7 @@ You can find a list of Network requesters running the default whitelist in the [ ## Viewing command help -To begin, move to `/taget/release` directory from which you run the node commands: +To begin, move to `/target/release` directory from which you run the node commands: ``` cd target/release From 2eeb8bb590cef6fd125370051fc88000e64e1940 Mon Sep 17 00:00:00 2001 From: mfahampshire Date: Mon, 7 Aug 2023 16:38:13 +0200 Subject: [PATCH 085/361] NR page --- .../docs/src/nodes/network-requester.md | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/documentation/docs/src/nodes/network-requester.md b/documentation/docs/src/nodes/network-requester.md index 83778e18a4..ac083a3143 100644 --- a/documentation/docs/src/nodes/network-requester.md +++ b/documentation/docs/src/nodes/network-requester.md @@ -1,9 +1,22 @@ -# Network Requesters +# Network Requester > The network requester setup and maintenance guide has moved to the [Operator Guides book](https://nymtech.net/developers/nodes/network-requester-setup.html). Network requesters are the first instance of the catch-all term 'service', or 'service providers'. In essence, think of services as being the part of the mixnet infrastructure that let you _do_ something, such as access emails, messaging service backends, or blockchains via the mixnet. -Network requesters, in essence, act as a form of proxy (somewhat analagous to a Tor exit node) +## Domain filtering +Network requesters, in essence, act as a form of proxy (somewhat analagous to a Tor exit node). If you have access to a server, you can run the network requester, which allows Nym users to send outbound requests from their local machine through the mixnet to a server, which then makes the request on their behalf, shielding them (and their metadata) from clearnet, untrusted and unknown infrastructure, such as email or message client servers. +By default the network requester is **not** an open proxy (although it can be used as one). It uses a whitelist for outbound requests. +Any request to a URL which is not on this local list (modified by the node operator) or [Nym's default list](https://nymtech.net/.wellknown/network-requester/standard-allowed-list.txt) will be blocked. + +This default whitelist is useful for knowing that the majority of Network requesters are able to support certain apps 'out of the box', and the local whitelist allows operators to include their own whitelisted domains. + +> Substantial changes are on the horizon concerning how Network Requesters manage incoming requests - if you are an operator and have experience running software such as Tor exit nodes or p2p nodes get in touch via our [Matrix server](https://matrix.to/#/#dev:nymtech.chat). + +## (Coming soon) Consuming credentials for anonymous service payment + +## Further reading +* [Nym Blog: Network Requester deepdive](https://blog.nymtech.net/tech-deepdive-network-requesters-e5359a6cc31c) +* [Nym Blog: Choose Your Character](https://blog.nymtech.net/choose-your-character-an-overview-of-nym-network-actors-19e6a9808540) \ No newline at end of file From 1a195d151dcfeb2d085e7b1d443f2de906f33af6 Mon Sep 17 00:00:00 2001 From: mfahampshire Date: Tue, 8 Aug 2023 10:59:42 +0200 Subject: [PATCH 086/361] added ide config to gitignore --- documentation/dev-portal/.gitignore | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/documentation/dev-portal/.gitignore b/documentation/dev-portal/.gitignore index bb380f03ab..c29699d0c0 100644 --- a/documentation/dev-portal/.gitignore +++ b/documentation/dev-portal/.gitignore @@ -17,4 +17,6 @@ book theme/ theme -theme/* \ No newline at end of file +theme/* + +.idea \ No newline at end of file From ffb9ab901935b1387d0ccb7c3571ab210352322f Mon Sep 17 00:00:00 2001 From: mfahampshire Date: Tue, 8 Aug 2023 11:00:11 +0200 Subject: [PATCH 087/361] tweaks to tutorial --- .../tutorials/cosmos-service/client-src.md | 8 +++-- .../src/tutorials/cosmos-service/client.md | 8 ++--- .../src/tutorials/cosmos-service/intro.md | 6 ++-- .../src/tutorials/cosmos-service/lib.md | 33 +++++++++++++++---- .../src/tutorials/cosmos-service/overview.md | 9 +++-- .../tutorials/cosmos-service/preparing-env.md | 19 ++++------- .../tutorials/cosmos-service/service-src.md | 2 +- .../src/tutorials/cosmos-service/service.md | 6 ++-- 8 files changed, 53 insertions(+), 38 deletions(-) diff --git a/documentation/dev-portal/src/tutorials/cosmos-service/client-src.md b/documentation/dev-portal/src/tutorials/cosmos-service/client-src.md index b8b223357b..63fb82b99b 100644 --- a/documentation/dev-portal/src/tutorials/cosmos-service/client-src.md +++ b/documentation/dev-portal/src/tutorials/cosmos-service/client-src.md @@ -8,11 +8,13 @@ use crate::{handle_response, wait_for_non_empty_message, RequestTypes, DEFAULT_V use cosmrs::AccountId; use nym_sdk::mixnet::MixnetClient; use nym_sphinx_addressing::clients::Recipient; -use nym_validator_client::nyxd::Coin +use nym_validator_client::nyxd::Coin; ``` +As well as importing message-handling functionality, request types, and the default RPC endpoint, this file relies on the `AccountId` type to construct blockchain addresses, the `MixnetClient` for interacting with the mixnet, the `Recipient` type to construct mixnet recipient addresses, and the `Coin` type for properly handling the returned balance of the account that will be queried. + # Querying via the Mixnet -The following is used to construct a `BalanceRequest`, send this to the supplied `service` address, and then handle the response, matching it to a `ResponseType`, in this case the only expected response, a `BalanceResponse`. +The following is used to construct a `BalanceRequest`, send this to the supplied `service` address, and then handle the response, matching it to a `ResponseType` (in this case the only expected response, a `BalanceResponse`). The actual sending of the request is performed by `client.send_bytes`: sending the serialised `BalanceRequest` to the supplied Nym address (the `Recipient` imported from the `nym_sphinx_addressing` crate). It is sending the default number of SURBs along with the message, defined [here](https://github.com/nymtech/nym/blob/develop/sdk/rust/nym-sdk/src/mixnet/client.rs#L34). @@ -50,4 +52,4 @@ pub async fn query_balance( } ``` -That is all of the client code written: now to move on to the `service` that will be interacting with the blockchain on behalf of the `client`. +That is all the client code written: now to move on to the `service` that will be interacting with the blockchain on behalf of the `client`. diff --git a/documentation/dev-portal/src/tutorials/cosmos-service/client.md b/documentation/dev-portal/src/tutorials/cosmos-service/client.md index df3751c4dd..be9f142f09 100644 --- a/documentation/dev-portal/src/tutorials/cosmos-service/client.md +++ b/documentation/dev-portal/src/tutorials/cosmos-service/client.md @@ -43,10 +43,10 @@ struct QueryBalance { ``` ## `main()` -This is the root logic of the `client`. Using `[tokio](URL_TO_DO)` for the async runtime, this function performs the following functions: -* If not already existing, creates a Nym client with config at `/tmp/client`. Otherwise it loads the already existing client from this config. -* Matches the command from the CLI - in this instance, the `QueryBalance` function which will be defined in the next section. This will create a `BalanceRequest` and send this to the `service`, before returning the response sent back to it to the main thread and printing this to the console. -* Performs a proper shutdown of the running Nym client. +This is the root logic of the `client`. Using `[tokio](https://tokio.rs/)` for the async runtime, this function performs the following functions: +* If not already existing, create a Nym client with config at `/tmp/client`. Otherwise load the already existing client from this config. +* Matche the command from the CLI - in this instance, the `QueryBalance` function which will be defined in the next section. This creates a `BalanceRequest` and sends this to the `service`, before returning the response back to the main thread and print this to the console. +* Perform a proper shutdown of the Nym client. ```rust #[tokio::main] diff --git a/documentation/dev-portal/src/tutorials/cosmos-service/intro.md b/documentation/dev-portal/src/tutorials/cosmos-service/intro.md index 5b08d0ecfe..cf9dad5fd0 100644 --- a/documentation/dev-portal/src/tutorials/cosmos-service/intro.md +++ b/documentation/dev-portal/src/tutorials/cosmos-service/intro.md @@ -1,7 +1,7 @@ -# Building a Cosmos Blockchain Service with the Rust SDK +# Interacting with a Cosmos SDK Blockchain via the Mixnet with the Rust SDK -This tutorial is for Rust developers wanting to interact with the Rust SDK and take a first step at building a service with which to interact with a blockchain. +This tutorial is for Rust developers wanting to interact with the Rust SDK and take a first step at building a service with which to interact with a Cosmos SDK blockchain. -The key here is to think of the service as a proxy: it interacts with the blockchain _on the client's behalf_, shielding it from the Validator it interacts with, whilst also being shielded from the client by the mixnet. +The key here is to think of the service as a proxy: it interacts with the blockchain _on the client's behalf_, shielding the client from the Validator it interacts with, whilst also being shielded from the client by the mixnet. > This service also nicely highlights the limitations of the mixnet - even though with this code your metadata is shielded from the Validator, and even the service does not know your Nym address, application-level information such as a blockchain address is not made private, in virtue of the fact that using the mixnet provides solely network-level privacy. For information on what application-level privacy Nym offers, check out the [coconut credential SDK example](https://nymtech.net/docs/sdk/rust.html#coconut-credential-generation). diff --git a/documentation/dev-portal/src/tutorials/cosmos-service/lib.md b/documentation/dev-portal/src/tutorials/cosmos-service/lib.md index 8adb147bd4..0be72a690d 100644 --- a/documentation/dev-portal/src/tutorials/cosmos-service/lib.md +++ b/documentation/dev-portal/src/tutorials/cosmos-service/lib.md @@ -5,7 +5,7 @@ Now move on to preparing shared data structures and functions in `src/lib.rs`. These include the request and response types the client and the service will be passing through the mixnet, as well as shared functions such as client creation, and message parsing. ## Dependencies -The dependecies for the shared `lib` file are the following: +The dependencies for the shared `lib` file are the following: ```rust use anyhow::bail; use cosmrs::AccountId; @@ -31,7 +31,7 @@ pub const DEFAULT_DENOM: &str = "unym"; pub const DEFAULT_PREFIX: &str = "n"; ``` -These define the RPC endpoint your service will use to interact with the blockchain - in this case the Sandbox testnet - as well as the coin denomination and Bech32-prefix of the blockchain accounts. +These define the RPC endpoint your service will use to interact with the blockchain - in this case the Sandbox testnet - as well as the expected coin denomination, and Bech32-prefix of addresses. ## Shared Data Structures Define the following structs for our different request and responses that will be serialised and sent through the mixnet between your client and service binaries: @@ -79,17 +79,17 @@ impl ResponseTypes { } ``` -The above data types are pretty straightforward. Even though there are only one instance of a request type (sent from client -> service) and one of a response type (service -> client) so far, a pair of enums has been defined to contain additional response or request types that will be added in the future. +The above data types are pretty straightforward. Even though there are only one instance of a request type (sent from `client` -> mixnet -> `service`) and one of a response type (`service` -> mixnet -> `client`) so far, a pair of enums has been defined to contain additional response and request types that will be added in part 2 of this tutorial, when adding credential functionality. `BalanceRequest` will be used when requesting the service to query the token balance of the supplied address on the client's behalf. You can see the information that will be returned from the chain to the service, and from the service to the client, in `BalanceResponse`. -Custom serialistion and deserialisation have been implemented for each enum for ease of future modification if required, and testing. +Custom serialistion and deserialisation have been implemented for each enum for ease of future modification and testing. ## Shared Functions Now to define functions shared by the `client` and `service` binaries. ### Client Creation -The following function is called on startup by each binary, with the `config_path` being a filepath for storing client config. +The following function is called on startup by each binary, with the `config_path` being a filepath for storing client config: ```rust // create our client with specified path for key storage @@ -107,6 +107,25 @@ pub async fn create_client(config_path: PathBuf) -> MixnetClient { } ``` +If no config files exist at the location designated by `config_path` (in this case `/tmp/service`) then the following files are generated: + +```sh +service +├── ack_key.pem +├── db.sqlite +├── db.sqlite-shm +├── db.sqlite-wal +├── gateway_details.json +├── gateway_shared.pem +├── persistent_reply_store.sqlite +├── private_encryption.pem +├── private_identity.pem +├── public_encryption.pem +└── public_identity.pem + +1 directory, 11 files +``` + > If keys and config already exist at this location, re-running this function **will not** overwrite them. ### Listening for & Parsing Incoming messages @@ -114,7 +133,7 @@ Next to define two functions: one for listening _for_ messages from the mixnet ( Both functions attempt to deserialise the vec of `ReconstructedMessages` that are reconstructed by the client from delivered Sphinx packets after decryption. -`handle_request` performs one additional function - parsing the `sender_tag` from the incoming reconstructed message. This is the randomised alphanumeric string used to identify a bucket of _SURBs_ (Single Use Reply Blocks) that are sent along with any outgoing message by default. More information about them can be found [here](https://nymtech.net/docs/architecture/traffic-flow.html#private-replies-using-surbs) but all that is necessary to know for now is that these are pre-addressed packets that clients send out with their messages. Any reply to their message that is to be sent back to them back be written to the payload of these packets, but without the entity seeing the destination address. This allows for services to _anonymously reply_ to clients without being able to doxx them. +`handle_request` performs one additional function - parsing the `sender_tag` from the incoming reconstructed message. This is the randomised alphanumeric string used to identify a bucket of _SURBs_ (Single Use Reply Blocks) that are sent along with any outgoing message by default. More information about them can be found [here](https://nymtech.net/docs/architecture/traffic-flow.html#private-replies-using-surbs) but all that is necessary to know for now is that these are pre-addressed packets that clients send out with their messages. Any reply to their message that is to be sent back to them back be written to the payload of these packets, but without the replying party being able to see the destination that the reply is being sent to. This allows for services to **anonymously reply to clients without being able to doxx them by knowing their Nym address**. ```rust pub fn handle_response(message: ReconstructedMessage) -> anyhow::Result { @@ -129,7 +148,7 @@ pub fn handle_request( } ``` -Before moving on to the `client` and `service` code, one more function is needed. This allows for both binaries to parse empty incoming messages that they might receive. This is necessary as incoming SURBs as well as requests for more SURBs contain empty data fields. +Before moving on to the `client` and `service` code, one more function is needed. This allows for both binaries to parse empty incoming messages that they might receive. This is necessary as incoming SURBs, as well as requests for more SURBs, contain empty data fields. ```rust pub async fn wait_for_non_empty_message( diff --git a/documentation/dev-portal/src/tutorials/cosmos-service/overview.md b/documentation/dev-portal/src/tutorials/cosmos-service/overview.md index a422ea4047..829ef476ee 100644 --- a/documentation/dev-portal/src/tutorials/cosmos-service/overview.md +++ b/documentation/dev-portal/src/tutorials/cosmos-service/overview.md @@ -2,16 +2,15 @@ This tutorial involves writing two pieces of code in Rust: -- A client side binary used to (for now) construct a blockchain query and send this query to a service, which will interact with a Cosmos SDK blockchain on our behalf (bear in mind this principle works for all blockchains - we're just utilising the `cosmrs` library to interact with the Nyx blockchain in this tutorial). This query will be to query the balance of an account, in preparation for spending these tokens on a credential in a subsequent tutorial. +- A client side binary used to construct a blockchain query and send this query to a service, which will query the Cosmos SDK blockchain and then pass the response back to the client (bear in mind this principle works for all blockchains - we're just utilising the `cosmrs` library to interact with the Sandbox testnet blockchain in this tutorial). This query will be to query the balance of an account, in preparation for spending these tokens on a [bandwidth credential](https://nymtech.net/docs/bandwidth-credentials.html) in a subsequent tutorial. - A service which will listen out for requests from the mixnet, act on those requests, and anonymously reply to the client sending the requests. You will learn how to do the following with the Rust SDK: - Create clients with manual storage settings. -- Parse incoming traffic from the mixnet and reply anonymously using [SURBs](). +- Parse incoming traffic from the mixnet and reply anonymously using [SURBs](https://nymtech.net/docs/architecture/traffic-flow.html#private-replies-using-surbs). > Services usually run on remote servers to assure reliable uptime and to unlink sender and receiver metadata. For demonstration purposes however, you will run both components on your local machine, looping messages through the mixnet to yourself. - ``` TODO @@ -30,9 +29,9 @@ TUTORIAL ``` -You can find the code for these components [here](). You can use it as a reference while building or simply download it and follow along as you progress through the tutorial. +You can find the code for these components [here](https://github.com/nymtech/developer-tutorials). You can use it as a reference while building or simply download it and follow along as you progress through the tutorial. -Notice that this tutorial attempts to use very few external libraries. This tutorial is not showing you how to build production-grade code, but **to understand how to connect and send messages to, as well as recieve messages from, the mixnet.** +Notice that this tutorial attempts to use very few external libraries. This tutorial is not showing you how to build production-grade code, but **to understand how to connect and send messages to, as well as receive messages from, the mixnet.** ```admonish note title="Sidenote: What is a Service / Service Provider?" 'Service' or 'Service Provider' are catchall names used to refer to any type of app that can communicate with the mixnet via a Nym client - in this case, one embedded in its app process via the Rust SDK. diff --git a/documentation/dev-portal/src/tutorials/cosmos-service/preparing-env.md b/documentation/dev-portal/src/tutorials/cosmos-service/preparing-env.md index 474a3add08..e5016e2ef2 100644 --- a/documentation/dev-portal/src/tutorials/cosmos-service/preparing-env.md +++ b/documentation/dev-portal/src/tutorials/cosmos-service/preparing-env.md @@ -33,23 +33,18 @@ cosmrs = "=0.14.0" tokio = { version = "1.24.1", features = ["rt-multi-thread", "macros"] } serde = "1.0.152" serde_json = "1.0.91" +anyhow = "1.0.72" +``` + +These are non Nym-specific dependencies for the project. `clap` is for setting up the CLI commands, `cosmrs` for cosmos-specific types and functionality, `tokio` for the async/await environment, and `serde` for (de)serialisation. `anyhow` is for catch-all error handling. + +* Next add Nym-specific dependencies. Since these libraries are not yet on [crates io](https://crates.io) then you need to import them from the Nym monorepo: +``` nym-sdk = { git = "https://github.com/nymtech/nym", rev = "5dacf0c8f8775de6168d4da808fdce56e1ac2706" } nym-sphinx-addressing = { git = "https://github.com/nymtech/nym", rev = "5dacf0c8f8775de6168d4da808fdce56e1ac2706" } nym-validator-client = { git = "https://github.com/nymtech/nym", rev = "5dacf0c8f8775de6168d4da808fdce56e1ac2706" } nym-bin-common = { git = "https://github.com/nymtech/nym", rev = "5dacf0c8f8775de6168d4da808fdce56e1ac2706" } nym-sphinx-anonymous-replies = { git = "https://github.com/nymtech/nym", rev = "5dacf0c8f8775de6168d4da808fdce56e1ac2706" } -anyhow = "1.0.72" -``` - -These are non Nym-specific dependencies for the project. `anyhow` is for catch-all error handling, `clap` is for setting up the CLI commands, `cosmrs` for cosmos-specific types and functionality, `tokio` for the async/await environment, and `serde` for (de)serialisation. - -* Next add Nym-specific dependencies. Since these libraries are not yet on [crates io](https://crates.io) then you need to import them from the Nym monorepo: -``` -nym-sdk = { git = "https://github.com/nymtech/nym" } -nym-sphinx-addressing = { git = "https://github.com/nymtech/nym" } -nym-validator-client = { git = "https://github.com/nymtech/nym" } -nym-bin-common = { git = "https://github.com/nymtech/nym" } -nym-sphinx-anonymous-replies = { git = "https://github.com/nymtech/nym" } ``` The `sphinx` dependencies are for packet- and address-related functionality, the `validator-client` for Nyx blockchain specific configs, `common` for client logging, and the `sdk` for SDK functionality: creating and managing client storage and connections, and sending and receiving messages to and from the mixnet. diff --git a/documentation/dev-portal/src/tutorials/cosmos-service/service-src.md b/documentation/dev-portal/src/tutorials/cosmos-service/service-src.md index 6fb3324726..0e463362e8 100644 --- a/documentation/dev-portal/src/tutorials/cosmos-service/service-src.md +++ b/documentation/dev-portal/src/tutorials/cosmos-service/service-src.md @@ -3,7 +3,7 @@ Now to define the logic of creating the `broadcaster` for interacting with the blockchain, and querying it in `src/service.rs`. ## Dependencies -The following dependencies are for creating a client to interact with the blockchain, and deal with the returned `Coin` type expected from the balance query. +The following dependencies are for creating a client to interact with the blockchain, and deal with the returned `Coin` type in `BalanceResponse`. ```rust use crate::{BalanceResponse, DEFAULT_DENOM, DEFAULT_VALIDATOR_RPC}; diff --git a/documentation/dev-portal/src/tutorials/cosmos-service/service.md b/documentation/dev-portal/src/tutorials/cosmos-service/service.md index f8313bed8b..055bddfd00 100644 --- a/documentation/dev-portal/src/tutorials/cosmos-service/service.md +++ b/documentation/dev-portal/src/tutorials/cosmos-service/service.md @@ -18,16 +18,16 @@ The `AnonymousSenderTag` type is used for SURBs. ## main() Also using tokio for the async runtime, `main` does the following: -* If not already existing, creates a Nym client with config at `/tmp/client`. Otherwise it loads the already existing client from this config. +* Create a Nym client with config at `/tmp/service`, or load the existing client from this config directory. * Create a `broadcaster` - this is used by the service to interact with the blockchain, using the consts defined in `src/lib.rs` as chain config. * Listen out for incoming messages, and in much the same way as the `client`, handle and match the incoming request. -* Using the `sender_tag`, anonymously reply to the Nym client in `client` with the response from the blockchain. +* Using the `sender_tag`, anonymously reply to the `client` with the response from the blockchain **without having to know the client's Nym address**. ```rust #[tokio::main] async fn main() -> anyhow::Result<()> { setup_logging(); - let mut client = create_client("/tmp/service2".into()).await; + let mut client = create_client("/tmp/service".into()).await; let our_address = client.nym_address(); println!("\nservice's nym address: {our_address}"); // the httpclient we will use to broadcast our query to the blockchain From 795efb9f0b68d194cdb9bfd7e07c8d9bc373eb46 Mon Sep 17 00:00:00 2001 From: benedettadavico Date: Tue, 8 Aug 2023 11:15:30 +0200 Subject: [PATCH 088/361] Update changelog and versions for release v1.1.26 --- CHANGELOG.md | 22 +++++++++++++++++++ Cargo.lock | 18 +++++++-------- clients/native/Cargo.toml | 2 +- clients/socks5/Cargo.toml | 2 +- explorer-api/Cargo.toml | 2 +- gateway/Cargo.toml | 2 +- mixnode/Cargo.toml | 2 +- nym-api/Cargo.toml | 2 +- nym-connect/desktop/CHANGELOG.md | 6 +++++ nym-connect/desktop/package.json | 2 +- nym-connect/desktop/src-tauri/Cargo.toml | 2 +- nym-connect/desktop/src-tauri/tauri.conf.json | 2 +- .../network-requester/Cargo.toml | 2 +- .../network-statistics/Cargo.toml | 2 +- tools/nym-cli/Cargo.toml | 2 +- 15 files changed, 49 insertions(+), 21 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f938c81fe3..1ec4a60145 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,28 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https:// ## [Unreleased] +## [v1.1.24] (2023-08-08) + +- Latency based gateway selection is serial and slow ([#3710]) +- Network-requester: strip comments from allow lists ([#3625]) +- Remove (or start maintaining) `upgrade` commands from all binaries ([#3600]) +- Set sphinx as default packet type ([#3748]) +- Apply fix from feature/ephemera to develop too (#3698) ([#3742]) +- Feature/coco demos ([#3732]) +- Add updates to community list projects ([#3722]) +- Add geo-aware mixnet topology provider ([#3713]) +- Add updates to community list projects ([#3711]) + +[#3710]: https://github.com/nymtech/nym/issues/3710 +[#3625]: https://github.com/nymtech/nym/issues/3625 +[#3600]: https://github.com/nymtech/nym/issues/3600 +[#3748]: https://github.com/nymtech/nym/pull/3748 +[#3742]: https://github.com/nymtech/nym/pull/3742 +[#3732]: https://github.com/nymtech/nym/pull/3732 +[#3722]: https://github.com/nymtech/nym/pull/3722 +[#3713]: https://github.com/nymtech/nym/pull/3713 +[#3711]: https://github.com/nymtech/nym/pull/3711 + ## [v1.1.23] (2023-07-04) - nym-cli: add client identity key signing support ([#3576]) diff --git a/Cargo.lock b/Cargo.lock index f7ba594a47..dc276f492c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1895,7 +1895,7 @@ checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" [[package]] name = "explorer-api" -version = "1.1.23" +version = "1.1.24" dependencies = [ "chrono", "clap 4.2.7", @@ -3531,7 +3531,7 @@ dependencies = [ [[package]] name = "nym-api" -version = "1.1.24" +version = "1.1.25" dependencies = [ "anyhow", "async-trait", @@ -3668,7 +3668,7 @@ dependencies = [ [[package]] name = "nym-cli" -version = "1.1.23" +version = "1.1.24" dependencies = [ "anyhow", "base64 0.13.1", @@ -3734,7 +3734,7 @@ dependencies = [ [[package]] name = "nym-client" -version = "1.1.23" +version = "1.1.24" dependencies = [ "clap 4.2.7", "dirs 4.0.0", @@ -4022,7 +4022,7 @@ dependencies = [ [[package]] name = "nym-gateway" -version = "1.1.23" +version = "1.1.24" dependencies = [ "anyhow", "async-trait", @@ -4175,7 +4175,7 @@ dependencies = [ [[package]] name = "nym-mixnode" -version = "1.1.24" +version = "1.1.25" dependencies = [ "anyhow", "bs58 0.4.0", @@ -4291,7 +4291,7 @@ dependencies = [ [[package]] name = "nym-network-requester" -version = "1.1.23" +version = "1.1.24" dependencies = [ "anyhow", "async-file-watcher", @@ -4338,7 +4338,7 @@ dependencies = [ [[package]] name = "nym-network-statistics" -version = "1.1.23" +version = "1.1.24" dependencies = [ "dirs 4.0.0", "log", @@ -4497,7 +4497,7 @@ dependencies = [ [[package]] name = "nym-socks5-client" -version = "1.1.23" +version = "1.1.24" dependencies = [ "clap 4.2.7", "lazy_static", diff --git a/clients/native/Cargo.toml b/clients/native/Cargo.toml index 6064304a90..2135883752 100644 --- a/clients/native/Cargo.toml +++ b/clients/native/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "nym-client" -version = "1.1.23" +version = "1.1.24" authors = ["Dave Hrycyszyn ", "Jędrzej Stuczyński "] description = "Implementation of the Nym Client" edition = "2021" diff --git a/clients/socks5/Cargo.toml b/clients/socks5/Cargo.toml index 658d52a047..afbf36178f 100644 --- a/clients/socks5/Cargo.toml +++ b/clients/socks5/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "nym-socks5-client" -version = "1.1.23" +version = "1.1.24" authors = ["Dave Hrycyszyn "] description = "A SOCKS5 localhost proxy that converts incoming messages to Sphinx and sends them to a Nym address" edition = "2021" diff --git a/explorer-api/Cargo.toml b/explorer-api/Cargo.toml index c4c7b345bc..6dfd65903f 100644 --- a/explorer-api/Cargo.toml +++ b/explorer-api/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "explorer-api" -version = "1.1.23" +version = "1.1.24" edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/gateway/Cargo.toml b/gateway/Cargo.toml index 763b3ab676..007fcca40d 100644 --- a/gateway/Cargo.toml +++ b/gateway/Cargo.toml @@ -3,7 +3,7 @@ [package] name = "nym-gateway" -version = "1.1.23" +version = "1.1.24" authors = [ "Dave Hrycyszyn ", "Jędrzej Stuczyński ", diff --git a/mixnode/Cargo.toml b/mixnode/Cargo.toml index 9f512f0d85..8421bebb3a 100644 --- a/mixnode/Cargo.toml +++ b/mixnode/Cargo.toml @@ -3,7 +3,7 @@ [package] name = "nym-mixnode" -version = "1.1.24" +version = "1.1.25" authors = [ "Dave Hrycyszyn ", "Jędrzej Stuczyński ", diff --git a/nym-api/Cargo.toml b/nym-api/Cargo.toml index f9b378f59d..50568b440a 100644 --- a/nym-api/Cargo.toml +++ b/nym-api/Cargo.toml @@ -3,7 +3,7 @@ [package] name = "nym-api" -version = "1.1.24" +version = "1.1.25" authors = [ "Dave Hrycyszyn ", "Jędrzej Stuczyński ", diff --git a/nym-connect/desktop/CHANGELOG.md b/nym-connect/desktop/CHANGELOG.md index 1149c3b227..8a0b86151a 100644 --- a/nym-connect/desktop/CHANGELOG.md +++ b/nym-connect/desktop/CHANGELOG.md @@ -2,6 +2,12 @@ ## [Unreleased] +## [v1.1.16] (2023-08-08) + +- Uncouple network-requester <-> gateway in nym-connect and harbourmaster ([#3472]) + +[#3472]: https://github.com/nymtech/nym/issues/3472 + ## [v1.1.15] (2023-07-25) - NC Desktop - remove sentry DSN from code ([#3694]) diff --git a/nym-connect/desktop/package.json b/nym-connect/desktop/package.json index 626b415b8f..96d9e83438 100644 --- a/nym-connect/desktop/package.json +++ b/nym-connect/desktop/package.json @@ -1,6 +1,6 @@ { "name": "@nym/nym-connect", - "version": "1.1.15", + "version": "1.1.16", "main": "index.js", "license": "MIT", "scripts": { diff --git a/nym-connect/desktop/src-tauri/Cargo.toml b/nym-connect/desktop/src-tauri/Cargo.toml index e3bd49cb41..0d1089132d 100644 --- a/nym-connect/desktop/src-tauri/Cargo.toml +++ b/nym-connect/desktop/src-tauri/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "nym-connect" -version = "1.1.15" +version = "1.1.16" description = "nym-connect" authors = ["Nym Technologies SA"] license = "" diff --git a/nym-connect/desktop/src-tauri/tauri.conf.json b/nym-connect/desktop/src-tauri/tauri.conf.json index b95859ca53..5f5e86bec0 100644 --- a/nym-connect/desktop/src-tauri/tauri.conf.json +++ b/nym-connect/desktop/src-tauri/tauri.conf.json @@ -1,7 +1,7 @@ { "package": { "productName": "nym-connect", - "version": "1.1.15" + "version": "1.1.16" }, "build": { "distDir": "../dist", diff --git a/service-providers/network-requester/Cargo.toml b/service-providers/network-requester/Cargo.toml index 5b80088b30..d1d7367d85 100644 --- a/service-providers/network-requester/Cargo.toml +++ b/service-providers/network-requester/Cargo.toml @@ -3,7 +3,7 @@ [package] name = "nym-network-requester" -version = "1.1.23" +version = "1.1.24" authors.workspace = true edition.workspace = true rust-version = "1.65" diff --git a/service-providers/network-statistics/Cargo.toml b/service-providers/network-statistics/Cargo.toml index cc7024c545..7c0138ca8f 100644 --- a/service-providers/network-statistics/Cargo.toml +++ b/service-providers/network-statistics/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "nym-network-statistics" -version = "1.1.23" +version = "1.1.24" edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/tools/nym-cli/Cargo.toml b/tools/nym-cli/Cargo.toml index ea5cad9e65..9f3c0d6a2a 100644 --- a/tools/nym-cli/Cargo.toml +++ b/tools/nym-cli/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "nym-cli" -version = "1.1.23" +version = "1.1.24" authors.workspace = true edition = "2021" From 2d60a9da3f9ae671e0fcc8192bfe4ddca5cc2c30 Mon Sep 17 00:00:00 2001 From: benedettadavico Date: Tue, 8 Aug 2023 11:24:08 +0200 Subject: [PATCH 089/361] Temporary update to fix windows build --- package.json | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index 523db5363f..cad6eaf88d 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,6 @@ "ts-packages/*", "nym-wallet", "nym-connect/**", - "nym-browser-extension", "explorer", "types", "clients/validator" @@ -29,9 +28,8 @@ "tsc": "lerna run tsc --stream", "types:lint:fix": "lerna run lint:fix --scope @nymproject/types --scope @nymproject/nym-wallet-app", "audit:fix": "npm_config_yes=true npx yarn-audit-fix -- --dry-run", - "preinstall": "yarn install:copy-placeholders && yarn install:copy-storage-placeholders", - "install:copy-placeholders": "cp scripts/build/yarn/wasm-placeholder/package.json sdk/typescript/packages/nym-client-wasm", - "install:copy-storage-placeholders": "mkdir -p nym-browser-extension/storage/pkg && cp scripts/build/yarn/storage-placeholder/package.json nym-browser-extension/storage/pkg" + "preinstall": "yarn install:copy-placeholders", + "install:copy-placeholders": "cp scripts/build/yarn/wasm-placeholder/package.json sdk/typescript/packages/nym-client-wasm" }, "devDependencies": { "lerna": "^6.6.2", From 50acec575f8be5b6572b9ef31034f176ea1c4037 Mon Sep 17 00:00:00 2001 From: mfahampshire Date: Tue, 8 Aug 2023 11:49:51 +0200 Subject: [PATCH 090/361] tweak --- .../dev-portal/src/tutorials/cosmos-service/querying.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/documentation/dev-portal/src/tutorials/cosmos-service/querying.md b/documentation/dev-portal/src/tutorials/cosmos-service/querying.md index 04a6f92832..c6fb3a431b 100644 --- a/documentation/dev-portal/src/tutorials/cosmos-service/querying.md +++ b/documentation/dev-portal/src/tutorials/cosmos-service/querying.md @@ -21,7 +21,7 @@ The following happens: * `client` sends a request to `service` using the supplied `n1...` address as the Nyx account to query the balance of, and the supplied Nym address to communicate with this instance of `service`. * `service` queries the Sandbox testnet blockchain using the `broadcaster` http client. It then serialises the response and returns it using SURBs to the `client`. -All in all, quite simple. By using the service as a proxy, the client never interacts with the blockchain, thus is not revealing metadata to the operator of the chain or anyone watching incoming traffic to it. Furthermore, the client doesn't even need to share its Nym address with the service, as the service is able to reply via SURBs. +All in all, quite simple. By using the service as a proxy, the client never interacts with the blockchain, thus is not revealing metadata to the operator of the Validator nor any entities monitoring its incoming and outgoing traffic. Furthermore, the client doesn't even need to share its Nym address with the service, as the service is able to reply via SURBs. ## Creating Sandbox Account If you wish to create an account on Sandbox to use instead of the supplied account above, the easiest way is by building the `nym-cli` tool and using that to create one: From 7e27413a12232c49afeee6483f4c78204daaf1ae Mon Sep 17 00:00:00 2001 From: mfahampshire Date: Tue, 8 Aug 2023 13:59:53 +0200 Subject: [PATCH 091/361] added custom 404 page for all books --- documentation/dev-portal/book.toml | 2 +- documentation/dev-portal/src/not-found.md | 11 +++++++++++ documentation/docs/src/not-found.md | 4 ++-- documentation/operators/book.toml | 4 ++-- documentation/operators/src/not-found.md | 11 +++++++++++ 5 files changed, 27 insertions(+), 5 deletions(-) create mode 100644 documentation/dev-portal/src/not-found.md create mode 100644 documentation/operators/src/not-found.md diff --git a/documentation/dev-portal/book.toml b/documentation/dev-portal/book.toml index 3b81f82751..216492690a 100644 --- a/documentation/dev-portal/book.toml +++ b/documentation/dev-portal/book.toml @@ -84,7 +84,7 @@ git-repository-icon = "fa-github" # edit-url-template = "https://github.com/rust-lang/mdBook/edit/master/guide/{path}" site-url = "/developers/" # cname = "nymtech.net" -# input-404 = "not-found.md" # todo make custom 404 page +input-404 = "not-found.md" [output.html.fold] enable = true # whether or not to enable section folding diff --git a/documentation/dev-portal/src/not-found.md b/documentation/dev-portal/src/not-found.md new file mode 100644 index 0000000000..704da5ec19 --- /dev/null +++ b/documentation/dev-portal/src/not-found.md @@ -0,0 +1,11 @@ +# Page Not Found + +You seem to have followed a link to a page that no longer exists. Our documentation is changing and growing over time, so this page has most likely been moved somewhere else. + +For node setup guides, see the [Operator Guides book](https://nymtech.net/operators). + +For information on network architecture traffic flow, clients, the SDKs, or interacting with the Nyx blockchain see the [Technical Documentation](https://nymtech.net/docs). + +If you are looking for information on developer tutorials, app guides, and demo and community apps, check the sitemap on the left hand side of the screen. + +If you still can't find what you're looking for get in touch with us via [Matrix](https://matrix.to/#/#nym-community:nymtech.chat). \ No newline at end of file diff --git a/documentation/docs/src/not-found.md b/documentation/docs/src/not-found.md index 3d203a9bb8..af87d6dd59 100644 --- a/documentation/docs/src/not-found.md +++ b/documentation/docs/src/not-found.md @@ -2,10 +2,10 @@ You seem to have followed a link to a page that no longer exists. Our documentation is changing and growing over time, so this page has most likely been moved somewhere else. -For node setup guides, see the [Operators Guides book](https://nymtech.net/operators). +For node setup guides, see the [Operator Guides book](https://nymtech.net/operators). For developer tutorials, app guides, and demo and community apps, see the [Developer Portal](https://nymtech.net/developers). If you are looking for information on network architecture traffic flow, clients, the SDKs, or interacting with the Nyx blockchain, check the sitemap on the left hand side of the screen. -If you still can't find what you're looking for get in touch with us via [Matrix](TODO). \ No newline at end of file +If you still can't find what you're looking for get in touch with us via [Matrix](https://matrix.to/#/#nym-community:nymtech.chat). \ No newline at end of file diff --git a/documentation/operators/book.toml b/documentation/operators/book.toml index 28efe62c57..2ce49e37bb 100644 --- a/documentation/operators/book.toml +++ b/documentation/operators/book.toml @@ -92,9 +92,9 @@ additional-js = ["theme/pagetoc.js"] git-repository-url = "https://github.com/nymtech/nym" git-repository-icon = "fa-github" # edit-url-template = "https://github.com/rust-lang/mdBook/edit/master/guide/{path}" -# site-url = "/docs/" +site-url = "/operators/" # cname = "nymtech.net" -# input-404 = "not-found.md" # todo make custom 404 page +input-404 = "not-found.md" [output.html.fold] enable = true # whether or not to enable section folding diff --git a/documentation/operators/src/not-found.md b/documentation/operators/src/not-found.md new file mode 100644 index 0000000000..aafb70cba5 --- /dev/null +++ b/documentation/operators/src/not-found.md @@ -0,0 +1,11 @@ +# Page Not Found + +You seem to have followed a link to a page that no longer exists. Our documentation is changing and growing over time, so this page has most likely been moved somewhere else. + +For node setup guides, see the [Operator Guides book](https://nymtech.net/operators). + +For developer tutorials, app guides, and demo and community apps, see the [Developer Portal](https://nymtech.net/developers). + +If you are looking for information on setting up and maintaining infrastructure nodes, check the sitemap on the left hand side of the screen. + +If you still can't find what you're looking for get in touch with us via [Matrix](https://matrix.to/#/#nym-community:nymtech.chat). \ No newline at end of file From 85c22525cf99b5f99cd7d345f322a923b389f062 Mon Sep 17 00:00:00 2001 From: mfahampshire Date: Tue, 8 Aug 2023 14:10:44 +0200 Subject: [PATCH 092/361] * tweaked mixnode doc * removed token diagram from nyx validator doc --- documentation/docs/src/nodes/mixnode.md | 2 +- documentation/docs/src/nodes/validator.md | 8 +------- 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/documentation/docs/src/nodes/mixnode.md b/documentation/docs/src/nodes/mixnode.md index e849b141b1..44dc924d86 100644 --- a/documentation/docs/src/nodes/mixnode.md +++ b/documentation/docs/src/nodes/mixnode.md @@ -4,7 +4,7 @@ Mix nodes are the backbone of the mixnet. These are the nodes that perform 'mix mining', otherwise known simply as 'mixing'. -Mix nodes, after receiving a packet, decrypt its outer 'layer', and hold them for a variable amount of time before passing them to their next destination - either another mix node, or a gateway. In doing so, they 'mix' packets by sending them to their next destination in a different order than they were received. +Mix nodes, after receiving a packet, decrypt its outer 'layer' and hold it for a variable amount of time before passing it to its next destination - either another mix node, or a gateway. In doing so, they 'mix' packets by sending them to their next destination in a different order than they were received. Mix nodes are rewarded according to their quality of service, and their inclusion in the active set (i.e. the nodes that mix traffic for the next epoch) affected by this (as well as their delegation-based reputation - see [further reading](#further-reading) for more on this). diff --git a/documentation/docs/src/nodes/validator.md b/documentation/docs/src/nodes/validator.md index ceb0abdf8a..eba1666351 100644 --- a/documentation/docs/src/nodes/validator.md +++ b/documentation/docs/src/nodes/validator.md @@ -1,17 +1,11 @@ -- [x] TODO section of whitepaper - # Validators > The validator setup and maintenance guide has moved to the [Operator Guides book](https://nymtech.net/operators/nodes/validator-setup.html). -> We are currently working towards building up a closed set of reputable validators. You can ask us for coins to get in, but please don't be offended if we say no - validators are part of our system's core security and we are starting out with people we already know or who have a solid reputation. - -Validators secure the Nyx blockchain via Proof of Stake consensus. The Nyx blockchain records the ledger of NYM transactions and executes the smart contracts for distributing NYM rewards. The Nyx validators are run via the `nyxd` binary ([codebase](https://github.com/nymtech/nyxd)), maintaining a CosmWasm- and IBC-enabled blockchain. +Validators secure the Nyx blockchain via Proof of Stake consensus. The Nyx blockchain records the ledger of `NYM` transactions and executes the smart contracts for distributing `NYM` rewards. The Nyx validators are run via the `nyxd` binary ([codebase](https://github.com/nymtech/nyxd)), maintaining a CosmWasm- and IBC-enabled blockchain. The blockchain plays a supporting but fundamental role in the mixnet: the `NYM` token used to incentivise node operators is one of two native tokens of the chain, and the chain is where the [Mixnet](../nyx/mixnet-contract.md) and [Vesting](../nyx/vesting-contract.md) smart contracts are deployed. -![Nym Token Flow](../images/nym-token-flow.png) - ## Further Reading * Detailed info on Nyx Validators and token flow can be found in [Nym Reward Sharing for Mixnets document](https://nymtech.net/nym-cryptoecon-paper.pdf) in section 2.3 and 2.4. * For more detailed calculation, read our blog post [Nym Token Economics update](https://blog.nymtech.net/nym-token-economics-update-fedff0ed5267). From 88d2592a4eee329b1c05e280b1b220127ebb5879 Mon Sep 17 00:00:00 2001 From: mfahampshire Date: Tue, 8 Aug 2023 14:16:48 +0200 Subject: [PATCH 093/361] added notes to ws and socks client subsections re: rust sdk --- documentation/docs/src/clients/overview.md | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/documentation/docs/src/clients/overview.md b/documentation/docs/src/clients/overview.md index 4bfc125b5c..c8d2db1503 100644 --- a/documentation/docs/src/clients/overview.md +++ b/documentation/docs/src/clients/overview.md @@ -23,19 +23,23 @@ At present, there are three Nym clients: You need to choose which one you want incorporate into your app. Which one you use will depend largely on your preferred programming style and the purpose of your app. ### The websocket client -Your first option is the native websocket client (`nym-client`). This is a compiled program that can run on Linux, Mac OS X, and Windows machines. It runs as a persistent process on a desktop or server machine. You can connect to it with **any language that supports websockets**. +Your first option is the native websocket client (`nym-client`). This is a compiled program that can run on Linux, Mac OS X, and Windows machines. It can be run as a persistent process on a desktop or server machine. You can connect to it with **any language that supports websockets**. + +_Rust developers can import websocket client functionality into their code via the [Rust SDK](../sdk/rust.md)_. ### The webassembly client If you're working in JavaScript or Typescript in the browser, or building an [edge computing](https://en.wikipedia.org/wiki/Edge_computing) app, you'll likely want to choose the webassembly client. It's packaged and [available on the npm registry](https://www.npmjs.com/package/@nymproject/nym-client-wasm), so you can `npm install` it into your JavaScript or TypeScript application. -The webassembly client is most easily used via the [sdk](../sdk/typescript.md). +_The webassembly client is most easily used via the [Typescript SDK](../sdk/typescript.md)_. ### The SOCKS5 client -This client (`nym-socks5-client`) is useful for allowing existing applications to use the Nym mixnet without any code changes. All that's necessary is that they can use one of the SOCKS5, SOCKS4a, or SOCKS4 proxy protocols (which many applications can - crypto wallets, browsers, chat applications etc). +The `nym-socks5-client` is useful for allowing existing applications to use the Nym mixnet without any code changes. All that's necessary is that they can use one of the SOCKS5, SOCKS4a, or SOCKS4 proxy protocols (which many applications can - crypto wallets, browsers, chat applications etc). -It's less flexible as a way of writing custom applications than the other clients, but able to be used to proxy application traffic through the mixnet without having to make any code changes. +When used as a standalone client, it's less flexible as a way of writing custom applications than the other clients, but able to be used to proxy application traffic through the mixnet without having to make any code changes. + +_Rust developers can import socks client functionality into their code via the [Rust SDK](../sdk/rust.md)_. ## Commonalities between clients All Nym client packages present basically the same capabilities to the privacy application developer. They need to run as a persistent process in order to stay connected and ready to receive any incoming messages from their gateway nodes. They register and authenticate to gateways, and encrypt Sphinx packets. From 0af9b3c6c87cb92cd789c65238ac4752c320ec39 Mon Sep 17 00:00:00 2001 From: benedettadavico Date: Tue, 8 Aug 2023 14:17:55 +0200 Subject: [PATCH 094/361] Revert package.json changes needed temporary build --- package.json | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index cad6eaf88d..523db5363f 100644 --- a/package.json +++ b/package.json @@ -8,6 +8,7 @@ "ts-packages/*", "nym-wallet", "nym-connect/**", + "nym-browser-extension", "explorer", "types", "clients/validator" @@ -28,8 +29,9 @@ "tsc": "lerna run tsc --stream", "types:lint:fix": "lerna run lint:fix --scope @nymproject/types --scope @nymproject/nym-wallet-app", "audit:fix": "npm_config_yes=true npx yarn-audit-fix -- --dry-run", - "preinstall": "yarn install:copy-placeholders", - "install:copy-placeholders": "cp scripts/build/yarn/wasm-placeholder/package.json sdk/typescript/packages/nym-client-wasm" + "preinstall": "yarn install:copy-placeholders && yarn install:copy-storage-placeholders", + "install:copy-placeholders": "cp scripts/build/yarn/wasm-placeholder/package.json sdk/typescript/packages/nym-client-wasm", + "install:copy-storage-placeholders": "mkdir -p nym-browser-extension/storage/pkg && cp scripts/build/yarn/storage-placeholder/package.json nym-browser-extension/storage/pkg" }, "devDependencies": { "lerna": "^6.6.2", From 23a669c4de89c87deffc1d4f100a1dec1d1ce61f Mon Sep 17 00:00:00 2001 From: mfahampshire Date: Tue, 8 Aug 2023 14:19:29 +0200 Subject: [PATCH 095/361] updated vars in all books for next release version --- documentation/dev-portal/book.toml | 2 +- documentation/docs/book.toml | 2 +- documentation/operators/book.toml | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/documentation/dev-portal/book.toml b/documentation/dev-portal/book.toml index 216492690a..73fdb0d4fa 100644 --- a/documentation/dev-portal/book.toml +++ b/documentation/dev-portal/book.toml @@ -50,7 +50,7 @@ assets_version = "2.0.0" # do not edit: managed by `mdbook-admonish install` # code prerequisites versions minimum_rust_version = "1.66" # TODO remove this in place of develop in next release -platform_release_version = "v1.1.21" +platform_release_version = "v1.1.27" [preprocessor.last-changed] command = "mdbook-last-changed" diff --git a/documentation/docs/book.toml b/documentation/docs/book.toml index f507798295..d4097f4456 100644 --- a/documentation/docs/book.toml +++ b/documentation/docs/book.toml @@ -49,7 +49,7 @@ assets_version = "2.0.0" # do not edit: managed by `mdbook-admonish install` [preprocessor.variables.variables] minimum_rust_version = "1.66" # vars for links: TODO think on how to streamline updating -platform_release_version = "v1.1.26" +platform_release_version = "v1.1.27" wallet_release_version = "v1.2.7" [preprocessor.last-changed] diff --git a/documentation/operators/book.toml b/documentation/operators/book.toml index 2ce49e37bb..c39af18a65 100644 --- a/documentation/operators/book.toml +++ b/documentation/operators/book.toml @@ -49,8 +49,8 @@ assets_version = "2.0.0" # do not edit: managed by `mdbook-admonish install` [preprocessor.variables.variables] minimum_rust_version = "1.66" # vars for links: TODO think on how to streamline updating -platform_release_version = "v1.1.22" -wallet_release_version = "v1.2.5" +platform_release_version = "v1.1.27" +wallet_release_version = "v1.2.7" [preprocessor.last-changed] From e00910bcb8088e694586c24b964fb244ecee2d97 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jon=20H=C3=A4ggblad?= Date: Fri, 28 Jul 2023 14:48:33 +0200 Subject: [PATCH 096/361] Add geo-aware mixnet topology provider (#3713) * WIP: initial work * wupwup * WIP: experiments * Move topology provider and requests to own crate * Make sure we use the new crate everywhere * Sort Cargo.toml * Extract out some functions in geo_aware_provider * rustfmt * Add CountryGroup type * Assign unknown as well * wipwip * Add command line flag to socks5-client * Use geo-aware mixnode selection in nym-connect when in medium mode * rustfmt * clippy * Fix nym-connect build * wasm fix * Spelling --- Cargo.lock | 20 ++ Cargo.toml | 5 +- clients/socks5/src/commands/init.rs | 1 + clients/socks5/src/commands/mod.rs | 15 +- clients/socks5/src/commands/run.rs | 13 + clients/webassembly/Cargo.lock | 13 + clients/webassembly/src/client/config.rs | 1 + common/client-core/Cargo.toml | 10 +- .../client-core/src/client/base_client/mod.rs | 15 +- .../topology_control/geo_aware_provider.rs | 319 ++++++++++++++++++ .../src/client/topology_control/mod.rs | 1 + common/client-core/src/config/mod.rs | 22 +- .../src/config/old_config_v1_1_20_2.rs | 1 + .../mixnet-contract/src/mixnode.rs | 4 + common/socks5-client-core/Cargo.toml | 13 +- common/socks5-client-core/src/lib.rs | 1 + explorer-api/Cargo.toml | 17 +- explorer-api/explorer-api-requests/Cargo.toml | 12 + explorer-api/explorer-api-requests/src/lib.rs | 44 +++ .../src/country_statistics/geolocate.rs | 4 +- explorer-api/src/geo_ip/location.rs | 12 + explorer-api/src/mix_node/http.rs | 3 +- explorer-api/src/mix_node/mod.rs | 2 +- explorer-api/src/mix_node/models.rs | 38 +-- explorer-api/src/mix_nodes/http.rs | 2 +- explorer-api/src/mix_nodes/location.rs | 23 +- explorer-api/src/mix_nodes/models.rs | 4 +- nym-connect/desktop/Cargo.lock | 22 +- nym-connect/desktop/src-tauri/src/tasks.rs | 26 +- .../src/allowed_hosts/hosts.rs | 1 - 30 files changed, 568 insertions(+), 96 deletions(-) create mode 100644 common/client-core/src/client/topology_control/geo_aware_provider.rs create mode 100644 explorer-api/explorer-api-requests/Cargo.toml create mode 100644 explorer-api/explorer-api-requests/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index c2eadc94b5..1dcf4528bf 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1909,6 +1909,7 @@ dependencies = [ "maxminddb", "nym-bin-common", "nym-contracts-common", + "nym-explorer-api-requests", "nym-mixnet-contract-common", "nym-network-defaults", "nym-task", @@ -3782,6 +3783,7 @@ dependencies = [ "nym-config", "nym-credential-storage", "nym-crypto", + "nym-explorer-api-requests", "nym-gateway-client", "nym-gateway-requests", "nym-network-defaults", @@ -3792,6 +3794,7 @@ dependencies = [ "nym-topology", "nym-validator-client", "rand 0.7.3", + "reqwest", "serde", "serde_json", "sha2 0.10.6", @@ -4004,6 +4007,18 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "nym-explorer-api-requests" +version = "0.1.0" +dependencies = [ + "nym-contracts-common", + "nym-mixnet-contract-common", + "nym-validator-client", + "schemars", + "serde", + "ts-rs", +] + [[package]] name = "nym-gateway" version = "1.1.23" @@ -4520,7 +4535,9 @@ dependencies = [ "nym-bandwidth-controller", "nym-client-core", "nym-config", + "nym-contracts-common", "nym-credential-storage", + "nym-mixnet-contract-common", "nym-network-defaults", "nym-service-providers-common", "nym-socks5-proxy-helpers", @@ -4530,10 +4547,13 @@ dependencies = [ "nym-validator-client", "pin-project", "rand 0.7.3", + "reqwest", + "schemars", "serde", "tap", "thiserror", "tokio", + "url", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 666d1f76fc..198f37890e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -74,6 +74,7 @@ members = [ "common/types", "common/wasm-utils", "explorer-api", + "explorer-api/explorer-api-requests", "gateway", "gateway/gateway-requests", "integrations/bity", @@ -135,15 +136,17 @@ cw4 = { version = "=1.1.0" } cw-controllers = { version = "=1.1.0" } dotenvy = "0.15.6" generic-array = "0.14.7" -k256 = "0.13" getrandom = "0.2.10" +k256 = "0.13" lazy_static = "1.4.0" log = "0.4" once_cell = "1.7.2" rand = "0.8.5" +reqwest = "0.11.18" serde = "1.0.152" serde_json = "1.0.91" tap = "1.0.1" +tendermint-rpc = "=0.32" # same version as used by cosmrs thiserror = "1.0.38" tokio = "1.24.1" url = "2.2" diff --git a/clients/socks5/src/commands/init.rs b/clients/socks5/src/commands/init.rs index 76337b84ac..ef370c54b8 100644 --- a/clients/socks5/src/commands/init.rs +++ b/clients/socks5/src/commands/init.rs @@ -94,6 +94,7 @@ impl From for OverrideConfig { use_anonymous_replies: init_config.use_reply_surbs, fastmode: init_config.fastmode, no_cover: init_config.no_cover, + geo_routing: None, medium_toggle: false, nyxd_urls: init_config.nyxd_urls, enabled_credentials_mode: init_config.enabled_credentials_mode, diff --git a/clients/socks5/src/commands/mod.rs b/clients/socks5/src/commands/mod.rs index 5ef887a3a2..393429246d 100644 --- a/clients/socks5/src/commands/mod.rs +++ b/clients/socks5/src/commands/mod.rs @@ -16,7 +16,8 @@ use nym_client_core::client::base_client::storage::gateway_details::{ OnDiskGatewayDetails, PersistedGatewayDetails, }; use nym_client_core::client::key_manager::persistence::OnDiskKeys; -use nym_client_core::config::GatewayEndpointConfig; +use nym_client_core::client::topology_control::geo_aware_provider::CountryGroup; +use nym_client_core::config::{GatewayEndpointConfig, TopologyStructure}; use nym_client_core::error::ClientCoreError; use nym_config::OptionalSet; use nym_sphinx::params::{PacketSize, PacketType}; @@ -75,6 +76,7 @@ pub(crate) struct OverrideConfig { use_anonymous_replies: Option, fastmode: bool, no_cover: bool, + geo_routing: Option, medium_toggle: bool, nyxd_urls: Option>, enabled_credentials_mode: Option, @@ -99,6 +101,13 @@ 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)) + } else { + TopologyStructure::default() + }; + let packet_type = if args.outfox { PacketType::Outfox } else { @@ -122,6 +131,10 @@ pub(crate) fn override_config(config: Config, args: OverrideConfig) -> Config { // NOTE: see comment above about the order of the other disble cover traffic config .with_base(BaseClientConfig::with_disabled_cover_traffic, args.no_cover) .with_base(BaseClientConfig::with_packet_type, packet_type) + .with_base( + BaseClientConfig::with_topology_structure, + topology_structure, + ) .with_optional(Config::with_anonymous_replies, args.use_anonymous_replies) .with_optional(Config::with_port, args.port) .with_optional_base_custom_env( diff --git a/clients/socks5/src/commands/run.rs b/clients/socks5/src/commands/run.rs index 5d54038c06..8dd289e2bf 100644 --- a/clients/socks5/src/commands/run.rs +++ b/clients/socks5/src/commands/run.rs @@ -11,6 +11,7 @@ use clap::Args; use log::*; use nym_bin_common::version_checker::is_minor_version_compatible; use nym_client_core::client::base_client::storage::OnDiskPersistent; +use nym_client_core::client::topology_control::geo_aware_provider::CountryGroup; use nym_crypto::asymmetric::identity; use nym_socks5_client_core::NymClient; use nym_sphinx::addressing::clients::Recipient; @@ -60,6 +61,10 @@ pub(crate) struct Run { #[clap(long, hide = true)] no_cover: bool, + /// Set geo-aware mixnode selection when sending mixnet traffic, for experiments only. + #[clap(long, hide = true, value_parser = validate_country_group)] + geo_routing: Option, + /// Enable medium mixnet traffic, for experiments only. /// This includes things like disabling cover traffic, no per hop delays, etc. #[clap(long, hide = true)] @@ -82,6 +87,7 @@ impl From for OverrideConfig { use_anonymous_replies: run_config.use_anonymous_replies, fastmode: run_config.fastmode, no_cover: run_config.no_cover, + geo_routing: run_config.geo_routing, medium_toggle: run_config.medium_toggle, nyxd_urls: run_config.nyxd_urls, enabled_credentials_mode: run_config.enabled_credentials_mode, @@ -90,6 +96,13 @@ impl From for OverrideConfig { } } +fn validate_country_group(s: &str) -> Result { + match s.parse() { + Ok(cg) => Ok(cg), + Err(_) => Err(format!("failed to parse country group: {}", s)), + } +} + // this only checks compatibility between config the binary. It does not take into consideration // network version. It might do so in the future. fn version_check(cfg: &Config) -> bool { diff --git a/clients/webassembly/Cargo.lock b/clients/webassembly/Cargo.lock index 793f321a97..0ea047444e 100644 --- a/clients/webassembly/Cargo.lock +++ b/clients/webassembly/Cargo.lock @@ -2475,6 +2475,7 @@ dependencies = [ "nym-config", "nym-credential-storage", "nym-crypto", + "nym-explorer-api-requests", "nym-gateway-client", "nym-gateway-requests", "nym-network-defaults", @@ -2485,6 +2486,7 @@ dependencies = [ "nym-topology", "nym-validator-client", "rand 0.7.3", + "reqwest", "serde", "serde_json", "sha2 0.10.6", @@ -2689,6 +2691,17 @@ dependencies = [ "zeroize", ] +[[package]] +name = "nym-explorer-api-requests" +version = "0.1.0" +dependencies = [ + "nym-contracts-common", + "nym-mixnet-contract-common", + "nym-validator-client", + "schemars", + "serde", +] + [[package]] name = "nym-gateway-client" version = "0.1.0" diff --git a/clients/webassembly/src/client/config.rs b/clients/webassembly/src/client/config.rs index 07d9ab5f5f..f63d1456ac 100644 --- a/clients/webassembly/src/client/config.rs +++ b/clients/webassembly/src/client/config.rs @@ -246,6 +246,7 @@ impl From for ConfigTopology { topology.topology_resolution_timeout_ms, ), disable_refreshing: topology.disable_refreshing, + topology_structure: Default::default(), } } } diff --git a/common/client-core/Cargo.toml b/common/client-core/Cargo.toml index ab1c9b8c04..0811b08add 100644 --- a/common/client-core/Cargo.toml +++ b/common/client-core/Cargo.toml @@ -10,27 +10,29 @@ rust-version = "1.66" [dependencies] async-trait = { workspace = true } base64 = "0.21.2" -dirs = "4.0" dashmap = "5.4.0" +dirs = "4.0" futures = "0.3" humantime-serde = "1.0" log = { workspace = true } rand = { version = "0.7.3", features = ["wasm-bindgen"] } +reqwest = { workspace = true } serde = { workspace = true, features = ["derive"] } serde_json = { workspace = true } sha2 = "0.10.6" tap = "1.0.1" thiserror = "1.0.34" -url = { version ="2.2", features = ["serde"] } -tungstenite = { version = "0.13.0", default-features = false } -tokio = { version = "1.24.1", features = ["macros"]} time = "0.3.17" +tokio = { version = "1.24.1", features = ["macros"]} +tungstenite = { version = "0.13.0", default-features = false } +url = { version ="2.2", features = ["serde"] } zeroize = { workspace = true } # internal 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-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" } diff --git a/common/client-core/src/client/base_client/mod.rs b/common/client-core/src/client/base_client/mod.rs index aecb7eb5c1..a934793953 100644 --- a/common/client-core/src/client/base_client/mod.rs +++ b/common/client-core/src/client/base_client/mod.rs @@ -2,6 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 use super::received_buffer::ReceivedBufferMessage; +use super::topology_control::geo_aware_provider::GeoAwareTopologyProvider; use crate::client::base_client::storage::MixnetClientStorage; use crate::client::cover_traffic_stream::LoopCoverTrafficStream; use crate::client::inbound_messages::{InputMessage, InputMessageReceiver, InputMessageSender}; @@ -339,14 +340,20 @@ where fn setup_topology_provider( custom_provider: Option>, + provider_from_config: config::TopologyStructure, nym_api_urls: Vec, ) -> Box { // if no custom provider was ... provided ..., create one using nym-api - custom_provider.unwrap_or_else(|| { - Box::new(NymApiTopologyProvider::new( + custom_provider.unwrap_or_else(|| match provider_from_config { + config::TopologyStructure::NymApi => Box::new(NymApiTopologyProvider::new( 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, + )), }) } @@ -521,8 +528,10 @@ where let topology_provider = Self::setup_topology_provider( self.custom_topology_provider.take(), + self.config.debug.topology.topology_structure, self.config.get_nym_api_endpoints(), ); + Self::start_topology_refresher( topology_provider, self.config.debug.topology, diff --git a/common/client-core/src/client/topology_control/geo_aware_provider.rs b/common/client-core/src/client/topology_control/geo_aware_provider.rs new file mode 100644 index 0000000000..268f6612e0 --- /dev/null +++ b/common/client-core/src/client/topology_control/geo_aware_provider.rs @@ -0,0 +1,319 @@ +use std::{collections::HashMap, fmt}; + +use log::{debug, error, info}; +use nym_explorer_api_requests::PrettyDetailedMixNodeBond; +use nym_topology::{ + nym_topology_from_detailed, + provider_trait::{async_trait, TopologyProvider}, + NymTopology, +}; +use nym_validator_client::client::MixId; +use rand::{prelude::SliceRandom, thread_rng}; +use serde::{Deserialize, Serialize}; +use url::Url; + +const MIN_NODES_PER_LAYER: usize = 1; +const EXPLORER_API_MIXNODES_URL: &str = "https://explorer.nymtech.net/api/v1/mix-nodes"; + +// TODO: create a explorer-api-client +async fn fetch_mixnodes_from_explorer_api() -> Option> { + reqwest::get(EXPLORER_API_MIXNODES_URL) + .await + .ok()? + .json::>() + .await + .ok() +} + +#[derive(Copy, Clone, Hash, PartialEq, Eq, Serialize, Deserialize, Debug)] +pub enum CountryGroup { + Europe, + NorthAmerica, + SouthAmerica, + Oceania, + Asia, + Africa, + Unknown, +} + +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. + fn new(country_code: &str) -> Self { + let country_code = country_code.to_uppercase(); + use CountryGroup::*; + match country_code.as_ref() { + // Europe + "AT" => Europe, + "BG" => Europe, + "CH" => Europe, + "CY" => Europe, + "CZ" => Europe, + "DE" => Europe, + "DK" => Europe, + "ES" => Europe, + "FI" => Europe, + "FR" => Europe, + "GB" => Europe, + "GR" => Europe, + "IE" => Europe, + "IT" => Europe, + "LT" => Europe, + "LU" => Europe, + "LV" => Europe, + "MD" => Europe, + "MT" => Europe, + "NL" => Europe, + "NO" => Europe, + "PL" => Europe, + "RO" => Europe, + "SE" => Europe, + "SK" => Europe, + "TR" => Europe, + "UA" => Europe, + + // North America + "CA" => NorthAmerica, + "MX" => NorthAmerica, + "US" => NorthAmerica, + + // South America + "AR" => SouthAmerica, + "BR" => SouthAmerica, + "CL" => SouthAmerica, + "CO" => SouthAmerica, + "CR" => SouthAmerica, + "GT" => SouthAmerica, + + // Oceania + "AU" => Oceania, + + // Asia + "AM" => Asia, + "BH" => Asia, + "CN" => Asia, + "GE" => Asia, + "HK" => Asia, + "ID" => Asia, + "IL" => Asia, + "IN" => Asia, + "JP" => Asia, + "KH" => Asia, + "KR" => Asia, + "KZ" => Asia, + "MY" => Asia, + "RU" => Asia, + "SG" => Asia, + "TH" => Asia, + "VN" => Asia, + + // Africa + "SC" => Africa, + "UG" => Africa, + "ZA" => Africa, + + // And group level codes work too + "EU" => Europe, + "NA" => NorthAmerica, + "SA" => SouthAmerica, + "OC" => Oceania, + "AS" => Asia, + "AF" => Africa, + + // And some aliases + "EUROPE" => Europe, + "NORTHAMERICA" => NorthAmerica, + "SOUTHAMERICA" => SouthAmerica, + "OCEANIA" => Oceania, + "ASIA" => Asia, + "AFRICA" => Africa, + + _ => { + info!("Unknown country code: {}", country_code); + Unknown + } + } + } +} + +impl fmt::Display for CountryGroup { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + use CountryGroup::*; + match self { + Europe => write!(f, "EU"), + NorthAmerica => write!(f, "NA"), + SouthAmerica => write!(f, "SA"), + Oceania => write!(f, "OC"), + Asia => write!(f, "AS"), + Africa => write!(f, "AF"), + Unknown => write!(f, "Unknown"), + } + } +} + +impl std::str::FromStr for CountryGroup { + type Err = (); + + fn from_str(s: &str) -> Result { + let group = CountryGroup::new(s); + if group == CountryGroup::Unknown { + Err(()) + } else { + Ok(group) + } + } +} + +impl CountryGroup { + #[allow(unused)] + fn known(self) -> Option { + use CountryGroup::*; + match self { + Europe | NorthAmerica | SouthAmerica | Oceania | Asia | Africa => Some(self), + Unknown => None, + } + } +} + +fn group_mixnodes_by_country_code( + mixnodes: Vec, +) -> HashMap> { + mixnodes + .into_iter() + .fold(HashMap::>::new(), |mut acc, m| { + if let Some(ref location) = m.location { + let country_code = location.two_letter_iso_country_code.clone(); + let group_code = CountryGroup::new(country_code.as_str()); + let mixnodes = acc.entry(group_code).or_insert_with(Vec::new); + mixnodes.push(m.mix_id); + } + acc + }) +} + +fn log_mixnode_distribution(mixnodes: &HashMap>) { + let mixnode_distribution = mixnodes + .iter() + .map(|(k, v)| format!("{}: {}", k, v.len())) + .collect::>() + .join(", "); + debug!("Mixnode distribution - {}", mixnode_distribution); +} + +fn check_layer_integrity(topology: NymTopology) -> Result<(), ()> { + let mixes = topology.mixes(); + if mixes.keys().len() < 3 { + error!("Layer is missing in topology!"); + return Err(()); + } + for (layer, mixnodes) in mixes { + debug!("Layer {:?} has {} mixnodes", layer, mixnodes.len()); + if mixnodes.len() < MIN_NODES_PER_LAYER { + error!( + "There are only {} mixnodes in layer {:?}", + mixnodes.len(), + layer + ); + return Err(()); + } + } + Ok(()) +} + +pub struct GeoAwareTopologyProvider { + validator_client: nym_validator_client::client::NymApiClient, + filter_on: CountryGroup, + client_version: String, +} + +impl GeoAwareTopologyProvider { + pub fn new( + mut nym_api_urls: Vec, + client_version: String, + filter_on: CountryGroup, + ) -> GeoAwareTopologyProvider { + log::info!( + "Creating geo-aware topology provider with filter on {:?}", + filter_on + ); + nym_api_urls.shuffle(&mut thread_rng()); + + GeoAwareTopologyProvider { + validator_client: nym_validator_client::client::NymApiClient::new( + nym_api_urls[0].clone(), + ), + filter_on, + client_version, + } + } + + async fn get_topology(&self) -> Option { + let mixnodes = match self.validator_client.get_cached_active_mixnodes().await { + Err(err) => { + error!("failed to get network mixnodes - {err}"); + return None; + } + Ok(mixes) => mixes, + }; + + let gateways = match self.validator_client.get_cached_gateways().await { + Err(err) => { + error!("failed to get network gateways - {err}"); + return None; + } + Ok(gateways) => gateways, + }; + + // 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 { + error!("failed to get mixnodes from explorer-api"); + return None; + }; + + // 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. + // The reason we this instead of a straight filter is that this opens up the possibility to + // complement a small grouping with mixnodes from adjecent countries. + 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); + return None; + }; + + let mixnodes = mixnodes + .into_iter() + .filter(|m| filtered_mixnode_ids.contains(&m.mix_id())) + .collect::>(); + + let topology = nym_topology_from_detailed(mixnodes, gateways) + .filter_system_version(&self.client_version); + + // TODO: return real error type + check_layer_integrity(topology.clone()).ok()?; + + Some(topology) + } +} + +#[cfg(not(target_arch = "wasm32"))] +#[async_trait] +impl TopologyProvider for GeoAwareTopologyProvider { + // this will be manually refreshed on a timer specified inside mixnet client config + async fn get_new_topology(&mut self) -> Option { + self.get_topology().await + } +} + +#[cfg(target_arch = "wasm32")] +#[async_trait(?Send)] +impl TopologyProvider for GeoAwareTopologyProvider { + // this will be manually refreshed on a timer specified inside mixnet client config + async fn get_new_topology(&mut self) -> Option { + self.get_topology().await + } +} diff --git a/common/client-core/src/client/topology_control/mod.rs b/common/client-core/src/client/topology_control/mod.rs index 0fef523b1e..ed426dde50 100644 --- a/common/client-core/src/client/topology_control/mod.rs +++ b/common/client-core/src/client/topology_control/mod.rs @@ -10,6 +10,7 @@ use nym_topology::NymTopologyError; use std::time::Duration; mod accessor; +pub mod geo_aware_provider; pub(crate) mod nym_api_provider; // TODO: move it to config later diff --git a/common/client-core/src/config/mod.rs b/common/client-core/src/config/mod.rs index e8f08adfaa..465c227e19 100644 --- a/common/client-core/src/config/mod.rs +++ b/common/client-core/src/config/mod.rs @@ -8,7 +8,7 @@ use serde::{Deserialize, Serialize}; use std::time::Duration; use url::Url; -use crate::error::ClientCoreError; +use crate::{client::topology_control::geo_aware_provider::CountryGroup, error::ClientCoreError}; #[cfg(target_arch = "wasm32")] use wasm_bindgen::prelude::*; @@ -158,6 +158,15 @@ impl Config { self } + pub fn with_topology_structure(mut self, topology_structure: TopologyStructure) -> Self { + self.set_topology_structure(topology_structure); + self + } + + pub fn set_topology_structure(&mut self, topology_structure: TopologyStructure) { + self.debug.topology.topology_structure = topology_structure; + } + pub fn with_no_per_hop_delays(mut self, no_per_hop_delays: bool) -> Self { if no_per_hop_delays { self.set_no_per_hop_delays() @@ -466,6 +475,16 @@ pub struct Topology { /// the first valid instance. /// Supersedes `topology_refresh_rate_ms`. pub disable_refreshing: bool, + + /// Specifies the mixnode topology to be used for sending packets. + pub topology_structure: TopologyStructure, +} + +#[derive(Default, Copy, Clone, Debug, PartialEq, Serialize, Deserialize)] +pub enum TopologyStructure { + #[default] + NymApi, + GeoAware(CountryGroup), } impl Default for Topology { @@ -474,6 +493,7 @@ impl Default for Topology { topology_refresh_rate: DEFAULT_TOPOLOGY_REFRESH_RATE, topology_resolution_timeout: DEFAULT_TOPOLOGY_RESOLUTION_TIMEOUT, disable_refreshing: false, + topology_structure: TopologyStructure::default(), } } } diff --git a/common/client-core/src/config/old_config_v1_1_20_2.rs b/common/client-core/src/config/old_config_v1_1_20_2.rs index ed0cd0356a..a097b55f2a 100644 --- a/common/client-core/src/config/old_config_v1_1_20_2.rs +++ b/common/client-core/src/config/old_config_v1_1_20_2.rs @@ -267,6 +267,7 @@ impl From for Topology { topology_refresh_rate: value.topology_refresh_rate, topology_resolution_timeout: value.topology_resolution_timeout, disable_refreshing: value.disable_refreshing, + topology_structure: Default::default(), } } } diff --git a/common/cosmwasm-smart-contracts/mixnet-contract/src/mixnode.rs b/common/cosmwasm-smart-contracts/mixnet-contract/src/mixnode.rs index 248bc7e143..676098fce6 100644 --- a/common/cosmwasm-smart-contracts/mixnet-contract/src/mixnode.rs +++ b/common/cosmwasm-smart-contracts/mixnet-contract/src/mixnode.rs @@ -69,6 +69,10 @@ impl MixNodeDetails { self.bond_information.mix_id } + pub fn layer(&self) -> Layer { + self.bond_information.layer + } + pub fn is_unbonding(&self) -> bool { self.bond_information.is_unbonding } diff --git a/common/socks5-client-core/Cargo.toml b/common/socks5-client-core/Cargo.toml index 9ca792f8b1..2e1aa52b32 100644 --- a/common/socks5-client-core/Cargo.toml +++ b/common/socks5-client-core/Cargo.toml @@ -7,22 +7,27 @@ edition = "2021" [dependencies] dirs = "4.0" +futures = "0.3" log = { workspace = true } pin-project = "1.0" rand = { version = "0.7.3", features = ["wasm-bindgen"] } +reqwest = "0.11.4" +schemars = { version = "0.8", features = ["preserve_order"] } serde = { workspace = true, features = ["derive"] } # for config serialization/deserialization -thiserror = "1.0.34" tap = "1.0.1" +thiserror = "1.0.34" tokio = { version = "1.24.1", features = ["rt-multi-thread", "net", "signal"] } -futures = "0.3" +url = "2.2" -nym-client-core = { path = "../client-core", features = ["fs-surb-storage"] } nym-bandwidth-controller = { path = "../../common/bandwidth-controller" } +nym-client-core = { path = "../client-core", features = ["fs-surb-storage"] } nym-config = { path = "../config" } +nym-contracts-common = { path = "../cosmwasm-smart-contracts/contracts-common" } nym-credential-storage = { path = "../credential-storage" } +nym-mixnet-contract-common = { path = "../cosmwasm-smart-contracts/mixnet-contract" } nym-network-defaults = { path = "../network-defaults" } -nym-socks5-proxy-helpers = { path = "../socks5/proxy-helpers" } nym-service-providers-common = { path = "../../service-providers/common" } +nym-socks5-proxy-helpers = { path = "../socks5/proxy-helpers" } nym-socks5-requests = { path = "../socks5/requests" } nym-sphinx = { path = "../nymsphinx" } nym-task = { path = "../task" } diff --git a/common/socks5-client-core/src/lib.rs b/common/socks5-client-core/src/lib.rs index 2070eeaf71..74d27b69ed 100644 --- a/common/socks5-client-core/src/lib.rs +++ b/common/socks5-client-core/src/lib.rs @@ -24,6 +24,7 @@ use nym_credential_storage::storage::Storage as CredentialStorage; use nym_sphinx::addressing::clients::Recipient; use nym_sphinx::params::PacketType; use nym_task::{TaskClient, TaskManager}; + use std::error::Error; pub mod config; diff --git a/explorer-api/Cargo.toml b/explorer-api/Cargo.toml index ab0a56612b..d6941a19e1 100644 --- a/explorer-api/Cargo.toml +++ b/explorer-api/Cargo.toml @@ -8,13 +8,18 @@ edition = "2021" [dependencies] chrono = { version = "0.4.19", features = ["serde"] } clap = { version = "4.0", features = ["cargo", "derive"] } +dotenvy = "0.15.6" humantime-serde = "1.0" isocountry = "0.3.2" itertools = "0.10.3" lazy_static = "1.4.0" log = { workspace = true } +maxminddb = "0.23.0" okapi = { version = "0.7.0-rc.1", features = ["impl_json_schema"] } pretty_env_logger = "0.4.0" +rand = "0.8.5" +rand_pcg = "0.3.1" +rand_seeder = "0.2.3" reqwest = "0.11.4" rocket = { version = "0.5.0-rc.2", features = ["json"] } rocket_cors = { git="https://github.com/lawliet89/rocket_cors", rev="dfd3662c49e2f6fc37df35091cb94d82f7fb5915" } @@ -24,15 +29,11 @@ serde = "1.0.126" serde_json = "1.0.66" thiserror = "1.0.29" tokio = {version = "1.21.2", features = ["full"] } -maxminddb = "0.23.0" -dotenvy = "0.15.6" -rand = "0.8.5" -rand_seeder = "0.2.3" -rand_pcg = "0.3.1" -nym-mixnet-contract-common = { path = "../common/cosmwasm-smart-contracts/mixnet-contract" } -nym-contracts-common = { path = "../common/cosmwasm-smart-contracts/contracts-common" } -nym-network-defaults = { path = "../common/network-defaults" } nym-bin-common = { path = "../common/bin-common"} +nym-contracts-common = { path = "../common/cosmwasm-smart-contracts/contracts-common" } +nym-explorer-api-requests = { path = "explorer-api-requests" } +nym-mixnet-contract-common = { path = "../common/cosmwasm-smart-contracts/mixnet-contract" } +nym-network-defaults = { path = "../common/network-defaults" } nym-task = { path = "../common/task" } nym-validator-client = { path = "../common/client-libs/validator-client", features=["http-client"] } diff --git a/explorer-api/explorer-api-requests/Cargo.toml b/explorer-api/explorer-api-requests/Cargo.toml new file mode 100644 index 0000000000..93c8faaada --- /dev/null +++ b/explorer-api/explorer-api-requests/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "nym-explorer-api-requests" +version = "0.1.0" +edition = "2021" + +[dependencies] +nym-contracts-common = { path = "../../common/cosmwasm-smart-contracts/contracts-common" } +nym-mixnet-contract-common = { path = "../../common/cosmwasm-smart-contracts/mixnet-contract" } +nym-validator-client = { path = "../../common/client-libs/validator-client" } +schemars = { version = "0.8", features = ["preserve_order"] } +serde = { version = "1.0", features = ["derive"] } +ts-rs = { version = "6.1.2", optional = true } diff --git a/explorer-api/explorer-api-requests/src/lib.rs b/explorer-api/explorer-api-requests/src/lib.rs new file mode 100644 index 0000000000..bfcafa2860 --- /dev/null +++ b/explorer-api/explorer-api-requests/src/lib.rs @@ -0,0 +1,44 @@ +use nym_contracts_common::Percent; +use nym_mixnet_contract_common::{Addr, Coin, Layer, MixId, MixNode}; +use nym_validator_client::models::NodePerformance; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq)] +#[serde(rename_all = "snake_case")] +pub enum MixnodeStatus { + Active, // in both the active set and the rewarded set + Standby, // only in the rewarded set + Inactive, // in neither the rewarded set nor the active set +} + +#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)] +pub struct PrettyDetailedMixNodeBond { + pub mix_id: MixId, + pub location: Option, + pub status: MixnodeStatus, + pub pledge_amount: Coin, + pub total_delegation: Coin, + pub owner: Addr, + pub layer: Layer, + pub mix_node: MixNode, + pub stake_saturation: f32, + pub uncapped_saturation: f32, + pub avg_uptime: u8, + pub node_performance: NodePerformance, + pub estimated_operator_apy: f64, + pub estimated_delegators_apy: f64, + pub operating_cost: Coin, + pub profit_margin_percent: Percent, + pub family_id: Option, + pub blacklisted: bool, +} + +#[derive(Clone, Debug, JsonSchema, Serialize, Deserialize)] +pub struct Location { + pub two_letter_iso_country_code: String, + pub three_letter_iso_country_code: String, + pub country_name: String, + pub latitude: Option, + pub longitude: Option, +} diff --git a/explorer-api/src/country_statistics/geolocate.rs b/explorer-api/src/country_statistics/geolocate.rs index 8158c6e65e..a80ebc28e4 100644 --- a/explorer-api/src/country_statistics/geolocate.rs +++ b/explorer-api/src/country_statistics/geolocate.rs @@ -1,9 +1,9 @@ // Copyright 2022 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use crate::mix_nodes::location::Location; use crate::state::ExplorerApiStateContext; use log::{info, warn}; +use nym_explorer_api_requests::Location; use nym_task::TaskClient; pub(crate) struct GeoLocateTask { @@ -64,7 +64,7 @@ impl GeoLocateTask { ) { Ok(opt) => match opt { Some(location) => { - let location = Location::new(location); + let location: Location = location.into(); trace!( "{} mix nodes already located. Ip {} is located in {:#?}", diff --git a/explorer-api/src/geo_ip/location.rs b/explorer-api/src/geo_ip/location.rs index adbdb6cef3..fdb8584938 100644 --- a/explorer-api/src/geo_ip/location.rs +++ b/explorer-api/src/geo_ip/location.rs @@ -42,6 +42,18 @@ pub(crate) struct Location { pub(crate) longitude: Option, } +impl From for nym_explorer_api_requests::Location { + fn from(location: Location) -> Self { + nym_explorer_api_requests::Location { + country_name: location.name, + two_letter_iso_country_code: location.iso_alpha2, + three_letter_iso_country_code: location.iso_alpha3, + latitude: location.latitude, + longitude: location.longitude, + } + } +} + impl GeoIp { pub fn new() -> Self { let db_path = std::env::var("GEOIP_DB_PATH").unwrap_or_else(|e| { diff --git a/explorer-api/src/mix_node/http.rs b/explorer-api/src/mix_node/http.rs index 137c6250ed..e00ed8ff82 100644 --- a/explorer-api/src/mix_node/http.rs +++ b/explorer-api/src/mix_node/http.rs @@ -6,9 +6,10 @@ use crate::mix_node::delegations::{ }; use crate::mix_node::econ_stats::retrieve_mixnode_econ_stats; use crate::mix_node::models::{ - EconomicDynamicsStats, NodeDescription, NodeStats, PrettyDetailedMixNodeBond, SummedDelegations, + EconomicDynamicsStats, NodeDescription, NodeStats, SummedDelegations, }; use crate::state::ExplorerApiStateContext; +use nym_explorer_api_requests::PrettyDetailedMixNodeBond; use nym_mixnet_contract_common::{Delegation, MixId}; use reqwest::Error as ReqwestError; use rocket::response::status::NotFound; diff --git a/explorer-api/src/mix_node/mod.rs b/explorer-api/src/mix_node/mod.rs index 88c3b1cd71..dac365c71d 100644 --- a/explorer-api/src/mix_node/mod.rs +++ b/explorer-api/src/mix_node/mod.rs @@ -1,4 +1,4 @@ pub(crate) mod delegations; pub(crate) mod econ_stats; pub(crate) mod http; -pub(crate) mod models; +pub mod models; diff --git a/explorer-api/src/mix_node/models.rs b/explorer-api/src/mix_node/models.rs index 94ae6558d0..4adfa6dd86 100644 --- a/explorer-api/src/mix_node/models.rs +++ b/explorer-api/src/mix_node/models.rs @@ -2,49 +2,15 @@ // SPDX-License-Identifier: Apache-2.0 use crate::cache::Cache; -use crate::mix_nodes::location::Location; -use nym_contracts_common::Percent; use nym_mixnet_contract_common::Delegation; -use nym_mixnet_contract_common::{Addr, Coin, Layer, MixId, MixNode}; -use nym_validator_client::models::{NodePerformance, SelectionChance}; +use nym_mixnet_contract_common::{Addr, Coin, MixId}; +use nym_validator_client::models::SelectionChance; use serde::Deserialize; use serde::Serialize; use std::sync::Arc; use std::time::SystemTime; use tokio::sync::RwLock; -#[derive(Clone, Debug, Serialize, JsonSchema, PartialEq)] -#[serde(rename_all = "snake_case")] -pub(crate) enum MixnodeStatus { - Active, // in both the active set and the rewarded set - Standby, // only in the rewarded set - Inactive, // in neither the rewarded set nor the active set -} - -#[derive(Clone, Debug, Serialize, JsonSchema)] -pub(crate) struct PrettyDetailedMixNodeBond { - // I leave this to @MS to refactor this type as a lot of things here are redundant thanks to - // the existence of `MixNodeDetails` - pub mix_id: MixId, - pub location: Option, - pub status: MixnodeStatus, - pub pledge_amount: Coin, - pub total_delegation: Coin, - pub owner: Addr, - pub layer: Layer, - pub mix_node: MixNode, - pub stake_saturation: f32, - pub uncapped_saturation: f32, - pub avg_uptime: u8, - pub node_performance: NodePerformance, - pub estimated_operator_apy: f64, - pub estimated_delegators_apy: f64, - pub operating_cost: Coin, - pub profit_margin_percent: Percent, - pub family_id: Option, - pub blacklisted: bool, -} - #[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq, JsonSchema)] pub struct SummedDelegations { pub owner: Addr, diff --git a/explorer-api/src/mix_nodes/http.rs b/explorer-api/src/mix_nodes/http.rs index ef5570e455..a36843b66f 100644 --- a/explorer-api/src/mix_nodes/http.rs +++ b/explorer-api/src/mix_nodes/http.rs @@ -1,6 +1,6 @@ -use crate::mix_node::models::{MixnodeStatus, PrettyDetailedMixNodeBond}; use crate::mix_nodes::models::{MixNodeActiveSetSummary, MixNodeSummary}; use crate::state::ExplorerApiStateContext; +use nym_explorer_api_requests::{MixnodeStatus, PrettyDetailedMixNodeBond}; use rocket::serde::json::Json; use rocket::{Route, State}; use rocket_okapi::okapi::openapi3::OpenApi; diff --git a/explorer-api/src/mix_nodes/location.rs b/explorer-api/src/mix_nodes/location.rs index b876cdc792..4908564697 100644 --- a/explorer-api/src/mix_nodes/location.rs +++ b/explorer-api/src/mix_nodes/location.rs @@ -1,7 +1,7 @@ // Copyright 2021 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use crate::geo_ip::location; +use nym_explorer_api_requests::Location; use nym_mixnet_contract_common::MixId; use serde::{Deserialize, Serialize}; use std::collections::HashMap; @@ -31,27 +31,6 @@ pub(crate) struct LocationCacheItem { pub(crate) valid_until: SystemTime, } -#[derive(Clone, Debug, JsonSchema, Serialize, Deserialize)] -pub(crate) struct Location { - pub(crate) two_letter_iso_country_code: String, - pub(crate) three_letter_iso_country_code: String, - pub(crate) country_name: String, - pub(crate) latitude: Option, - pub(crate) longitude: Option, -} - -impl Location { - pub(crate) fn new(location: location::Location) -> Self { - Location { - country_name: location.name, - two_letter_iso_country_code: location.iso_alpha2, - three_letter_iso_country_code: location.iso_alpha3, - latitude: location.latitude, - longitude: location.longitude, - } - } -} - impl LocationCacheItem { pub(crate) fn new_from_location(location: Option) -> Self { LocationCacheItem { diff --git a/explorer-api/src/mix_nodes/models.rs b/explorer-api/src/mix_nodes/models.rs index bfead42c8c..b107bb2168 100644 --- a/explorer-api/src/mix_nodes/models.rs +++ b/explorer-api/src/mix_nodes/models.rs @@ -5,6 +5,7 @@ use std::collections::{HashMap, HashSet}; use std::sync::Arc; use std::time::{Duration, SystemTime}; +use nym_explorer_api_requests::{Location, MixnodeStatus, PrettyDetailedMixNodeBond}; use nym_mixnet_contract_common::rewarding::helpers::truncate_reward; use nym_mixnet_contract_common::MixId; use serde::Serialize; @@ -14,8 +15,7 @@ use crate::helpers::best_effort_small_dec_to_f64; use nym_validator_client::models::MixNodeBondAnnotated; use super::utils::family_numerical_id; -use crate::mix_node::models::{MixnodeStatus, PrettyDetailedMixNodeBond}; -use crate::mix_nodes::location::{Location, LocationCache, LocationCacheItem}; +use crate::mix_nodes::location::{LocationCache, LocationCacheItem}; use crate::mix_nodes::CACHE_ENTRY_TTL; #[derive(Clone, Debug, Serialize, JsonSchema)] diff --git a/nym-connect/desktop/Cargo.lock b/nym-connect/desktop/Cargo.lock index 5897f51672..c522d297d1 100644 --- a/nym-connect/desktop/Cargo.lock +++ b/nym-connect/desktop/Cargo.lock @@ -3559,6 +3559,7 @@ dependencies = [ "nym-config", "nym-credential-storage", "nym-crypto", + "nym-explorer-api-requests", "nym-gateway-client", "nym-gateway-requests", "nym-network-defaults", @@ -3569,6 +3570,7 @@ dependencies = [ "nym-topology", "nym-validator-client", "rand 0.7.3", + "reqwest", "serde", "serde_json", "sha2 0.10.6", @@ -3780,6 +3782,17 @@ dependencies = [ "zeroize", ] +[[package]] +name = "nym-explorer-api-requests" +version = "0.1.0" +dependencies = [ + "nym-contracts-common", + "nym-mixnet-contract-common", + "nym-validator-client", + "schemars", + "serde", +] + [[package]] name = "nym-gateway-client" version = "0.1.0" @@ -3979,7 +3992,9 @@ dependencies = [ "nym-bandwidth-controller", "nym-client-core", "nym-config", + "nym-contracts-common", "nym-credential-storage", + "nym-mixnet-contract-common", "nym-network-defaults", "nym-service-providers-common", "nym-socks5-proxy-helpers", @@ -3989,10 +4004,13 @@ dependencies = [ "nym-validator-client", "pin-project", "rand 0.7.3", + "reqwest", + "schemars", "serde", "tap", "thiserror", "tokio", + "url", ] [[package]] @@ -5121,9 +5139,9 @@ checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" [[package]] name = "reqwest" -version = "0.11.15" +version = "0.11.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ba30cc2c0cd02af1222ed216ba659cdb2f879dfe3181852fe7c50b1d0005949" +checksum = "cde824a14b7c14f85caff81225f411faacc04a2013f41670f41443742b1c1c55" dependencies = [ "base64 0.21.2", "bytes", diff --git a/nym-connect/desktop/src-tauri/src/tasks.rs b/nym-connect/desktop/src-tauri/src/tasks.rs index 8c69b1be7e..06b0809c2f 100644 --- a/nym-connect/desktop/src-tauri/src/tasks.rs +++ b/nym-connect/desktop/src-tauri/src/tasks.rs @@ -1,17 +1,23 @@ use futures::{channel::mpsc, StreamExt}; -use nym_client_core::client::base_client::storage::gateway_details::GatewayDetailsStore; -use nym_client_core::client::base_client::storage::{MixnetClientStorage, OnDiskPersistent}; -use nym_client_core::{config::GatewayEndpointConfig, error::ClientCoreStatusMessage}; -use nym_socks5_client_core::NymClient as Socks5NymClient; -use nym_socks5_client_core::Socks5ControlMessageSender; +use nym_client_core::{ + client::{ + base_client::storage::{ + gateway_details::GatewayDetailsStore, MixnetClientStorage, OnDiskPersistent, + }, + topology_control::geo_aware_provider::CountryGroup, + }, + config::{GatewayEndpointConfig, TopologyStructure}, + error::ClientCoreStatusMessage, +}; +use nym_socks5_client_core::{NymClient as Socks5NymClient, Socks5ControlMessageSender}; use nym_sphinx::params::PacketSize; use nym_task::manager::TaskStatus; use std::sync::Arc; use tap::TapFallible; use tokio::sync::RwLock; -use crate::config::{Config, PrivacyLevel}; use crate::{ + config::{Config, PrivacyLevel}, error::Result, events::{self, emit_event, emit_status_event}, models::{ConnectionStatusKind, ConnectivityTestResult}, @@ -46,6 +52,14 @@ fn override_config_from_env(config: &mut Config, privacy_level: &PrivacyLevel) { log::warn!("Disabling per-hop delay"); config.core.base.set_no_per_hop_delays(); + + // TODO: selectable in the UI + let default_country_group = CountryGroup::Europe; + log::warn!("Using geo-aware mixnode selection: {default_country_group}"); + config + .core + .base + .set_topology_structure(TopologyStructure::GeoAware(default_country_group)); } } diff --git a/service-providers/network-requester/src/allowed_hosts/hosts.rs b/service-providers/network-requester/src/allowed_hosts/hosts.rs index 5c48369d0d..11aa3dd276 100644 --- a/service-providers/network-requester/src/allowed_hosts/hosts.rs +++ b/service-providers/network-requester/src/allowed_hosts/hosts.rs @@ -136,7 +136,6 @@ impl HostsStore { }) .map(Host::from) .collect(); - dbg!(&hosts); Ok(hosts) } } From 53db18df2c4e29359daef186f9cdb51c6e02f2ca Mon Sep 17 00:00:00 2001 From: mfahampshire Date: Wed, 2 Aug 2023 16:26:41 +0200 Subject: [PATCH 097/361] rephrased intro --- documentation/dev-portal/src/SUMMARY.md | 2 ++ documentation/dev-portal/src/introduction.md | 4 ++-- documentation/dev-portal/src/quickstart/chat-demo.md | 5 +++++ .../dev-portal/src/quickstart/cred-playground.md | 5 +++++ documentation/dev-portal/src/quickstart/overview.md | 12 +++--------- 5 files changed, 17 insertions(+), 11 deletions(-) create mode 100644 documentation/dev-portal/src/quickstart/chat-demo.md create mode 100644 documentation/dev-portal/src/quickstart/cred-playground.md diff --git a/documentation/dev-portal/src/SUMMARY.md b/documentation/dev-portal/src/SUMMARY.md index aeae804662..0e0c02bc58 100644 --- a/documentation/dev-portal/src/SUMMARY.md +++ b/documentation/dev-portal/src/SUMMARY.md @@ -11,6 +11,8 @@ # Quickstart - [Overview](quickstart/overview.md) +- [Chat demo (webapp)](quickstart/chat-demo.md) +- [Coconut Credential Playground (webapp)](quickstart/cred-playground.md) - [SOCKS Proxy (CLI)](quickstart/socks-proxy.md) - [NymConnect Beta (GUI)](quickstart/nymconnect-gui.md) diff --git a/documentation/dev-portal/src/introduction.md b/documentation/dev-portal/src/introduction.md index d679a77c53..7335fab32b 100644 --- a/documentation/dev-portal/src/introduction.md +++ b/documentation/dev-portal/src/introduction.md @@ -1,5 +1,5 @@ # Introduction -Welcome to the Nym Developer Portal, containing the resources and tools needed to build privacy enhanced apps (PEApps) with Nym's technology. Whether you're a seasoned developer or just getting started, our portal is designed to help you create decentralized applications and services utilising the Nym mixnet. +Welcome to the Nym Developer Portal, containing quickstart resources, user manuals, integration information, and tutorials outlining to start building privacy enhanced apps. -Join our community of developers and start building the decentralized future today! +For more in-depth information about nodes, network traffic flows, etc check out the [docs](https://nymtech.net/docs). diff --git a/documentation/dev-portal/src/quickstart/chat-demo.md b/documentation/dev-portal/src/quickstart/chat-demo.md new file mode 100644 index 0000000000..6e2f5c244a --- /dev/null +++ b/documentation/dev-portal/src/quickstart/chat-demo.md @@ -0,0 +1,5 @@ +# Chat demo (webapp) + +You can find a browser-based 'hello world' chat app [here](https://chat-demo.nymtech.net). + +Either open in two browser windows and send messages to yourself, or share with a friend and send messages to each other through the mixnet. diff --git a/documentation/dev-portal/src/quickstart/cred-playground.md b/documentation/dev-portal/src/quickstart/cred-playground.md new file mode 100644 index 0000000000..aa2220940c --- /dev/null +++ b/documentation/dev-portal/src/quickstart/cred-playground.md @@ -0,0 +1,5 @@ +# Coconut Credential Playground (webapp) + +There is a coconut-scheme based Credential Library playground [here](https://coco-demo.nymtech.net/). This is a WASM implementation of our Coconut libraries which generate raw Coconut credentials. Test it to create and re-randomize your own credentials. + +For more information on what is happening here check out the [Coconut docs](https://nymtech.net/docs/coconut.html). diff --git a/documentation/dev-portal/src/quickstart/overview.md b/documentation/dev-portal/src/quickstart/overview.md index b2ac4199c9..534ed60fe5 100644 --- a/documentation/dev-portal/src/quickstart/overview.md +++ b/documentation/dev-portal/src/quickstart/overview.md @@ -1,13 +1,7 @@ # Overview -There are multiple options to quickly connect to Nym and see the network in action without the need for any code changes to your application. At most, these involve running Nym as a second process alongside an existing application in order to send traffic through the mixnet. +There are multiple options to quickly connect to Nym and play with both the mixnet and credentials on the Sandbox testnet. -Demo applications: -* a browser-based 'hello world' [chat application](https://chat-demo.nymtech.net). Either open in two browser windows and send messages to yourself, or share with a friend and send messages to each other through the mixnet! -* a Coconut-scheme based [Credential Library](https://coco-demo.nymtech.net/). This is a WASM implementation of our Coconut libraries which generate raw Coconut credentials. Test it to create and re-randomize your own credentials! -
-Proxy traffic with the Nym Socks5 client: -* set up a plug-and-play connection with the [NymConnect](./nymconnect-gui.md) GUI for proxying Telegram, Electrum, Keybase or Blockstream Green traffic through the mixnet (~2 minutes). -* [Download and run](./socks-proxy.md) the Nym Socks5 client via the CLI, for other desktop applications with SOCKS5 connection options (~30 minutes). +At most, these involve running Nym as a second process alongside an existing application in order to send traffic through the mixnet, most are either interact webpages or a standalone app. -If you've already covered the information in this section, or want to jump straight into integrating/ a Nym connection into an existing application, head to the [Integrations](../integrations/integration-options.md) section. +If you've already covered the information in this section, or want to jump straight into integrating/ a Nym connection into an existing application, head to the [Integrations](../integrations/integration-options.md) section. From 122612fa03441c5758c154a94b4d3b0656189580 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bogdan-=C8=98tefan=20Neac=C5=9Fu?= Date: Mon, 7 Aug 2023 12:27:31 +0300 Subject: [PATCH 098/361] Apply fix from feature/ephemera to develop too (#3698) (#3742) --- common/nymsphinx/params/src/packet_sizes.rs | 39 ++++++++++++++++++--- contracts/Cargo.lock | 6 ++++ nym-connect/desktop/Cargo.lock | 2 -- nym-wallet/Cargo.lock | 17 +++++++++ 4 files changed, 57 insertions(+), 7 deletions(-) diff --git a/common/nymsphinx/params/src/packet_sizes.rs b/common/nymsphinx/params/src/packet_sizes.rs index ecae71993d..58d9681efc 100644 --- a/common/nymsphinx/params/src/packet_sizes.rs +++ b/common/nymsphinx/params/src/packet_sizes.rs @@ -2,10 +2,10 @@ // SPDX-License-Identifier: Apache-2.0 use crate::PacketType; -use nym_sphinx_types::{ - header::HEADER_SIZE, MIN_PACKET_SIZE, MIX_PARAMS_LEN, OUTFOX_PACKET_OVERHEAD, - PAYLOAD_OVERHEAD_SIZE, -}; +#[cfg(feature = "sphinx")] +use nym_sphinx_types::{header::HEADER_SIZE, PAYLOAD_OVERHEAD_SIZE}; +#[cfg(feature = "outfox")] +use nym_sphinx_types::{MIN_PACKET_SIZE, MIX_PARAMS_LEN, OUTFOX_PACKET_OVERHEAD}; use serde::{Deserialize, Serialize}; use std::cmp::Ordering; use std::convert::TryFrom; @@ -14,6 +14,7 @@ use std::str::FromStr; use thiserror::Error; // each sphinx packet contains mandatory header and payload padding + markers +#[cfg(feature = "sphinx")] const SPHINX_PACKET_OVERHEAD: usize = HEADER_SIZE + PAYLOAD_OVERHEAD_SIZE; // it's up to the smart people to figure those values out : ) @@ -22,16 +23,23 @@ const SPHINX_PACKET_OVERHEAD: usize = HEADER_SIZE + PAYLOAD_OVERHEAD_SIZE; // TODO: I'm not entirely sure if we can easily extract `::NonceSize` // into a const usize before relevant stuff is stabilised in rust... - +#[cfg(feature = "sphinx")] const ACK_IV_SIZE: usize = 16; +#[cfg(feature = "sphinx")] const ACK_PACKET_SIZE: usize = ACK_IV_SIZE + crate::FRAG_ID_LEN + SPHINX_PACKET_OVERHEAD; +#[cfg(feature = "sphinx")] const REGULAR_PACKET_SIZE: usize = 2 * 1024 + SPHINX_PACKET_OVERHEAD; +#[cfg(feature = "sphinx")] const EXTENDED_PACKET_SIZE_8: usize = 8 * 1024 + SPHINX_PACKET_OVERHEAD; +#[cfg(feature = "sphinx")] const EXTENDED_PACKET_SIZE_16: usize = 16 * 1024 + SPHINX_PACKET_OVERHEAD; +#[cfg(feature = "sphinx")] const EXTENDED_PACKET_SIZE_32: usize = 32 * 1024 + SPHINX_PACKET_OVERHEAD; +#[cfg(feature = "outfox")] const OUTFOX_ACK_PACKET_SIZE: usize = MIN_PACKET_SIZE + OUTFOX_PACKET_OVERHEAD; +#[cfg(feature = "outfox")] const OUTFOX_REGULAR_PACKET_SIZE: usize = 2 * 1024 + OUTFOX_PACKET_OVERHEAD; #[derive(Debug, Error)] @@ -154,39 +162,55 @@ impl TryFrom for PacketSize { impl PacketSize { pub const fn size(self) -> usize { + #[allow(unreachable_patterns)] match self { + #[cfg(feature = "sphinx")] PacketSize::RegularPacket => REGULAR_PACKET_SIZE, + #[cfg(feature = "sphinx")] PacketSize::AckPacket => ACK_PACKET_SIZE, + #[cfg(feature = "sphinx")] PacketSize::ExtendedPacket8 => EXTENDED_PACKET_SIZE_8, + #[cfg(feature = "sphinx")] PacketSize::ExtendedPacket16 => EXTENDED_PACKET_SIZE_16, + #[cfg(feature = "sphinx")] PacketSize::ExtendedPacket32 => EXTENDED_PACKET_SIZE_32, + #[cfg(feature = "outfox")] PacketSize::OutfoxRegularPacket => OUTFOX_REGULAR_PACKET_SIZE, + #[cfg(feature = "outfox")] PacketSize::OutfoxAckPacket => OUTFOX_ACK_PACKET_SIZE, + _ => 0, } } pub const fn header_size(&self) -> usize { + #[allow(unreachable_patterns)] match self { + #[cfg(feature = "sphinx")] PacketSize::RegularPacket | PacketSize::AckPacket | PacketSize::ExtendedPacket8 | PacketSize::ExtendedPacket16 | PacketSize::ExtendedPacket32 => HEADER_SIZE, + #[cfg(feature = "outfox")] PacketSize::OutfoxRegularPacket | PacketSize::OutfoxAckPacket => MIX_PARAMS_LEN, + _ => 0, } } pub const fn payload_overhead(&self) -> usize { #[allow(unreachable_patterns)] match self { + #[cfg(feature = "sphinx")] PacketSize::RegularPacket | PacketSize::AckPacket | PacketSize::ExtendedPacket8 | PacketSize::ExtendedPacket16 | PacketSize::ExtendedPacket32 => PAYLOAD_OVERHEAD_SIZE, + #[cfg(feature = "outfox")] PacketSize::OutfoxRegularPacket | PacketSize::OutfoxAckPacket => { OUTFOX_PACKET_OVERHEAD - MIX_PARAMS_LEN // Mix params are calculated into the total overhead so we take them out here } + _ => 0, } } @@ -244,11 +268,16 @@ impl PacketSize { plaintext_size: usize, packet_type: PacketType, ) -> Result { + #[allow(unreachable_patterns)] let overhead = match packet_type { + #[cfg(feature = "sphinx")] PacketType::Mix => SPHINX_PACKET_OVERHEAD, #[allow(deprecated)] + #[cfg(feature = "sphinx")] PacketType::Vpn => SPHINX_PACKET_OVERHEAD, + #[cfg(feature = "outfox")] PacketType::Outfox => OUTFOX_PACKET_OVERHEAD, + _ => 0, }; let packet_size = plaintext_size + overhead; Self::get_type(packet_size) diff --git a/contracts/Cargo.lock b/contracts/Cargo.lock index 1a87652242..ebeae6d7ea 100644 --- a/contracts/Cargo.lock +++ b/contracts/Cargo.lock @@ -234,6 +234,12 @@ dependencies = [ "libc", ] +[[package]] +name = "crunchy" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" + [[package]] name = "crypto-bigint" version = "0.4.9" diff --git a/nym-connect/desktop/Cargo.lock b/nym-connect/desktop/Cargo.lock index c522d297d1..947ecbf73c 100644 --- a/nym-connect/desktop/Cargo.lock +++ b/nym-connect/desktop/Cargo.lock @@ -4181,8 +4181,6 @@ dependencies = [ name = "nym-sphinx-types" version = "0.2.0" dependencies = [ - "nym-outfox", - "sphinx-packet", "thiserror", ] diff --git a/nym-wallet/Cargo.lock b/nym-wallet/Cargo.lock index 2e7df7a43e..b9d551fd84 100644 --- a/nym-wallet/Cargo.lock +++ b/nym-wallet/Cargo.lock @@ -101,6 +101,12 @@ dependencies = [ "password-hash", ] +[[package]] +name = "arrayref" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4c527152e37cf757a3f78aae5a06fbeefdb07ccc535c980a3208ee3060dd544" + [[package]] name = "async-trait" version = "0.1.64" @@ -464,6 +470,16 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "chacha" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddf3c081b5fba1e5615640aae998e0fbd10c24cbd897ee39ed754a77601a4862" +dependencies = [ + "byteorder", + "keystream", +] + [[package]] name = "cipher" version = "0.4.4" @@ -3120,6 +3136,7 @@ dependencies = [ name = "nym-sphinx-types" version = "0.2.0" dependencies = [ + "sphinx-packet", "thiserror", ] From 5620fd7009e8b4dd54c25ca6fac59ee53c4021da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bogdan-=C8=98tefan=20Neac=C8=99u?= Date: Mon, 7 Aug 2023 15:52:03 +0300 Subject: [PATCH 099/361] Set sphinx as default packet type --- common/crypto/Cargo.toml | 1 + common/nymsphinx/Cargo.toml | 5 + common/nymsphinx/framing/Cargo.toml | 5 +- common/nymsphinx/params/Cargo.toml | 1 + common/nymsphinx/types/Cargo.toml | 1 + contracts/Cargo.lock | 236 ++++++++++++++++++++++++++-- nym-connect/desktop/Cargo.lock | 2 + 7 files changed, 233 insertions(+), 18 deletions(-) diff --git a/common/crypto/Cargo.toml b/common/crypto/Cargo.toml index 27702da016..992005f2d4 100644 --- a/common/crypto/Cargo.toml +++ b/common/crypto/Cargo.toml @@ -34,6 +34,7 @@ nym-pemstore = { path = "../../common/pemstore", version = "0.3.0" } rand_chacha = "0.2" [features] +default = ["sphinx"] serde = ["serde_crate", "serde_bytes", "ed25519-dalek/serde", "x25519-dalek/serde"] asymmetric = ["x25519-dalek", "ed25519-dalek", "zeroize"] hashing = ["blake3", "digest", "hkdf", "hmac", "generic-array"] diff --git a/common/nymsphinx/Cargo.toml b/common/nymsphinx/Cargo.toml index df93301876..955051b267 100644 --- a/common/nymsphinx/Cargo.toml +++ b/common/nymsphinx/Cargo.toml @@ -40,3 +40,8 @@ path = "framing" [target."cfg(not(target_arch = \"wasm32\"))".dependencies.tokio] version = "1.24.1" features = ["sync"] + +[features] +default = ["sphinx"] +sphinx = ["nym-crypto/sphinx", "nym-sphinx-params/sphinx", "nym-sphinx-types/sphinx"] +outfox = ["nym-crypto/outfox", "nym-sphinx-params/outfox", "nym-sphinx-types/outfox"] diff --git a/common/nymsphinx/framing/Cargo.toml b/common/nymsphinx/framing/Cargo.toml index b510926555..3915f204a2 100644 --- a/common/nymsphinx/framing/Cargo.toml +++ b/common/nymsphinx/framing/Cargo.toml @@ -12,5 +12,6 @@ bytes = "1.0" tokio-util = { version = "0.7.4", features = ["codec"] } thiserror = "1.0.37" -nym-sphinx-types = { path = "../types" } -nym-sphinx-params = { path = "../params" } +nym-sphinx-types = { path = "../types", features = ["sphinx", "outfox"] } +nym-sphinx-params = { path = "../params", features = ["sphinx", "outfox"] } + diff --git a/common/nymsphinx/params/Cargo.toml b/common/nymsphinx/params/Cargo.toml index 57872bb0ca..9d9a5a24e2 100644 --- a/common/nymsphinx/params/Cargo.toml +++ b/common/nymsphinx/params/Cargo.toml @@ -15,5 +15,6 @@ nym-crypto = { path = "../../crypto", features = ["hashing", "symmetric"] } nym-sphinx-types = { path = "../types" } [features] +default = ["sphinx"] sphinx = ["nym-crypto/sphinx", "nym-sphinx-types/outfox"] outfox = ["nym-crypto/outfox", "nym-sphinx-types/outfox"] diff --git a/common/nymsphinx/types/Cargo.toml b/common/nymsphinx/types/Cargo.toml index 0569ef1406..ae348113a0 100644 --- a/common/nymsphinx/types/Cargo.toml +++ b/common/nymsphinx/types/Cargo.toml @@ -13,5 +13,6 @@ nym-outfox = { path = "../../../nym-outfox", optional = true } thiserror = "1" [features] +default = ["sphinx"] sphinx = ["sphinx-packet"] outfox = ["nym-outfox"] diff --git a/contracts/Cargo.lock b/contracts/Cargo.lock index ebeae6d7ea..0f56b6f7a9 100644 --- a/contracts/Cargo.lock +++ b/contracts/Cargo.lock @@ -2,6 +2,19 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "aes" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e8b47f52ea9bae42228d07ec09eb676433d7c4ed1ebdf0f1d1c29ed446f1ab8" +dependencies = [ + "cfg-if", + "cipher", + "cpufeatures", + "ctr", + "opaque-debug 0.3.0", +] + [[package]] name = "ahash" version = "0.7.6" @@ -19,6 +32,12 @@ version = "1.0.70" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7de8ce5e0f9f8d88245311066a578d72b7af3e7088f32783804676302df237e4" +[[package]] +name = "arrayref" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b4930d2cb77ce62f89ee5d5289b4ac049559b1c45539271f5ed4fdc7db34545" + [[package]] name = "autocfg" version = "1.1.0" @@ -55,13 +74,25 @@ version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" +[[package]] +name = "blake2" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94cb07b0da6a73955f8fb85d24c466778e70cda767a568229b104f0264089330" +dependencies = [ + "byte-tools", + "crypto-mac 0.7.0", + "digest 0.8.1", + "opaque-debug 0.2.3", +] + [[package]] name = "block-buffer" version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" dependencies = [ - "generic-array", + "generic-array 0.14.6", ] [[package]] @@ -70,7 +101,7 @@ version = "0.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" dependencies = [ - "generic-array", + "generic-array 0.14.6", ] [[package]] @@ -91,6 +122,12 @@ version = "3.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b1ce199063694f33ffb7dd4e0ee620741495c32833cde5aa08f02a0bf96f0c8" +[[package]] +name = "byte-tools" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3b5ca7a04898ad4bcd41c90c5285445ff5b791899bb1b0abdd2a2aa791211d7" + [[package]] name = "byteorder" version = "1.4.3" @@ -118,6 +155,25 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "chacha" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddf3c081b5fba1e5615640aae998e0fbd10c24cbd897ee39ed754a77601a4862" +dependencies = [ + "byteorder", + "keystream", +] + +[[package]] +name = "cipher" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ee52072ec15386f770805afd189a01c8841be8696bed250fa2f13c4c0d6dfb7" +dependencies = [ + "generic-array 0.14.6", +] + [[package]] name = "coconut-test" version = "0.1.0" @@ -246,9 +302,9 @@ version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ef2b4b23cddf68b89b8f8069890e8c270d54e2d5fe1b143820234805e4cb17ef" dependencies = [ - "generic-array", + "generic-array 0.14.6", "rand_core 0.6.4", - "subtle", + "subtle 2.4.1", "zeroize", ] @@ -258,10 +314,39 @@ version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" dependencies = [ - "generic-array", + "generic-array 0.14.6", "typenum", ] +[[package]] +name = "crypto-mac" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4434400df11d95d556bac068ddfedd482915eb18fe8bea89bc80b6e4b1c179e5" +dependencies = [ + "generic-array 0.12.4", + "subtle 1.0.0", +] + +[[package]] +name = "crypto-mac" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1d1a86f49236c215f271d40892d5fc950490551400b02ef360692c29815c714" +dependencies = [ + "generic-array 0.14.6", + "subtle 2.4.1", +] + +[[package]] +name = "ctr" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "049bb91fb4aaf0e3c7efa6cd5ef877dbbbd15b39dad06d9948de4ec8a75761ea" +dependencies = [ + "cipher", +] + [[package]] name = "curve25519-dalek" version = "3.2.0" @@ -271,7 +356,7 @@ dependencies = [ "byteorder", "digest 0.9.0", "rand_core 0.5.1", - "subtle", + "subtle 2.4.1", "zeroize", ] @@ -483,13 +568,22 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "digest" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3d0c8c8752312f9713efd397ff63acb9f85585afbf179282e720e7704954dd5" +dependencies = [ + "generic-array 0.12.4", +] + [[package]] name = "digest" version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" dependencies = [ - "generic-array", + "generic-array 0.14.6", ] [[package]] @@ -500,7 +594,7 @@ checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ "block-buffer 0.10.4", "crypto-common", - "subtle", + "subtle 2.4.1", ] [[package]] @@ -576,12 +670,12 @@ dependencies = [ "der", "digest 0.10.7", "ff", - "generic-array", + "generic-array 0.14.6", "group", "pkcs8", "rand_core 0.6.4", "sec1", - "subtle", + "subtle 2.4.1", "zeroize", ] @@ -642,7 +736,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d013fc25338cc558c5c2cfbad646908fb23591e2404481826742b651c9af7160" dependencies = [ "rand_core 0.6.4", - "subtle", + "subtle 2.4.1", ] [[package]] @@ -761,6 +855,15 @@ dependencies = [ "slab", ] +[[package]] +name = "generic-array" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffdf9f34f1447443d37393cc6c2b8313aebddcd96906caf34e54c68d8e57d7bd" +dependencies = [ + "typenum", +] + [[package]] name = "generic-array" version = "0.14.6" @@ -828,7 +931,7 @@ checksum = "5dfbfb3a6cfbd390d5c9564ab283a0349b9b9fcd46a706c1eb10e0db70bfbac7" dependencies = [ "ff", "rand_core 0.6.4", - "subtle", + "subtle 2.4.1", ] [[package]] @@ -852,6 +955,26 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" +[[package]] +name = "hkdf" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01706d578d5c281058480e673ae4086a9f4710d8df1ad80a5b03e39ece5f886b" +dependencies = [ + "digest 0.9.0", + "hmac 0.11.0", +] + +[[package]] +name = "hmac" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a2a2320eb7ec0ebe8da8f744d7812d9fc4cb4d09344ac01898dbcb6a20ae69b" +dependencies = [ + "crypto-mac 0.11.1", + "digest 0.9.0", +] + [[package]] name = "hmac" version = "0.12.1" @@ -952,6 +1075,12 @@ dependencies = [ "sha2 0.10.6", ] +[[package]] +name = "keystream" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c33070833c9ee02266356de0c43f723152bd38bd96ddf52c82b3af10c9138b28" + [[package]] name = "lazy_static" version = "1.4.0" @@ -976,6 +1105,12 @@ dependencies = [ "pkg-config", ] +[[package]] +name = "libm" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7012b1bbb0719e1097c47611d3898568c546d597c2e74d66f6087edd5233ff4" + [[package]] name = "libz-sys" version = "1.1.8" @@ -994,6 +1129,18 @@ version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ece97ea872ece730aed82664c424eb4c8291e1ff2480247ccf7409044bc6479f" +[[package]] +name = "lioness" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ae926706ba42c425c9457121178330d75e273df2e82e28b758faf3de3a9acb9" +dependencies = [ + "arrayref", + "blake2", + "chacha", + "keystream", +] + [[package]] name = "log" version = "0.4.17" @@ -1025,6 +1172,16 @@ dependencies = [ "rand_chacha 0.2.2", ] +[[package]] +name = "num-traits" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f30b0abd723be7e2ffca1272140fac1a2f084c77ec3e123c192b66af1ee9e6c2" +dependencies = [ + "autocfg", + "libm", +] + [[package]] name = "nym-coconut-bandwidth" version = "0.1.0" @@ -1263,6 +1420,7 @@ dependencies = [ name = "nym-sphinx-types" version = "0.2.0" dependencies = [ + "sphinx-packet", "thiserror", ] @@ -1308,6 +1466,12 @@ version = "1.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3" +[[package]] +name = "opaque-debug" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2839e79665f131bdb5782e51f2c6c9599c133c6098982a54c794358bf432529c" + [[package]] name = "opaque-debug" version = "0.3.0" @@ -1498,6 +1662,16 @@ dependencies = [ "getrandom 0.2.10", ] +[[package]] +name = "rand_distr" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9e9532ada3929fb8b2e9dbe28d1e06c9b2cc65813f074fcb6bd5fbefeff9d56" +dependencies = [ + "num-traits", + "rand 0.7.3", +] + [[package]] name = "rand_hc" version = "0.2.0" @@ -1538,7 +1712,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7743f17af12fa0b03b803ba12cd6a8d9483a587e89c69445e3909655c0b9fabb" dependencies = [ "crypto-bigint", - "hmac", + "hmac 0.12.1", "zeroize", ] @@ -1647,9 +1821,9 @@ checksum = "3be24c1842290c45df0a7bf069e0c268a747ad05a192f2fd7dcfdbc1cba40928" dependencies = [ "base16ct", "der", - "generic-array", + "generic-array 0.14.6", "pkcs8", - "subtle", + "subtle 2.4.1", "zeroize", ] @@ -1731,7 +1905,7 @@ dependencies = [ "cfg-if", "cpufeatures", "digest 0.9.0", - "opaque-debug", + "opaque-debug 0.3.0", ] [[package]] @@ -1764,6 +1938,30 @@ dependencies = [ "autocfg", ] +[[package]] +name = "sphinx-packet" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc43eda802856ee82a7555c7b75ceb9e07451741c7a2f5f23d036020e01189d4" +dependencies = [ + "aes", + "arrayref", + "blake2", + "bs58", + "byteorder", + "chacha", + "curve25519-dalek", + "digest 0.9.0", + "hkdf", + "hmac 0.11.0", + "lioness", + "log", + "rand 0.7.3", + "rand_distr", + "sha2 0.9.9", + "subtle 2.4.1", +] + [[package]] name = "spki" version = "0.6.0" @@ -1774,6 +1972,12 @@ dependencies = [ "der", ] +[[package]] +name = "subtle" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d67a5a62ba6e01cb2192ff309324cb4875d0c451d55fe2319433abe7a05a8ee" + [[package]] name = "subtle" version = "2.4.1" diff --git a/nym-connect/desktop/Cargo.lock b/nym-connect/desktop/Cargo.lock index 947ecbf73c..c522d297d1 100644 --- a/nym-connect/desktop/Cargo.lock +++ b/nym-connect/desktop/Cargo.lock @@ -4181,6 +4181,8 @@ dependencies = [ name = "nym-sphinx-types" version = "0.2.0" dependencies = [ + "nym-outfox", + "sphinx-packet", "thiserror", ] From 8fda246a3c94aecc46aeb51ff32c0a06f06b2143 Mon Sep 17 00:00:00 2001 From: benedettadavico Date: Tue, 8 Aug 2023 11:15:30 +0200 Subject: [PATCH 100/361] Update changelog and versions for release v1.1.26 --- CHANGELOG.md | 22 +++++++++++++++++++ Cargo.lock | 18 +++++++-------- clients/native/Cargo.toml | 2 +- clients/socks5/Cargo.toml | 2 +- explorer-api/Cargo.toml | 2 +- gateway/Cargo.toml | 2 +- mixnode/Cargo.toml | 2 +- nym-api/Cargo.toml | 2 +- nym-connect/desktop/CHANGELOG.md | 6 +++++ nym-connect/desktop/package.json | 2 +- nym-connect/desktop/src-tauri/Cargo.toml | 2 +- nym-connect/desktop/src-tauri/tauri.conf.json | 2 +- .../network-requester/Cargo.toml | 2 +- .../network-statistics/Cargo.toml | 2 +- tools/nym-cli/Cargo.toml | 2 +- 15 files changed, 49 insertions(+), 21 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f938c81fe3..1ec4a60145 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,28 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https:// ## [Unreleased] +## [v1.1.24] (2023-08-08) + +- Latency based gateway selection is serial and slow ([#3710]) +- Network-requester: strip comments from allow lists ([#3625]) +- Remove (or start maintaining) `upgrade` commands from all binaries ([#3600]) +- Set sphinx as default packet type ([#3748]) +- Apply fix from feature/ephemera to develop too (#3698) ([#3742]) +- Feature/coco demos ([#3732]) +- Add updates to community list projects ([#3722]) +- Add geo-aware mixnet topology provider ([#3713]) +- Add updates to community list projects ([#3711]) + +[#3710]: https://github.com/nymtech/nym/issues/3710 +[#3625]: https://github.com/nymtech/nym/issues/3625 +[#3600]: https://github.com/nymtech/nym/issues/3600 +[#3748]: https://github.com/nymtech/nym/pull/3748 +[#3742]: https://github.com/nymtech/nym/pull/3742 +[#3732]: https://github.com/nymtech/nym/pull/3732 +[#3722]: https://github.com/nymtech/nym/pull/3722 +[#3713]: https://github.com/nymtech/nym/pull/3713 +[#3711]: https://github.com/nymtech/nym/pull/3711 + ## [v1.1.23] (2023-07-04) - nym-cli: add client identity key signing support ([#3576]) diff --git a/Cargo.lock b/Cargo.lock index 1dcf4528bf..85ff104b27 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1896,7 +1896,7 @@ checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" [[package]] name = "explorer-api" -version = "1.1.23" +version = "1.1.24" dependencies = [ "chrono", "clap 4.2.7", @@ -3530,7 +3530,7 @@ dependencies = [ [[package]] name = "nym-api" -version = "1.1.24" +version = "1.1.25" dependencies = [ "anyhow", "async-trait", @@ -3667,7 +3667,7 @@ dependencies = [ [[package]] name = "nym-cli" -version = "1.1.23" +version = "1.1.24" dependencies = [ "anyhow", "base64 0.13.1", @@ -3733,7 +3733,7 @@ dependencies = [ [[package]] name = "nym-client" -version = "1.1.23" +version = "1.1.24" dependencies = [ "clap 4.2.7", "dirs 4.0.0", @@ -4021,7 +4021,7 @@ dependencies = [ [[package]] name = "nym-gateway" -version = "1.1.23" +version = "1.1.24" dependencies = [ "anyhow", "async-trait", @@ -4176,7 +4176,7 @@ dependencies = [ [[package]] name = "nym-mixnode" -version = "1.1.24" +version = "1.1.25" dependencies = [ "anyhow", "bs58 0.4.0", @@ -4293,7 +4293,7 @@ dependencies = [ [[package]] name = "nym-network-requester" -version = "1.1.23" +version = "1.1.24" dependencies = [ "anyhow", "async-file-watcher", @@ -4340,7 +4340,7 @@ dependencies = [ [[package]] name = "nym-network-statistics" -version = "1.1.23" +version = "1.1.24" dependencies = [ "dirs 4.0.0", "log", @@ -4497,7 +4497,7 @@ dependencies = [ [[package]] name = "nym-socks5-client" -version = "1.1.23" +version = "1.1.24" dependencies = [ "clap 4.2.7", "lazy_static", diff --git a/clients/native/Cargo.toml b/clients/native/Cargo.toml index 6064304a90..2135883752 100644 --- a/clients/native/Cargo.toml +++ b/clients/native/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "nym-client" -version = "1.1.23" +version = "1.1.24" authors = ["Dave Hrycyszyn ", "Jędrzej Stuczyński "] description = "Implementation of the Nym Client" edition = "2021" diff --git a/clients/socks5/Cargo.toml b/clients/socks5/Cargo.toml index 658d52a047..afbf36178f 100644 --- a/clients/socks5/Cargo.toml +++ b/clients/socks5/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "nym-socks5-client" -version = "1.1.23" +version = "1.1.24" authors = ["Dave Hrycyszyn "] description = "A SOCKS5 localhost proxy that converts incoming messages to Sphinx and sends them to a Nym address" edition = "2021" diff --git a/explorer-api/Cargo.toml b/explorer-api/Cargo.toml index d6941a19e1..04e7255d01 100644 --- a/explorer-api/Cargo.toml +++ b/explorer-api/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "explorer-api" -version = "1.1.23" +version = "1.1.24" edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/gateway/Cargo.toml b/gateway/Cargo.toml index 763b3ab676..007fcca40d 100644 --- a/gateway/Cargo.toml +++ b/gateway/Cargo.toml @@ -3,7 +3,7 @@ [package] name = "nym-gateway" -version = "1.1.23" +version = "1.1.24" authors = [ "Dave Hrycyszyn ", "Jędrzej Stuczyński ", diff --git a/mixnode/Cargo.toml b/mixnode/Cargo.toml index 9f512f0d85..8421bebb3a 100644 --- a/mixnode/Cargo.toml +++ b/mixnode/Cargo.toml @@ -3,7 +3,7 @@ [package] name = "nym-mixnode" -version = "1.1.24" +version = "1.1.25" authors = [ "Dave Hrycyszyn ", "Jędrzej Stuczyński ", diff --git a/nym-api/Cargo.toml b/nym-api/Cargo.toml index f9b378f59d..50568b440a 100644 --- a/nym-api/Cargo.toml +++ b/nym-api/Cargo.toml @@ -3,7 +3,7 @@ [package] name = "nym-api" -version = "1.1.24" +version = "1.1.25" authors = [ "Dave Hrycyszyn ", "Jędrzej Stuczyński ", diff --git a/nym-connect/desktop/CHANGELOG.md b/nym-connect/desktop/CHANGELOG.md index 1149c3b227..8a0b86151a 100644 --- a/nym-connect/desktop/CHANGELOG.md +++ b/nym-connect/desktop/CHANGELOG.md @@ -2,6 +2,12 @@ ## [Unreleased] +## [v1.1.16] (2023-08-08) + +- Uncouple network-requester <-> gateway in nym-connect and harbourmaster ([#3472]) + +[#3472]: https://github.com/nymtech/nym/issues/3472 + ## [v1.1.15] (2023-07-25) - NC Desktop - remove sentry DSN from code ([#3694]) diff --git a/nym-connect/desktop/package.json b/nym-connect/desktop/package.json index 626b415b8f..96d9e83438 100644 --- a/nym-connect/desktop/package.json +++ b/nym-connect/desktop/package.json @@ -1,6 +1,6 @@ { "name": "@nym/nym-connect", - "version": "1.1.15", + "version": "1.1.16", "main": "index.js", "license": "MIT", "scripts": { diff --git a/nym-connect/desktop/src-tauri/Cargo.toml b/nym-connect/desktop/src-tauri/Cargo.toml index e3bd49cb41..0d1089132d 100644 --- a/nym-connect/desktop/src-tauri/Cargo.toml +++ b/nym-connect/desktop/src-tauri/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "nym-connect" -version = "1.1.15" +version = "1.1.16" description = "nym-connect" authors = ["Nym Technologies SA"] license = "" diff --git a/nym-connect/desktop/src-tauri/tauri.conf.json b/nym-connect/desktop/src-tauri/tauri.conf.json index b95859ca53..5f5e86bec0 100644 --- a/nym-connect/desktop/src-tauri/tauri.conf.json +++ b/nym-connect/desktop/src-tauri/tauri.conf.json @@ -1,7 +1,7 @@ { "package": { "productName": "nym-connect", - "version": "1.1.15" + "version": "1.1.16" }, "build": { "distDir": "../dist", diff --git a/service-providers/network-requester/Cargo.toml b/service-providers/network-requester/Cargo.toml index 5b80088b30..d1d7367d85 100644 --- a/service-providers/network-requester/Cargo.toml +++ b/service-providers/network-requester/Cargo.toml @@ -3,7 +3,7 @@ [package] name = "nym-network-requester" -version = "1.1.23" +version = "1.1.24" authors.workspace = true edition.workspace = true rust-version = "1.65" diff --git a/service-providers/network-statistics/Cargo.toml b/service-providers/network-statistics/Cargo.toml index cc7024c545..7c0138ca8f 100644 --- a/service-providers/network-statistics/Cargo.toml +++ b/service-providers/network-statistics/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "nym-network-statistics" -version = "1.1.23" +version = "1.1.24" edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/tools/nym-cli/Cargo.toml b/tools/nym-cli/Cargo.toml index ea5cad9e65..9f3c0d6a2a 100644 --- a/tools/nym-cli/Cargo.toml +++ b/tools/nym-cli/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "nym-cli" -version = "1.1.23" +version = "1.1.24" authors.workspace = true edition = "2021" From a26c6d7a29c6f6935fac0dc4e894358ed6253726 Mon Sep 17 00:00:00 2001 From: benedettadavico Date: Tue, 8 Aug 2023 11:24:08 +0200 Subject: [PATCH 101/361] Temporary update to fix windows build --- package.json | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index 523db5363f..cad6eaf88d 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,6 @@ "ts-packages/*", "nym-wallet", "nym-connect/**", - "nym-browser-extension", "explorer", "types", "clients/validator" @@ -29,9 +28,8 @@ "tsc": "lerna run tsc --stream", "types:lint:fix": "lerna run lint:fix --scope @nymproject/types --scope @nymproject/nym-wallet-app", "audit:fix": "npm_config_yes=true npx yarn-audit-fix -- --dry-run", - "preinstall": "yarn install:copy-placeholders && yarn install:copy-storage-placeholders", - "install:copy-placeholders": "cp scripts/build/yarn/wasm-placeholder/package.json sdk/typescript/packages/nym-client-wasm", - "install:copy-storage-placeholders": "mkdir -p nym-browser-extension/storage/pkg && cp scripts/build/yarn/storage-placeholder/package.json nym-browser-extension/storage/pkg" + "preinstall": "yarn install:copy-placeholders", + "install:copy-placeholders": "cp scripts/build/yarn/wasm-placeholder/package.json sdk/typescript/packages/nym-client-wasm" }, "devDependencies": { "lerna": "^6.6.2", From 62316244d9222ea695387426bf5ed926c0b7dc5f Mon Sep 17 00:00:00 2001 From: benedettadavico Date: Tue, 8 Aug 2023 14:17:55 +0200 Subject: [PATCH 102/361] Revert package.json changes needed temporary build --- package.json | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index cad6eaf88d..523db5363f 100644 --- a/package.json +++ b/package.json @@ -8,6 +8,7 @@ "ts-packages/*", "nym-wallet", "nym-connect/**", + "nym-browser-extension", "explorer", "types", "clients/validator" @@ -28,8 +29,9 @@ "tsc": "lerna run tsc --stream", "types:lint:fix": "lerna run lint:fix --scope @nymproject/types --scope @nymproject/nym-wallet-app", "audit:fix": "npm_config_yes=true npx yarn-audit-fix -- --dry-run", - "preinstall": "yarn install:copy-placeholders", - "install:copy-placeholders": "cp scripts/build/yarn/wasm-placeholder/package.json sdk/typescript/packages/nym-client-wasm" + "preinstall": "yarn install:copy-placeholders && yarn install:copy-storage-placeholders", + "install:copy-placeholders": "cp scripts/build/yarn/wasm-placeholder/package.json sdk/typescript/packages/nym-client-wasm", + "install:copy-storage-placeholders": "mkdir -p nym-browser-extension/storage/pkg && cp scripts/build/yarn/storage-placeholder/package.json nym-browser-extension/storage/pkg" }, "devDependencies": { "lerna": "^6.6.2", From 191aa149df2c698427a3bbdcf1d4387f9bc74731 Mon Sep 17 00:00:00 2001 From: benedetta davico <46782255+benedettadavico@users.noreply.github.com> Date: Tue, 8 Aug 2023 15:37:16 +0200 Subject: [PATCH 103/361] Update Cargo.toml --- Cargo.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 198f37890e..2e9a393993 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -146,11 +146,11 @@ reqwest = "0.11.18" serde = "1.0.152" serde_json = "1.0.91" tap = "1.0.1" -tendermint-rpc = "=0.32" # same version as used by cosmrs +tendermint-rpc = "0.32" # same version as used by cosmrs thiserror = "1.0.38" tokio = "1.24.1" url = "2.2" zeroize = "1.6.0" # wasm-related dependencies -wasmtimer = "0.2.0" \ No newline at end of file +wasmtimer = "0.2.0" From d8a95d3810ddea730ad5b3e1e1ffe6fb9e80ee3d Mon Sep 17 00:00:00 2001 From: benedetta davico <46782255+benedettadavico@users.noreply.github.com> Date: Tue, 8 Aug 2023 15:42:05 +0200 Subject: [PATCH 104/361] Update Cargo.toml --- Cargo.toml | 1 - 1 file changed, 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 2e9a393993..9d609443f0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -127,7 +127,6 @@ serde-json-wasm = "=0.5.0" cosmwasm-storage = "=1.3.0" cosmrs = "=0.14.0" # same version as used by cosmrs -tendermint-rpc = "=0.32" cw-utils = "=1.0.1" cw-storage-plus = "=1.1.0" cw2 = { version = "=1.1.0" } From be88a9b2774ce9649a999dddfa18aeea28306a2f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bogdan-=C8=98tefan=20Neac=C5=9Fu?= Date: Tue, 8 Aug 2023 18:33:50 +0300 Subject: [PATCH 105/361] Remove stale BBC env var (#3720) --- .../validator-client/src/nyxd/mod.rs | 20 ------------------- common/network-defaults/src/lib.rs | 14 ------------- common/network-defaults/src/mainnet.rs | 10 ---------- common/network-defaults/src/var_names.rs | 1 - .../docs/src/bandwidth-credentials.md | 1 - envs/local.env | 1 - envs/mainnet.env | 1 - envs/qa-qwerty.env | 1 - envs/qa.env | 1 - envs/sandbox.env | 1 - nym-wallet/nym-wallet-types/src/network/qa.rs | 3 --- .../nym-wallet-types/src/network/sandbox.rs | 3 --- nym-wallet/src-tauri/src/config/mod.rs | 20 ------------------- .../src/operations/mixnet/account.rs | 7 +------ 14 files changed, 1 insertion(+), 83 deletions(-) diff --git a/common/client-libs/validator-client/src/nyxd/mod.rs b/common/client-libs/validator-client/src/nyxd/mod.rs index 404124df8d..9a4ee1eba4 100644 --- a/common/client-libs/validator-client/src/nyxd/mod.rs +++ b/common/client-libs/validator-client/src/nyxd/mod.rs @@ -78,7 +78,6 @@ pub struct Config { // however, I'd really prefer to use something more strongly typed (i.e. AccountId vs String) pub(crate) mixnet_contract_address: Option, pub(crate) vesting_contract_address: Option, - pub(crate) bandwidth_claim_contract_address: Option, pub(crate) coconut_bandwidth_contract_address: Option, pub(crate) group_contract_address: Option, pub(crate) multisig_contract_address: Option, @@ -126,10 +125,6 @@ impl Config { details.contracts.vesting_contract_address.as_ref(), prefix, )?, - bandwidth_claim_contract_address: Self::parse_optional_account( - details.contracts.bandwidth_claim_contract_address.as_ref(), - prefix, - )?, coconut_bandwidth_contract_address: Self::parse_optional_account( details .contracts @@ -548,10 +543,6 @@ impl NyxdClient { self.config.vesting_contract_address = Some(address); } - pub fn set_bandwidth_claim_contract_address(&mut self, address: AccountId) { - self.config.bandwidth_claim_contract_address = Some(address); - } - pub fn set_coconut_bandwidth_contract_address(&mut self, address: AccountId) { self.config.coconut_bandwidth_contract_address = Some(address); } @@ -580,17 +571,6 @@ impl NyxdClient { self.config.vesting_contract_address.as_ref().unwrap() } - // TODO: this should get changed into Result<&AccountId, NyxdError> (or Option<&AccountId> in future commits - // note: what unwrap is doing here is just moving a failure that would have normally - // occurred in `connect` when attempting to parse an empty address, - // so it's not introducing new source of failure (just moves it) - pub fn bandwidth_claim_contract_address(&self) -> &AccountId { - self.config - .bandwidth_claim_contract_address - .as_ref() - .unwrap() - } - // TODO: this should get changed into Result<&AccountId, NyxdError> (or Option<&AccountId> in future commits // note: what unwrap is doing here is just moving a failure that would have normally // occurred in `connect` when attempting to parse an empty address, diff --git a/common/network-defaults/src/lib.rs b/common/network-defaults/src/lib.rs index a84175af1e..019c894f96 100644 --- a/common/network-defaults/src/lib.rs +++ b/common/network-defaults/src/lib.rs @@ -25,7 +25,6 @@ pub struct ChainDetails { pub struct NymContracts { pub mixnet_contract_address: Option, pub vesting_contract_address: Option, - pub bandwidth_claim_contract_address: Option, pub coconut_bandwidth_contract_address: Option, pub group_contract_address: Option, pub multisig_contract_address: Option, @@ -112,10 +111,6 @@ impl NymNetworkDetails { .with_vesting_contract(Some( var(var_names::VESTING_CONTRACT_ADDRESS).expect("vesting contract not set"), )) - .with_bandwidth_claim_contract(Some( - var(var_names::BANDWIDTH_CLAIM_CONTRACT_ADDRESS) - .expect("bandwidth claim contract not set"), - )) .with_coconut_bandwidth_contract(Some( var(var_names::COCONUT_BANDWIDTH_CONTRACT_ADDRESS) .expect("coconut bandwidth contract not set"), @@ -151,9 +146,6 @@ impl NymNetworkDetails { contracts: NymContracts { mixnet_contract_address: parse_optional_str(mainnet::MIXNET_CONTRACT_ADDRESS), vesting_contract_address: parse_optional_str(mainnet::VESTING_CONTRACT_ADDRESS), - bandwidth_claim_contract_address: parse_optional_str( - mainnet::BANDWIDTH_CLAIM_CONTRACT_ADDRESS, - ), coconut_bandwidth_contract_address: parse_optional_str( mainnet::COCONUT_BANDWIDTH_CONTRACT_ADDRESS, ), @@ -216,12 +208,6 @@ impl NymNetworkDetails { self } - #[must_use] - pub fn with_bandwidth_claim_contract>(mut self, contract: Option) -> Self { - self.contracts.bandwidth_claim_contract_address = contract.map(Into::into); - self - } - #[must_use] pub fn with_coconut_bandwidth_contract>(mut self, contract: Option) -> Self { self.contracts.coconut_bandwidth_contract_address = contract.map(Into::into); diff --git a/common/network-defaults/src/mainnet.rs b/common/network-defaults/src/mainnet.rs index dc35157435..453f6695ec 100644 --- a/common/network-defaults/src/mainnet.rs +++ b/common/network-defaults/src/mainnet.rs @@ -13,8 +13,6 @@ pub const MIXNET_CONTRACT_ADDRESS: &str = "n17srjznxl9dvzdkpwpw24gg668wc73val88a6m5ajg6ankwvz9wtst0cznr"; pub const VESTING_CONTRACT_ADDRESS: &str = "n1nc5tatafv6eyq7llkr2gv50ff9e22mnf70qgjlv737ktmt4eswrq73f2nw"; -pub(crate) const BANDWIDTH_CLAIM_CONTRACT_ADDRESS: &str = - "n19lc9u84cz0yz3fww5283nucc9yvr8gsjmgeul0"; pub(crate) const COCONUT_BANDWIDTH_CONTRACT_ADDRESS: &str = "n19lc9u84cz0yz3fww5283nucc9yvr8gsjmgeul0"; pub(crate) const GROUP_CONTRACT_ADDRESS: &str = "n19lc9u84cz0yz3fww5283nucc9yvr8gsjmgeul0"; @@ -70,10 +68,6 @@ pub fn export_to_env() { var_names::VESTING_CONTRACT_ADDRESS, VESTING_CONTRACT_ADDRESS, ); - set_var_to_default( - var_names::BANDWIDTH_CLAIM_CONTRACT_ADDRESS, - BANDWIDTH_CLAIM_CONTRACT_ADDRESS, - ); set_var_to_default( var_names::COCONUT_BANDWIDTH_CONTRACT_ADDRESS, COCONUT_BANDWIDTH_CONTRACT_ADDRESS, @@ -115,10 +109,6 @@ pub fn export_to_env_if_not_set() { var_names::VESTING_CONTRACT_ADDRESS, VESTING_CONTRACT_ADDRESS, ); - set_var_conditionally_to_default( - var_names::BANDWIDTH_CLAIM_CONTRACT_ADDRESS, - BANDWIDTH_CLAIM_CONTRACT_ADDRESS, - ); set_var_conditionally_to_default( var_names::COCONUT_BANDWIDTH_CONTRACT_ADDRESS, COCONUT_BANDWIDTH_CONTRACT_ADDRESS, diff --git a/common/network-defaults/src/var_names.rs b/common/network-defaults/src/var_names.rs index ee7b1c9a2a..c7ff9bb680 100644 --- a/common/network-defaults/src/var_names.rs +++ b/common/network-defaults/src/var_names.rs @@ -12,7 +12,6 @@ pub const STAKE_DENOM_DISPLAY: &str = "STAKE_DENOM_DISPLAY"; pub const DENOMS_EXPONENT: &str = "DENOMS_EXPONENT"; pub const MIXNET_CONTRACT_ADDRESS: &str = "MIXNET_CONTRACT_ADDRESS"; pub const VESTING_CONTRACT_ADDRESS: &str = "VESTING_CONTRACT_ADDRESS"; -pub const BANDWIDTH_CLAIM_CONTRACT_ADDRESS: &str = "BANDWIDTH_CLAIM_CONTRACT_ADDRESS"; pub const COCONUT_BANDWIDTH_CONTRACT_ADDRESS: &str = "COCONUT_BANDWIDTH_CONTRACT_ADDRESS"; pub const GROUP_CONTRACT_ADDRESS: &str = "GROUP_CONTRACT_ADDRESS"; pub const MULTISIG_CONTRACT_ADDRESS: &str = "MULTISIG_CONTRACT_ADDRESS"; diff --git a/documentation/docs/src/bandwidth-credentials.md b/documentation/docs/src/bandwidth-credentials.md index 4bbe6c3a79..6423dcf035 100644 --- a/documentation/docs/src/bandwidth-credentials.md +++ b/documentation/docs/src/bandwidth-credentials.md @@ -20,7 +20,6 @@ DENOMS_EXPONENT=6 REWARDING_VALIDATOR_ADDRESS="nymt1mxuweurc066kprnngtm8zmvam7m2nw26yatpmv" MIXNET_CONTRACT_ADDRESS="nymt1dlsvvgey26ernlj0sq2afjluh3qd4ap0k9eerekfkw5algqrwqksaf2qf7" VESTING_CONTRACT_ADDRESS="nymt19g9xuqrvz2frv905v3fc7puryfypluhg383q9zwsmedrlqekfgys62ykm4" -BANDWIDTH_CLAIM_CONTRACT_ADDRESS="nymt1rhmk9udessnv3r8f3eh2s03f45svnjaczpmcqz" MULTISIG_CONTRACT_ADDRESS="nymt142dkm8xe9f0ytyarp7ww4kvclva65705jphxsk9exn3nqdsm8jkqnp06ac" COCONUT_BANDWIDTH_CONTRACT_ADDRESS="nymt1ty0frysegskh6ndm3v96z5xdq66qzcu0aw7xcxlgp54jg0mjwlgqplc6v0" COCONUT_DKG_CONTRACT_ADDRESS="nymt1gwk6muhmzeuxje7df7rjvqwl2vex0kj4t2hwuzmyx5k62kfusu5qk4k5z4" diff --git a/envs/local.env b/envs/local.env index c7121762d0..7c450b8aec 100644 --- a/envs/local.env +++ b/envs/local.env @@ -11,7 +11,6 @@ STAKE_DENOM_DISPLAY=nyx DENOMS_EXPONENT=6 MIXNET_CONTRACT_ADDRESS=n1xsml6tm6pnx8nsuz3a54cznh55g6nuwu9ug9t92ucjkq5ewp5w9syxeqsv VESTING_CONTRACT_ADDRESS=n1xr3rq8yvd7qplsw5yx90ftsr2zdhg4e9z60h5duusgxpv72hud3sjkxkav -BANDWIDTH_CLAIM_CONTRACT_ADDRESS=n19lc9u84cz0yz3fww5283nucc9yvr8gsjmgeul0 COCONUT_BANDWIDTH_CONTRACT_ADDRESS=n1lp5zex6685kd22agzskhqsylpnssxnweyuvsz4edr4p4ta92qf3q3dhmsx GROUP_CONTRACT_ADDRESS=n1rw8fw2mpcpzzq3jpa4e52ufawnmj5a4u68p35umvgskewuw0nlzsaa5w4m MULTISIG_CONTRACT_ADDRESS=n1rw8fw2mpcpzzq3jpa4e52ufawnmj5a4u68p35umvgskewuw0nlzsaa5w4m diff --git a/envs/mainnet.env b/envs/mainnet.env index 967534f006..3ace54478b 100644 --- a/envs/mainnet.env +++ b/envs/mainnet.env @@ -11,7 +11,6 @@ STAKE_DENOM_DISPLAY=nyx DENOMS_EXPONENT=6 MIXNET_CONTRACT_ADDRESS=n17srjznxl9dvzdkpwpw24gg668wc73val88a6m5ajg6ankwvz9wtst0cznr VESTING_CONTRACT_ADDRESS=n1nc5tatafv6eyq7llkr2gv50ff9e22mnf70qgjlv737ktmt4eswrq73f2nw -BANDWIDTH_CLAIM_CONTRACT_ADDRESS=n19lc9u84cz0yz3fww5283nucc9yvr8gsjmgeul0 COCONUT_BANDWIDTH_CONTRACT_ADDRESS=n19lc9u84cz0yz3fww5283nucc9yvr8gsjmgeul0 GROUP_CONTRACT_ADDRESS=n1rw8fw2mpcpzzq3jpa4e52ufawnmj5a4u68p35umvgskewuw0nlzsaa5w4m MULTISIG_CONTRACT_ADDRESS=n19lc9u84cz0yz3fww5283nucc9yvr8gsjmgeul0 diff --git a/envs/qa-qwerty.env b/envs/qa-qwerty.env index 2a6a5da732..aba57bb0ed 100644 --- a/envs/qa-qwerty.env +++ b/envs/qa-qwerty.env @@ -11,7 +11,6 @@ STAKE_DENOM_DISPLAY=nyx DENOMS_EXPONENT=6 MIXNET_CONTRACT_ADDRESS=n14hj2tavq8fpesdwxxcu44rty3hh90vhujrvcmstl4zr3txmfvw9sjyvg3g VESTING_CONTRACT_ADDRESS=n1nc5tatafv6eyq7llkr2gv50ff9e22mnf70qgjlv737ktmt4eswrq73f2nw -BANDWIDTH_CLAIM_CONTRACT_ADDRESS=n19lc9u84cz0yz3fww5283nucc9yvr8gsjmgeul0 COCONUT_BANDWIDTH_CONTRACT_ADDRESS=n19d2nwj7fdhxqmyvgy8lf3ad49a6vmww4shryhrkj2mqk36att66s6xzszw GROUP_CONTRACT_ADDRESS=n1fqquzw4mk0pkamgr2ywt2v7h2j9nuyjjn4gvpy8zlpp6xn0uyuzqfm28l5 MULTISIG_CONTRACT_ADDRESS=n1gaq3666chd5348apj8cka8t2mckv7azp9espyr7wgpxyuzur5d0sazpysy diff --git a/envs/qa.env b/envs/qa.env index a5442b7600..f09ee6baf1 100644 --- a/envs/qa.env +++ b/envs/qa.env @@ -11,7 +11,6 @@ STAKE_DENOM_DISPLAY=nyx DENOMS_EXPONENT=6 MIXNET_CONTRACT_ADDRESS=n1rjzps6qrmdqmf0xz4cn4x4rcmqeqzq6hnzqg4wcvd0r2lyasdq5sepn5s8 VESTING_CONTRACT_ADDRESS=n1xr3rq8yvd7qplsw5yx90ftsr2zdhg4e9z60h5duusgxpv72hud3sjkxkav -BANDWIDTH_CLAIM_CONTRACT_ADDRESS=n19lc9u84cz0yz3fww5283nucc9yvr8gsjmgeul0 COCONUT_BANDWIDTH_CONTRACT_ADDRESS=n1ghd753shjuwexxywmgs4xz7x2q732vcn7ty4yw GROUP_CONTRACT_ADDRESS=n1rw8fw2mpcpzzq3jpa4e52ufawnmj5a4u68p35umvgskewuw0nlzsaa5w4m MULTISIG_CONTRACT_ADDRESS=n17p9rzwnnfxcjp32un9ug7yhhzgtkhvl988qccs diff --git a/envs/sandbox.env b/envs/sandbox.env index f42a91a97d..70aa96ff42 100644 --- a/envs/sandbox.env +++ b/envs/sandbox.env @@ -13,7 +13,6 @@ DENOMS_EXPONENT=6 REWARDING_VALIDATOR_ADDRESS=n1pefc2utwpy5w78p2kqdsfmpjxfwmn9d39k5mqa MIXNET_CONTRACT_ADDRESS=n1xr3rq8yvd7qplsw5yx90ftsr2zdhg4e9z60h5duusgxpv72hud3sjkxkav VESTING_CONTRACT_ADDRESS=n1unyuj8qnmygvzuex3dwmg9yzt9alhvyeat0uu0jedg2wj33efl5qackslz -BANDWIDTH_CLAIM_CONTRACT_ADDRESS=n19lc9u84cz0yz3fww5283nucc9yvr8gsjmgeul0 COCONUT_BANDWIDTH_CONTRACT_ADDRESS=n16a32stm6kknhq5cc8rx77elr66pygf2hfszw7wvpq746x3uffylqkjar4l GROUP_CONTRACT_ADDRESS=n1pd7kfgvr5tpcv0xnlv46c4jsq9jg2r799xxrcwqdm4l2jhq2pjwqrmz5ju MULTISIG_CONTRACT_ADDRESS=n14ph4e660eyqz0j36zlkaey4zgzexm5twkmjlqaequxr2cjm9eprqsmad6k diff --git a/nym-wallet/nym-wallet-types/src/network/qa.rs b/nym-wallet/nym-wallet-types/src/network/qa.rs index 1d298de8dd..d333d71067 100644 --- a/nym-wallet/nym-wallet-types/src/network/qa.rs +++ b/nym-wallet/nym-wallet-types/src/network/qa.rs @@ -13,8 +13,6 @@ pub(crate) const MIXNET_CONTRACT_ADDRESS: &str = "n10qt8wg0n7z740ssvf3urmvgtjhxpyp74hxqvqt7z226gykuus7eq5u9pvq"; pub(crate) const VESTING_CONTRACT_ADDRESS: &str = "n1vguuxez2h5ekltfj9gjd62fs5k4rl2zy5hfrncasykzw08rezpfstk9xtk"; -pub(crate) const BANDWIDTH_CLAIM_CONTRACT_ADDRESS: &str = - "n19lc9u84cz0yz3fww5283nucc9yvr8gsjmgeul0"; pub(crate) const COCONUT_BANDWIDTH_CONTRACT_ADDRESS: &str = "n1ghd753shjuwexxywmgs4xz7x2q732vcn7ty4yw"; pub(crate) const GROUP_CONTRACT_ADDRESS: &str = @@ -48,7 +46,6 @@ pub(crate) fn network_details() -> nym_network_defaults::NymNetworkDetails { contracts: NymContracts { mixnet_contract_address: parse_optional_str(MIXNET_CONTRACT_ADDRESS), vesting_contract_address: parse_optional_str(VESTING_CONTRACT_ADDRESS), - bandwidth_claim_contract_address: parse_optional_str(BANDWIDTH_CLAIM_CONTRACT_ADDRESS), coconut_bandwidth_contract_address: parse_optional_str( COCONUT_BANDWIDTH_CONTRACT_ADDRESS, ), diff --git a/nym-wallet/nym-wallet-types/src/network/sandbox.rs b/nym-wallet/nym-wallet-types/src/network/sandbox.rs index 169c058c3c..aefbcc73b3 100644 --- a/nym-wallet/nym-wallet-types/src/network/sandbox.rs +++ b/nym-wallet/nym-wallet-types/src/network/sandbox.rs @@ -11,8 +11,6 @@ pub(crate) const STAKE_DENOM: DenomDetails = DenomDetails::new("unyxt", "nyxt", pub(crate) const MIXNET_CONTRACT_ADDRESS: &str = "nymt1ghd753shjuwexxywmgs4xz7x2q732vcnstz02j"; pub(crate) const VESTING_CONTRACT_ADDRESS: &str = "nymt14ejqjyq8um4p3xfqj74yld5waqljf88fn549lh"; -pub(crate) const BANDWIDTH_CLAIM_CONTRACT_ADDRESS: &str = - "nymt17p9rzwnnfxcjp32un9ug7yhhzgtkhvl9f8xzkv"; pub(crate) const COCONUT_BANDWIDTH_CONTRACT_ADDRESS: &str = "nymt1nz0r0au8aj6dc00wmm3ufy4g4k86rjzlgq608r"; pub(crate) const GROUP_CONTRACT_ADDRESS: &str = "nymt1k8re7jwz6rnnwrktnejdwkwnncte7ek7kk6fvg"; @@ -39,7 +37,6 @@ pub(crate) fn network_details() -> nym_network_defaults::NymNetworkDetails { contracts: NymContracts { mixnet_contract_address: parse_optional_str(MIXNET_CONTRACT_ADDRESS), vesting_contract_address: parse_optional_str(VESTING_CONTRACT_ADDRESS), - bandwidth_claim_contract_address: parse_optional_str(BANDWIDTH_CLAIM_CONTRACT_ADDRESS), coconut_bandwidth_contract_address: parse_optional_str( COCONUT_BANDWIDTH_CONTRACT_ADDRESS, ), diff --git a/nym-wallet/src-tauri/src/config/mod.rs b/nym-wallet/src-tauri/src/config/mod.rs index 24e8a7d03a..bdb369ade6 100644 --- a/nym-wallet/src-tauri/src/config/mod.rs +++ b/nym-wallet/src-tauri/src/config/mod.rs @@ -237,15 +237,6 @@ impl Config { .expect("Wrong format for vesting contract address") } - pub fn get_bandwidth_claim_contract_address(&self, network: WalletNetwork) -> CosmosAccountId { - self.base - .networks - .bandwidth_claim_contract_address(&network) - .expect("No bandwidth claim contract address found in config") - .parse() - .expect("Wrong format for bandwidth claim contract address") - } - pub fn set_default_nyxd_urls(&mut self, urls: &HashMap) { for (network, url) in urls { self.set_default_nyxd_url(url.to_owned(), network); @@ -499,12 +490,6 @@ impl SupportedNetworks { .map(|network_details| network_details.vesting_contract_address.as_str()) } - fn bandwidth_claim_contract_address(&self, network: &WalletNetwork) -> Option<&str> { - self.networks - .get(network) - .map(|network_details| network_details.bandwidth_claim_contract_address.as_str()) - } - fn validators(&self, network: &WalletNetwork) -> impl Iterator { self.networks .get(network) @@ -522,7 +507,6 @@ struct NetworkDetails { stake_denom: DenomDetailsOwned, mixnet_contract_address: String, vesting_contract_address: String, - bandwidth_claim_contract_address: String, statistics_service_url: String, validators: Vec, } @@ -543,10 +527,6 @@ impl From for NetworkDetails { .contracts .vesting_contract_address .unwrap_or_default(), - bandwidth_claim_contract_address: details - .contracts - .bandwidth_claim_contract_address - .unwrap_or_default(), statistics_service_url: "".to_string(), validators: details.endpoints, } diff --git a/nym-wallet/src-tauri/src/operations/mixnet/account.rs b/nym-wallet/src-tauri/src/operations/mixnet/account.rs index a15732e5ee..07f939ce95 100644 --- a/nym-wallet/src-tauri/src/operations/mixnet/account.rs +++ b/nym-wallet/src-tauri/src/operations/mixnet/account.rs @@ -282,12 +282,7 @@ fn create_clients( let network_details = NymNetworkDetails::from(network) .clone() .with_mixnet_contract(Some(config.get_mixnet_contract_address(network).as_ref())) - .with_vesting_contract(Some(config.get_vesting_contract_address(network).as_ref())) - .with_bandwidth_claim_contract(Some( - config - .get_bandwidth_claim_contract_address(network) - .as_ref(), - )); + .with_vesting_contract(Some(config.get_vesting_contract_address(network).as_ref())); let config = nym_validator_client::Config::try_from_nym_network_details(&network_details)? .with_urls(nyxd_url, api_url); From 4f05169361819353ed8a887ce902f1bf9baaa205 Mon Sep 17 00:00:00 2001 From: mfahampshire Date: Wed, 9 Aug 2023 11:29:34 +0200 Subject: [PATCH 106/361] added coc and license --- documentation/operators/src/SUMMARY.md | 5 +++++ documentation/operators/src/coc.md | 17 +++++++++++++++++ documentation/operators/src/licensing.md | 5 +++++ 3 files changed, 27 insertions(+) create mode 100644 documentation/operators/src/coc.md create mode 100644 documentation/operators/src/licensing.md diff --git a/documentation/operators/src/SUMMARY.md b/documentation/operators/src/SUMMARY.md index a3f55376c8..82345c6f49 100644 --- a/documentation/operators/src/SUMMARY.md +++ b/documentation/operators/src/SUMMARY.md @@ -20,3 +20,8 @@ - [Troubleshooting](./nodes/troubleshooting.md) - [FAQ](./faq.md) +--- +# Misc. +- [Code of Conduct](coc.md) +- [Licensing](licensing.md) +--- diff --git a/documentation/operators/src/coc.md b/documentation/operators/src/coc.md new file mode 100644 index 0000000000..f8771a73bf --- /dev/null +++ b/documentation/operators/src/coc.md @@ -0,0 +1,17 @@ +# Code of Conduct + +We are committed to providing a friendly, safe and welcoming environment for all, regardless of level of experience, gender identity and expression, sexual orientation, disability, personal appearance, body size, race, ethnicity, age, religion, nationality, or other similar characteristic. + +Please avoid using overtly sexual aliases or other nicknames that might detract from a friendly, safe and welcoming environment for all. + +Please be kind and courteous. There’s no need to be mean or rude. + +Respect that people have differences of opinion and that every design or implementation choice carries a trade-off and numerous costs. There is seldom a right answer. + +Please keep unstructured critique to a minimum. If you have solid ideas you want to experiment with, make a fork and see how it works. + +We will exclude you from interaction if you insult, demean or harass anyone. That is not welcome behaviour. We interpret the term “harassment” as including the definition in the Citizen Code of Conduct; if you have any lack of clarity about what might be included in that concept, please read their definition. In particular, we don’t tolerate behaviour that excludes people in socially marginalized groups. + +Private harassment is also unacceptable. No matter who you are, if you feel you have been or are being harassed or made uncomfortable by a community member, please contact one of the channel ops or any of the Rust moderation team immediately. Whether you’re a regular contributor or a newcomer, we care about making this community a safe place for you and we’ve got your back. + +Likewise any spamming, trolling, flaming, baiting or other attention-stealing behaviour is not welcome. diff --git a/documentation/operators/src/licensing.md b/documentation/operators/src/licensing.md new file mode 100644 index 0000000000..2e8612d4d4 --- /dev/null +++ b/documentation/operators/src/licensing.md @@ -0,0 +1,5 @@ +# Licensing + +Nym is Free Software released under the Apache License V2. + +All of the contributions of the Nym core developers are © Nym Technologies SA. If you are interested in talking to us about other licenses, please get in touch. From 432f6a006f4416660c57cf766e5df7bc6a7d4e06 Mon Sep 17 00:00:00 2001 From: mfahampshire Date: Wed, 9 Aug 2023 11:29:57 +0200 Subject: [PATCH 107/361] removed glossary file: needs rework --- documentation/dev-portal/src/SUMMARY.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/documentation/dev-portal/src/SUMMARY.md b/documentation/dev-portal/src/SUMMARY.md index be35e9c30a..d31adbade3 100644 --- a/documentation/dev-portal/src/SUMMARY.md +++ b/documentation/dev-portal/src/SUMMARY.md @@ -44,10 +44,9 @@ - [Community Guides](community-resources/community-guides.md) - [Change Service Grantee Information](info-request.md) - [Rewards FAQ](community-resources/rewards-faq.md) ---- +--- # Misc. -- [Glossary](glossary.md) - [Code of Conduct](coc.md) - [Licensing](licensing.md) --- From e9d10caaf862e412fc0c4c8589c2203ea367cda0 Mon Sep 17 00:00:00 2001 From: mfahampshire Date: Wed, 9 Aug 2023 11:30:12 +0200 Subject: [PATCH 108/361] final tweaks --- documentation/docs/src/nodes/gateway.md | 4 ++-- documentation/docs/src/nodes/mixnode.md | 2 +- documentation/docs/src/nodes/validator.md | 3 --- 3 files changed, 3 insertions(+), 6 deletions(-) diff --git a/documentation/docs/src/nodes/gateway.md b/documentation/docs/src/nodes/gateway.md index 814c550d4c..3634c36652 100644 --- a/documentation/docs/src/nodes/gateway.md +++ b/documentation/docs/src/nodes/gateway.md @@ -3,8 +3,8 @@ > The gateway setup and maintenance guide has migrated to the [Operator Guides book](https://nymtech.net/operators/nodes/gateway-setup.html). Gateways are key to both the usability of the mixnet, and the operation of the mixnet's tokenomics. They serve two main functions: -* In the future (when the mixnet is no longer running in 'free to use' mode), to check for zkNym credentials (previously referred to as [Coconut Credentials](../bandwidth-credentials.md)) with which users can anonymously prove they have paid to send traffic through the mixnet. A % of the worth of these credentials will be distributed to the operator of the gateway periodically as payment for providing their service. The more credentials user clients 'spend' with them the higher the rewards for the gateway operator and their delegators. The rest of this value will be sent to the Mixmining Pool, a pool of tokens from which `NYM` rewards are distributed to mix node operators. -* Act as a mailbox for connected clients. Clients create a lasting relationship with a gateway on initialisation, binding themselves to always use a particular gateway as their egress point for mixnet traffic, and always receive mixnet traffic from it (see the [mixnet traffic flow page](../architecture/traffic-flow.md) and the [addressing scheme](../clients/addressing-system.md) for further details). If a client is offline and the Gateway can't deliver packets addressed to it, they will hold these packets until the client comes back online. +* In the future (when the mixnet is no longer running in 'free to use' mode), to check for zkNym credentials (previously referred to as [Coconut Credentials](../bandwidth-credentials.md)) with which users can anonymously prove they have paid to send traffic through the mixnet. A % of the worth of these credentials will be distributed to the operator of the gateway periodically as payment for providing their service. The more credentials user clients 'spend' with them, the higher the rewards for the gateway operator and their delegators will be. The rest of this value will be sent to the Mixmining Pool, a pool of tokens from which `NYM` rewards are distributed to mix node operators. +* Act as a mailbox for connected clients. Clients create a lasting relationship with a gateway on initialisation, binding themselves to always use a particular gateway as their ingress point for mixnet traffic (this also means that this gateway is the egress point for any traffic sent _to_ this client - see the [mixnet traffic flow page](../architecture/traffic-flow.md) and the [addressing scheme](../clients/addressing-system.md) for further details). If a client is offline and the Gateway can't deliver packets addressed to it, they will hold these packets until the client comes back online. ## Further Reading * [Nym Whitepaper](https://nymtech.net/nym-whitepaper.pdf) section 4.2 diff --git a/documentation/docs/src/nodes/mixnode.md b/documentation/docs/src/nodes/mixnode.md index 44dc924d86..b093c6f0b4 100644 --- a/documentation/docs/src/nodes/mixnode.md +++ b/documentation/docs/src/nodes/mixnode.md @@ -6,7 +6,7 @@ Mix nodes are the backbone of the mixnet. These are the nodes that perform 'mix Mix nodes, after receiving a packet, decrypt its outer 'layer' and hold it for a variable amount of time before passing it to its next destination - either another mix node, or a gateway. In doing so, they 'mix' packets by sending them to their next destination in a different order than they were received. -Mix nodes are rewarded according to their quality of service, and their inclusion in the active set (i.e. the nodes that mix traffic for the next epoch) affected by this (as well as their delegation-based reputation - see [further reading](#further-reading) for more on this). +Mix nodes are rewarded according to their quality of service, and the probability of their inclusion in the active set (i.e. the nodes that mix traffic for the next epoch) is also affected by this (as well as their delegation-based reputation - see the [Mix node deepdive](#further-reading) below for more on this). ## (Coming soon) Mixing: a Step-by-Step Breakdown diff --git a/documentation/docs/src/nodes/validator.md b/documentation/docs/src/nodes/validator.md index eba1666351..855cf21700 100644 --- a/documentation/docs/src/nodes/validator.md +++ b/documentation/docs/src/nodes/validator.md @@ -8,8 +8,5 @@ The blockchain plays a supporting but fundamental role in the mixnet: the `NYM` ## Further Reading * Detailed info on Nyx Validators and token flow can be found in [Nym Reward Sharing for Mixnets document](https://nymtech.net/nym-cryptoecon-paper.pdf) in section 2.3 and 2.4. -* For more detailed calculation, read our blog post [Nym Token Economics update](https://blog.nymtech.net/nym-token-economics-update-fedff0ed5267). -* More info on staking can be found [here](https://blog.nymtech.net/staking-in-nym-introducing-mainnet-mixmining-f9bb1cbc7c36). -* [Here](https://blog.nymtech.net/want-to-stake-in-nym-here-is-how-to-choose-a-mix-node-to-delegate-nym-to-c3b862add165) is more info on how to choose a mix node for delegation. * Our [quarterly update](https://blog.nymtech.net/quarterly-token-economic-parameter-update-b2862948710f) on token economics from July 2023. * [Nym Whitepaper](https://nymtech.net/nym-whitepaper.pdf) section 3.1 \ No newline at end of file From 62c03b3136f7dbcffc929cdd80e6e474f37583f0 Mon Sep 17 00:00:00 2001 From: mfahampshire Date: Wed, 9 Aug 2023 11:47:11 +0200 Subject: [PATCH 109/361] removed doubled header --- documentation/operators/src/preliminary-steps.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/documentation/operators/src/preliminary-steps.md b/documentation/operators/src/preliminary-steps.md index ec13e7ea59..2f7cb2008b 100644 --- a/documentation/operators/src/preliminary-steps.md +++ b/documentation/operators/src/preliminary-steps.md @@ -1,8 +1,6 @@ # Preliminary Steps -> The Nym `mix node`, `gateway` and `network-requester` binaries were built in the [building nym](./binaries/building-nym.md) section. If you haven't yet built Nym and want to run the code, go there first. - -## Preliminary steps +> The Nym `mixnode`, `gateway` and `network-requester` binaries were built in the [building nym](./binaries/building-nym.md) section. If you haven't yet built Nym and want to run the code, go there first. There are a couple of steps that need completing before starting to set up your mix node, gateway or a network requester: From 3dd3692e8d61ce1e0f7a23e1d46557d40eea1f51 Mon Sep 17 00:00:00 2001 From: mfahampshire Date: Wed, 9 Aug 2023 12:51:34 +0200 Subject: [PATCH 110/361] updated links in operator binaries compilation list --- documentation/operators/src/binaries/building-nym.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/documentation/operators/src/binaries/building-nym.md b/documentation/operators/src/binaries/building-nym.md index 8610fcb63a..20c157ad21 100644 --- a/documentation/operators/src/binaries/building-nym.md +++ b/documentation/operators/src/binaries/building-nym.md @@ -55,12 +55,12 @@ NETWORK=sandbox cargo build --release # build your binaries with **sandbox** con Quite a bit of stuff gets built. The key working parts are: -* [mix node](../nodes/mix-node-setup.md): `nym-mixnode` -* [gateway node](../nodes/gateway-setup.md): `nym-gateway` +* [mix node](../nodes/mix-node.md): `nym-mixnode` +* [gateway node](../nodes/gateway.md): `nym-gateway` * [websocket client](https://nymtech.net/docs/clients/websocket-client.html): `nym-client` * [socks5 client](https://nymtech.net/docs/clients/socks5-client.html): `nym-socks5-client` * [webassembly client](https://nymtech.net/docs/clients/webassembly-client.html): `webassembly-client` -* [network requester](../nodes/network-requester-setup.md): `nym-network-requester` +* [network requester](../nodes/network-requester.md): `nym-network-requester` * [nym-cli tool](https://nymtech.net/docs/tools/nym-cli.html): `nym-cli` The repository also contains Typescript applications which aren't built in this process. These can be built by following the instructions on their respective docs pages. From fc7e4be1750144dc85d0b15041bc0dc50724d962 Mon Sep 17 00:00:00 2001 From: mfahampshire Date: Wed, 9 Aug 2023 13:25:37 +0200 Subject: [PATCH 111/361] fixed links --- documentation/operators/src/binaries/building-nym.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/documentation/operators/src/binaries/building-nym.md b/documentation/operators/src/binaries/building-nym.md index 20c157ad21..8610fcb63a 100644 --- a/documentation/operators/src/binaries/building-nym.md +++ b/documentation/operators/src/binaries/building-nym.md @@ -55,12 +55,12 @@ NETWORK=sandbox cargo build --release # build your binaries with **sandbox** con Quite a bit of stuff gets built. The key working parts are: -* [mix node](../nodes/mix-node.md): `nym-mixnode` -* [gateway node](../nodes/gateway.md): `nym-gateway` +* [mix node](../nodes/mix-node-setup.md): `nym-mixnode` +* [gateway node](../nodes/gateway-setup.md): `nym-gateway` * [websocket client](https://nymtech.net/docs/clients/websocket-client.html): `nym-client` * [socks5 client](https://nymtech.net/docs/clients/socks5-client.html): `nym-socks5-client` * [webassembly client](https://nymtech.net/docs/clients/webassembly-client.html): `webassembly-client` -* [network requester](../nodes/network-requester.md): `nym-network-requester` +* [network requester](../nodes/network-requester-setup.md): `nym-network-requester` * [nym-cli tool](https://nymtech.net/docs/tools/nym-cli.html): `nym-cli` The repository also contains Typescript applications which aren't built in this process. These can be built by following the instructions on their respective docs pages. From a1295a316b9b004f906c45fdc18323aa901389b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C4=99drzej=20Stuczy=C5=84ski?= Date: Wed, 9 Aug 2023 12:50:34 +0100 Subject: [PATCH 112/361] utility 'Stream' implementation for MixnetClient --- sdk/rust/nym-sdk/src/mixnet/native_client.rs | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/sdk/rust/nym-sdk/src/mixnet/native_client.rs b/sdk/rust/nym-sdk/src/mixnet/native_client.rs index 569997dcc6..774b5c9868 100644 --- a/sdk/rust/nym-sdk/src/mixnet/native_client.rs +++ b/sdk/rust/nym-sdk/src/mixnet/native_client.rs @@ -1,21 +1,21 @@ +use crate::mixnet::client::{IncludedSurbs, MixnetClientBuilder}; +use crate::Result; +use futures::{Stream, StreamExt}; use nym_client_core::client::{ base_client::{ClientInput, ClientOutput, ClientState}, inbound_messages::InputMessage, received_buffer::ReconstructedMessagesReceiver, }; use nym_sphinx::addressing::clients::Recipient; +use nym_sphinx::anonymous_replies::requests::AnonymousSenderTag; use nym_sphinx::{params::PacketType, receiver::ReconstructedMessage}; use nym_task::{ connections::{ConnectionCommandSender, LaneQueueLengths, TransmissionLane}, TaskManager, }; - -use futures::StreamExt; -use nym_sphinx::anonymous_replies::requests::AnonymousSenderTag; use nym_topology::NymTopology; - -use crate::mixnet::client::{IncludedSurbs, MixnetClientBuilder}; -use crate::Result; +use std::pin::Pin; +use std::task::{Context, Poll}; /// Client connected to the Nym mixnet. pub struct MixnetClient { @@ -274,3 +274,11 @@ impl MixnetClientSender { } } } + +impl Stream for MixnetClient { + type Item = Vec; + + fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + Pin::new(&mut self.reconstructed_receiver).poll_next(cx) + } +} From 2db3297d1e297680d9871c0c55c7345e63ad9eef Mon Sep 17 00:00:00 2001 From: mfahampshire Date: Wed, 9 Aug 2023 14:11:06 +0200 Subject: [PATCH 113/361] updated grantee info update link --- documentation/dev-portal/src/info-request.md | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/documentation/dev-portal/src/info-request.md b/documentation/dev-portal/src/info-request.md index 551da83bf3..e3edbdc3de 100644 --- a/documentation/dev-portal/src/info-request.md +++ b/documentation/dev-portal/src/info-request.md @@ -1,6 +1,6 @@ # Change Service Grantee Information -The Nym Service Grantees User Information Update Form allows Service Grantees to update their information with the Nym team. The form allows grantees to update their current: +If you wish to update any of the following information: - `Email Address` - `NYM address` - `Network Requester` @@ -9,10 +9,6 @@ The Nym Service Grantees User Information Update Form allows Service Grantees to - `Service` - `Payment Address` -Complete and submit your form so the Nym team to keep track of the service grantees and your current information, ensuring that deliver payments and handle other logistics smooth and accurately. - -> ⚠️ Service Grantee User Information will be updated by our team by 5pm UTC between Mon - Fri. - - +Please reach out either in the `#service-grantees` channel on Discord or on [Matrix](https://matrix.to/#/#nym-community:nymtech.chat). From 6be7c094be53209ea488569b9eb346332dcae809 Mon Sep 17 00:00:00 2001 From: mfahampshire Date: Wed, 9 Aug 2023 14:24:57 +0200 Subject: [PATCH 114/361] fixed incorrect import path for chat app --- documentation/docs/src/sdk/typescript.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/documentation/docs/src/sdk/typescript.md b/documentation/docs/src/sdk/typescript.md index eed1167692..5ce939e858 100644 --- a/documentation/docs/src/sdk/typescript.md +++ b/documentation/docs/src/sdk/typescript.md @@ -47,7 +47,7 @@ There are multiple example projects in [`nym/sdk/typescript/examples/`](https:// The best place to start if you just want to quickly get a basic frontend up and running with which to experiment is `examples/plain-html`: ```typescript -{{#include ../../../../sdk/typescript/examples/plain-html/src/index.ts}} +{{#include ../../../../sdk/typescript/examples/chat-app/plain-html/src/index.ts}} ``` As you can see, all that is required to create an ephemeral keypair and connect to the mixnet is creating a client and then subscribing to the mixnet events coming down the websocket, and adding logic to deal with them. From 05259410b20152e6eb16969977273d33076f09cd Mon Sep 17 00:00:00 2001 From: mfahampshire Date: Wed, 9 Aug 2023 14:25:29 +0200 Subject: [PATCH 115/361] changed out of date var 'mix_node_release_version' to 'platform_release_version --- documentation/operators/src/nodes/maintenance.md | 2 +- documentation/operators/src/nodes/mix-node-setup.md | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/documentation/operators/src/nodes/maintenance.md b/documentation/operators/src/nodes/maintenance.md index 911a6825af..bb7a0f0ea7 100644 --- a/documentation/operators/src/nodes/maintenance.md +++ b/documentation/operators/src/nodes/maintenance.md @@ -230,7 +230,7 @@ Here's a systemd service file to do that: ```ini [Unit] -Description=Nym Mixnode ({{mix_node_release_version}}) +Description=Nym Mixnode ({{platform_release_version}}) StartLimitInterval=350 StartLimitBurst=10 diff --git a/documentation/operators/src/nodes/mix-node-setup.md b/documentation/operators/src/nodes/mix-node-setup.md index 1c70383a61..a50c5cac13 100644 --- a/documentation/operators/src/nodes/mix-node-setup.md +++ b/documentation/operators/src/nodes/mix-node-setup.md @@ -87,13 +87,13 @@ Mixnode configuration completed. |_| |_|\__, |_| |_| |_| |___/ - (nym-mixnode - version {{mix_node_release_version}}) + (nym-mixnode - version {{platform_release_version}}) -Identity Key: DhmUYedPZvhP9MMwXdNpPaqCxxTQgjAg78s2nqtTTiNF","version":"{{mix_node_release_version}}"},"cost_params +Identity Key: DhmUYedPZvhP9MMwXdNpPaqCxxTQgjAg78s2nqtTTiNF","version":"{{platform_release_version}}"},"cost_params Sphinx Key: CfZSy1jRfrfiVi9JYexjFWPqWkKoY72t7NdpWaq37K8Z Host: 62.240.134.189 (bind address: 62.240.134.189) -Version: {{mix_node_release_version}} +Version: {{platform_release_version}} Mix Port: 1789, Verloc port: 1790, Http Port: 8000 ``` ~~~ @@ -134,7 +134,7 @@ It will look something like this: |_| |_|\__, |_| |_| |_| |___/ - (nym-mixnode - version {{mix_node_release_version}}) + (nym-mixnode - version {{platform_release_version}}) >>> attempting to sign 22Z9wt4PyiBCbMiErxj5bBa4VCCFsjNawZ1KnLyMeV9pMUQGyksRVANbXHjWndMUaXNRnAuEVJW6UCxpRJwZe788hDt4sicsrv7iAXRajEq19cWPVybbUqgeo76wbXbCbRdg1FvVKgYZGZZp8D72p5zWhKSBRD44qgCrqzfV1SkiFEhsvcLUvZATdLRocAUL75KmWivyRiQjCE1XYEWyRH9yvRYn4TymWwrKVDtEB63zhHjATN4QEi2E5qSrSbBcmmqatXsKakbgSbQoLsYygcHx7tkwbQ2HDYzeiKP1t16Rhcjn6Ftc2FuXUNnTcibk2LQ1hiqu3FAq31bHUbzn2wiaPfm4RgqTwGM4eqnjBofwR3251wQSxbYwKUYwGsrkweRcoPuEaovApR9R19oJ7GVG5BrKmFwZWX3XFVuECe8vt1x9MY7DbQ3xhAapsHhThUmzN6JPPU4qbQ3PdMt3YVWy6oRhap97ma2dPMBaidebfgLJizpRU3Yu7mtb6E8vgi5Xnehrgtd35gitoJqJUY5sB1p6TDPd6vk3MVU1zqusrke7Lvrud4xKfCLqp672Bj9eGb2wPwow643CpHuMkhigfSWsv9jDq13d75EGTEiprC2UmWTzCJWHrDH7ka68DZJ5XXAW67DBewu7KUm1jrJkNs55vS83SWwm5RjzQLVhscdtCH1Bamec6uZoFBNVzjs21o7ax2WHDghJpGMxFi6dmdMCZpqn618t4 From e705b76b363279d8dae8bd468ff0ae3ad9bacdda Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C4=99drzej=20Stuczy=C5=84ski?= Date: Wed, 9 Aug 2023 14:27:17 +0100 Subject: [PATCH 116/361] unified sending interface + example of split sending --- Cargo.lock | 1 + contracts/Cargo.lock | 6 - documentation/docs/src/sdk/rust.md | 2 +- nym-connect/desktop/Cargo.lock | 2 +- nym-wallet/Cargo.lock | 274 +++++++++++++++--- sdk/rust/nym-sdk/Cargo.toml | 1 + sdk/rust/nym-sdk/examples/bandwidth.rs | 6 +- sdk/rust/nym-sdk/examples/builder.rs | 6 +- .../nym-sdk/examples/builder_with_storage.rs | 6 +- .../examples/custom_topology_provider.rs | 6 +- .../examples/manually_handle_storage.rs | 6 +- .../examples/manually_overwrite_topology.rs | 6 +- .../parallel_sending_and_receiving.rs | 55 ++++ sdk/rust/nym-sdk/examples/simple.rs | 6 +- sdk/rust/nym-sdk/examples/surb-reply.rs | 13 +- sdk/rust/nym-sdk/src/error.rs | 3 + sdk/rust/nym-sdk/src/mixnet.rs | 2 + sdk/rust/nym-sdk/src/mixnet/native_client.rs | 175 +++-------- sdk/rust/nym-sdk/src/mixnet/traits.rs | 119 ++++++++ .../common/examples/control_requests.rs | 16 +- .../network-requester/examples/query.rs | 12 +- .../network-requester/src/core.rs | 7 +- tools/nym-nr-query/src/main.rs | 22 +- 23 files changed, 523 insertions(+), 229 deletions(-) create mode 100644 sdk/rust/nym-sdk/examples/parallel_sending_and_receiving.rs create mode 100644 sdk/rust/nym-sdk/src/mixnet/traits.rs diff --git a/Cargo.lock b/Cargo.lock index 85ff104b27..b072b0a686 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4438,6 +4438,7 @@ dependencies = [ name = "nym-sdk" version = "0.1.0" dependencies = [ + "async-trait", "bip39", "dotenvy", "futures", diff --git a/contracts/Cargo.lock b/contracts/Cargo.lock index 0f56b6f7a9..c87480ad1b 100644 --- a/contracts/Cargo.lock +++ b/contracts/Cargo.lock @@ -290,12 +290,6 @@ dependencies = [ "libc", ] -[[package]] -name = "crunchy" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" - [[package]] name = "crypto-bigint" version = "0.4.9" diff --git a/documentation/docs/src/sdk/rust.md b/documentation/docs/src/sdk/rust.md index 68004b7576..bf52bb441a 100644 --- a/documentation/docs/src/sdk/rust.md +++ b/documentation/docs/src/sdk/rust.md @@ -54,7 +54,7 @@ If you're integrating mixnet functionality into an existing app and want to inte ``` ### Anonymous replies with SURBs -Both functions used to send messages through the mixnet (`send_str` and `send_bytes`) send a pre-determined number of SURBs along with their messages by default. +Both functions used to send messages through the mixnet (`send_message` and `send_plain_message`) send a pre-determined number of SURBs along with their messages by default. The number of SURBs is set [here](https://github.com/nymtech/nym/blob/release/{{platform_release_version}}/sdk/rust/nym-sdk/src/mixnet/client.rs#L34): diff --git a/nym-connect/desktop/Cargo.lock b/nym-connect/desktop/Cargo.lock index c522d297d1..6ef76b497a 100644 --- a/nym-connect/desktop/Cargo.lock +++ b/nym-connect/desktop/Cargo.lock @@ -3656,7 +3656,7 @@ dependencies = [ [[package]] name = "nym-connect" -version = "1.1.15" +version = "1.1.16" dependencies = [ "anyhow", "bip39", diff --git a/nym-wallet/Cargo.lock b/nym-wallet/Cargo.lock index b9d551fd84..da53aee71f 100644 --- a/nym-wallet/Cargo.lock +++ b/nym-wallet/Cargo.lock @@ -21,7 +21,20 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0" dependencies = [ "crypto-common", - "generic-array", + "generic-array 0.14.7", +] + +[[package]] +name = "aes" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e8b47f52ea9bae42228d07ec09eb676433d7c4ed1ebdf0f1d1c29ed446f1ab8" +dependencies = [ + "cfg-if", + "cipher 0.3.0", + "cpufeatures", + "ctr 0.8.0", + "opaque-debug 0.3.0", ] [[package]] @@ -31,7 +44,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "433cfd6710c9986c576a25ca913c39d66a6474107b406f34f91d4a8923395241" dependencies = [ "cfg-if", - "cipher", + "cipher 0.4.4", "cpufeatures", ] @@ -42,11 +55,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "82e1366e0c69c9f927b1fa5ce2c7bf9eafc8f9268c0b9800729e8b267612447c" dependencies = [ "aead", - "aes", - "cipher", - "ctr", + "aes 0.8.2", + "cipher 0.4.4", + "ctr 0.9.2", "ghash", - "subtle", + "subtle 2.4.1", ] [[package]] @@ -97,7 +110,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "95c2fcf79ad1932ac6269a738109997a83c227c09b75842ae564dc8ede6a861c" dependencies = [ "base64ct", - "blake2", + "blake2 0.10.6", "password-hash", ] @@ -212,14 +225,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7e141fb0f8be1c7b45887af94c88b182472b57c96b56773250ae00cd6a14a164" dependencies = [ "bs58 0.5.0", - "hmac", + "hmac 0.12.1", "k256 0.13.1", "once_cell", "pbkdf2", "rand_core 0.6.4", "ripemd", "sha2 0.10.6", - "subtle", + "subtle 2.4.1", "zeroize", ] @@ -261,6 +274,18 @@ dependencies = [ "wyz", ] +[[package]] +name = "blake2" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94cb07b0da6a73955f8fb85d24c466778e70cda767a568229b104f0264089330" +dependencies = [ + "byte-tools", + "crypto-mac 0.7.0", + "digest 0.8.1", + "opaque-debug 0.2.3", +] + [[package]] name = "blake2" version = "0.10.6" @@ -282,7 +307,7 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" dependencies = [ - "generic-array", + "generic-array 0.14.7", ] [[package]] @@ -291,7 +316,7 @@ version = "0.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69cce20737498f97b993470a6e536b8523f0af7892a4f928cceb1ac5e52ebe7e" dependencies = [ - "generic-array", + "generic-array 0.14.7", ] [[package]] @@ -304,7 +329,7 @@ dependencies = [ "group 0.11.0", "pairing", "rand_core 0.6.4", - "subtle", + "subtle 2.4.1", "zeroize", ] @@ -366,6 +391,12 @@ version = "3.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0d261e256854913907f67ed06efbc3338dfe6179796deefc1ff763fc1aee5535" +[[package]] +name = "byte-tools" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3b5ca7a04898ad4bcd41c90c5285445ff5b791899bb1b0abdd2a2aa791211d7" + [[package]] name = "bytemuck" version = "1.13.0" @@ -480,6 +511,15 @@ dependencies = [ "keystream", ] +[[package]] +name = "cipher" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ee52072ec15386f770805afd189a01c8841be8696bed250fa2f13c4c0d6dfb7" +dependencies = [ + "generic-array 0.14.7", +] + [[package]] name = "cipher" version = "0.4.4" @@ -809,9 +849,9 @@ version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ef2b4b23cddf68b89b8f8069890e8c270d54e2d5fe1b143820234805e4cb17ef" dependencies = [ - "generic-array", + "generic-array 0.14.7", "rand_core 0.6.4", - "subtle", + "subtle 2.4.1", "zeroize", ] @@ -821,9 +861,9 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf4c2f4e1afd912bc40bfd6fed5d9dc1f288e0ba01bfcc835cc5bc3eb13efe15" dependencies = [ - "generic-array", + "generic-array 0.14.7", "rand_core 0.6.4", - "subtle", + "subtle 2.4.1", "zeroize", ] @@ -833,11 +873,31 @@ version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" dependencies = [ - "generic-array", + "generic-array 0.14.7", "rand_core 0.6.4", "typenum", ] +[[package]] +name = "crypto-mac" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4434400df11d95d556bac068ddfedd482915eb18fe8bea89bc80b6e4b1c179e5" +dependencies = [ + "generic-array 0.12.4", + "subtle 1.0.0", +] + +[[package]] +name = "crypto-mac" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1d1a86f49236c215f271d40892d5fc950490551400b02ef360692c29815c714" +dependencies = [ + "generic-array 0.14.7", + "subtle 2.4.1", +] + [[package]] name = "cssparser" version = "0.27.2" @@ -884,13 +944,22 @@ dependencies = [ "syn 1.0.107", ] +[[package]] +name = "ctr" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "049bb91fb4aaf0e3c7efa6cd5ef877dbbbd15b39dad06d9948de4ec8a75761ea" +dependencies = [ + "cipher 0.3.0", +] + [[package]] name = "ctr" version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0369ee1ad671834580515889b80f2ea915f23b8be8d0daa4bbaf2ac5c7590835" dependencies = [ - "cipher", + "cipher 0.4.4", ] [[package]] @@ -908,7 +977,7 @@ dependencies = [ "byteorder", "digest 0.9.0", "rand_core 0.5.1", - "subtle", + "subtle 2.4.1", "zeroize", ] @@ -1100,13 +1169,22 @@ dependencies = [ "syn 1.0.107", ] +[[package]] +name = "digest" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3d0c8c8752312f9713efd397ff63acb9f85585afbf179282e720e7704954dd5" +dependencies = [ + "generic-array 0.12.4", +] + [[package]] name = "digest" version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" dependencies = [ - "generic-array", + "generic-array 0.14.7", ] [[package]] @@ -1118,7 +1196,7 @@ dependencies = [ "block-buffer 0.10.3", "const-oid", "crypto-common", - "subtle", + "subtle 2.4.1", ] [[package]] @@ -1326,12 +1404,12 @@ dependencies = [ "der 0.6.1", "digest 0.10.7", "ff 0.12.1", - "generic-array", + "generic-array 0.14.7", "group 0.12.1", "pkcs8 0.9.0", "rand_core 0.6.4", "sec1 0.3.0", - "subtle", + "subtle 2.4.1", "zeroize", ] @@ -1345,12 +1423,12 @@ dependencies = [ "crypto-bigint 0.5.2", "digest 0.10.7", "ff 0.13.0", - "generic-array", + "generic-array 0.14.7", "group 0.13.0", "pkcs8 0.10.2", "rand_core 0.6.4", "sec1 0.7.3", - "subtle", + "subtle 2.4.1", "zeroize", ] @@ -1459,7 +1537,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "131655483be284720a17d74ff97592b8e76576dc25563148601df2d7c9080924" dependencies = [ "rand_core 0.6.4", - "subtle", + "subtle 2.4.1", ] [[package]] @@ -1469,7 +1547,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d013fc25338cc558c5c2cfbad646908fb23591e2404481826742b651c9af7160" dependencies = [ "rand_core 0.6.4", - "subtle", + "subtle 2.4.1", ] [[package]] @@ -1479,7 +1557,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ded41244b729663b1e574f1b4fb731469f69f79c17667b5d776b16cda0479449" dependencies = [ "rand_core 0.6.4", - "subtle", + "subtle 2.4.1", ] [[package]] @@ -1759,6 +1837,15 @@ dependencies = [ "windows 0.39.0", ] +[[package]] +name = "generic-array" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffdf9f34f1447443d37393cc6c2b8313aebddcd96906caf34e54c68d8e57d7bd" +dependencies = [ + "typenum", +] + [[package]] name = "generic-array" version = "0.14.7" @@ -1814,7 +1901,7 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d930750de5717d2dd0b8c0d42c076c0e884c81a73e6cab859bbd2339c71e3e40" dependencies = [ - "opaque-debug", + "opaque-debug 0.3.0", "polyval", ] @@ -1945,7 +2032,7 @@ dependencies = [ "byteorder", "ff 0.11.1", "rand_core 0.6.4", - "subtle", + "subtle 2.4.1", ] [[package]] @@ -1956,7 +2043,7 @@ checksum = "5dfbfb3a6cfbd390d5c9564ab283a0349b9b9fcd46a706c1eb10e0db70bfbac7" dependencies = [ "ff 0.12.1", "rand_core 0.6.4", - "subtle", + "subtle 2.4.1", ] [[package]] @@ -1967,7 +2054,7 @@ checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" dependencies = [ "ff 0.13.0", "rand_core 0.6.4", - "subtle", + "subtle 2.4.1", ] [[package]] @@ -2143,6 +2230,26 @@ version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7ebdb29d2ea9ed0083cd8cece49bbd968021bd99b0849edb4a9a7ee0fdf6a4e0" +[[package]] +name = "hkdf" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01706d578d5c281058480e673ae4086a9f4710d8df1ad80a5b03e39ece5f886b" +dependencies = [ + "digest 0.9.0", + "hmac 0.11.0", +] + +[[package]] +name = "hmac" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a2a2320eb7ec0ebe8da8f744d7812d9fc4cb4d09344ac01898dbcb6a20ae69b" +dependencies = [ + "crypto-mac 0.11.1", + "digest 0.9.0", +] + [[package]] name = "hmac" version = "0.12.1" @@ -2394,7 +2501,7 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5" dependencies = [ - "generic-array", + "generic-array 0.14.7", ] [[package]] @@ -2553,6 +2660,12 @@ dependencies = [ "signature 2.1.0", ] +[[package]] +name = "keystream" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c33070833c9ee02266356de0c43f723152bd38bd96ddf52c82b3af10c9138b28" + [[package]] name = "kuchiki" version = "0.8.1" @@ -2589,6 +2702,12 @@ dependencies = [ "pkg-config", ] +[[package]] +name = "libm" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7012b1bbb0719e1097c47611d3898568c546d597c2e74d66f6087edd5233ff4" + [[package]] name = "libz-sys" version = "1.1.8" @@ -2616,6 +2735,18 @@ version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f051f77a7c8e6957c0696eac88f26b0117e54f52d3fc682ab19397a8812846a4" +[[package]] +name = "lioness" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ae926706ba42c425c9457121178330d75e273df2e82e28b758faf3de3a9acb9" +dependencies = [ + "arrayref", + "blake2 0.8.1", + "chacha", + "keystream", +] + [[package]] name = "lock_api" version = "0.4.9" @@ -2859,6 +2990,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" dependencies = [ "autocfg", + "libm", ] [[package]] @@ -3146,7 +3278,7 @@ version = "0.1.0" dependencies = [ "aes-gcm", "argon2", - "generic-array", + "generic-array 0.14.7", "getrandom 0.2.10", "rand 0.8.5", "serde", @@ -3364,6 +3496,12 @@ version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6f61fba1741ea2b3d6a1e3178721804bb716a68a6aeba1149b5d52e3d464ea66" +[[package]] +name = "opaque-debug" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2839e79665f131bdb5782e51f2c6c9599c133c6098982a54c794358bf432529c" + [[package]] name = "opaque-debug" version = "0.3.0" @@ -3518,7 +3656,7 @@ checksum = "346f04948ba92c43e8469c1ee6736c7563d71012b17d40745260fe106aac2166" dependencies = [ "base64ct", "rand_core 0.6.4", - "subtle", + "subtle 2.4.1", ] [[package]] @@ -3540,7 +3678,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8ed6a7761f76e3b9f92dfb0a60a6a6477c61024b775147ff0973a02653abaf2" dependencies = [ "digest 0.10.7", - "hmac", + "hmac 0.12.1", ] [[package]] @@ -3821,7 +3959,7 @@ checksum = "7ef234e08c11dfcb2e56f79fd70f6f2eb7f025c0ce2333e82f4f0518ecad30c6" dependencies = [ "cfg-if", "cpufeatures", - "opaque-debug", + "opaque-debug 0.3.0", "universal-hash", ] @@ -4027,6 +4165,16 @@ dependencies = [ "getrandom 0.2.10", ] +[[package]] +name = "rand_distr" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9e9532ada3929fb8b2e9dbe28d1e06c9b2cc65813f074fcb6bd5fbefeff9d56" +dependencies = [ + "num-traits", + "rand 0.7.3", +] + [[package]] name = "rand_hc" version = "0.2.0" @@ -4153,7 +4301,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7743f17af12fa0b03b803ba12cd6a8d9483a587e89c69445e3909655c0b9fabb" dependencies = [ "crypto-bigint 0.4.9", - "hmac", + "hmac 0.12.1", "zeroize", ] @@ -4163,8 +4311,8 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2" dependencies = [ - "hmac", - "subtle", + "hmac 0.12.1", + "subtle 2.4.1", ] [[package]] @@ -4363,9 +4511,9 @@ checksum = "3be24c1842290c45df0a7bf069e0c268a747ad05a192f2fd7dcfdbc1cba40928" dependencies = [ "base16ct 0.1.1", "der 0.6.1", - "generic-array", + "generic-array 0.14.7", "pkcs8 0.9.0", - "subtle", + "subtle 2.4.1", "zeroize", ] @@ -4377,9 +4525,9 @@ checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc" dependencies = [ "base16ct 0.2.0", "der 0.7.7", - "generic-array", + "generic-array 0.14.7", "pkcs8 0.10.2", - "subtle", + "subtle 2.4.1", "zeroize", ] @@ -4620,7 +4768,7 @@ dependencies = [ "cfg-if", "cpufeatures", "digest 0.9.0", - "opaque-debug", + "opaque-debug 0.3.0", ] [[package]] @@ -4731,6 +4879,30 @@ dependencies = [ "system-deps 5.0.0", ] +[[package]] +name = "sphinx-packet" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc43eda802856ee82a7555c7b75ceb9e07451741c7a2f5f23d036020e01189d4" +dependencies = [ + "aes 0.7.5", + "arrayref", + "blake2 0.8.1", + "bs58 0.4.0", + "byteorder", + "chacha", + "curve25519-dalek", + "digest 0.9.0", + "hkdf", + "hmac 0.11.0", + "lioness", + "log", + "rand 0.7.3", + "rand_distr", + "sha2 0.9.9", + "subtle 2.4.1", +] + [[package]] name = "spin" version = "0.5.2" @@ -4826,6 +4998,12 @@ dependencies = [ "syn 1.0.107", ] +[[package]] +name = "subtle" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d67a5a62ba6e01cb2192ff309324cb4875d0c451d55fe2319433abe7a05a8ee" + [[package]] name = "subtle" version = "2.4.1" @@ -5185,7 +5363,7 @@ dependencies = [ "serde_repr", "sha2 0.10.6", "signature 2.1.0", - "subtle", + "subtle 2.4.1", "subtle-encoding", "tendermint-proto", "time", @@ -5245,7 +5423,7 @@ dependencies = [ "serde", "serde_bytes", "serde_json", - "subtle", + "subtle 2.4.1", "subtle-encoding", "tendermint", "tendermint-config", @@ -5643,7 +5821,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7d3160b73c9a19f7e2939a2fdad446c57c1bbbbf4d919d3213ff1267a580d8b5" dependencies = [ "crypto-common", - "subtle", + "subtle 2.4.1", ] [[package]] diff --git a/sdk/rust/nym-sdk/Cargo.toml b/sdk/rust/nym-sdk/Cargo.toml index 7aaced99aa..d4b2d16b5b 100644 --- a/sdk/rust/nym-sdk/Cargo.toml +++ b/sdk/rust/nym-sdk/Cargo.toml @@ -6,6 +6,7 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +async-trait = { workspace = true } bip39 = { workspace = true } nym-client-core = { path = "../../../common/client-core", features = ["fs-surb-storage"]} nym-crypto = { path = "../../../common/crypto" } diff --git a/sdk/rust/nym-sdk/examples/bandwidth.rs b/sdk/rust/nym-sdk/examples/bandwidth.rs index fbe100f037..b7f452fa92 100644 --- a/sdk/rust/nym-sdk/examples/bandwidth.rs +++ b/sdk/rust/nym-sdk/examples/bandwidth.rs @@ -1,4 +1,5 @@ use nym_sdk::mixnet; +use nym_sdk::mixnet::MixnetMessageSender; #[tokio::main] async fn main() { @@ -29,7 +30,10 @@ async fn main() { let our_address = client.nym_address(); // Send a message throughout the mixnet to ourselves - client.send_str(*our_address, "hello there").await; + client + .send_plain_message(*our_address, "hello there") + .await + .unwrap(); println!("Waiting for message"); if let Some(received) = client.wait_for_messages().await { diff --git a/sdk/rust/nym-sdk/examples/builder.rs b/sdk/rust/nym-sdk/examples/builder.rs index af6beec5f8..6ab50bc92c 100644 --- a/sdk/rust/nym-sdk/examples/builder.rs +++ b/sdk/rust/nym-sdk/examples/builder.rs @@ -1,4 +1,5 @@ use nym_sdk::mixnet; +use nym_sdk::mixnet::MixnetMessageSender; #[tokio::main] async fn main() { @@ -19,7 +20,10 @@ async fn main() { println!("Our client nym address is: {our_address}"); // Send a message through the mixnet to ourselves - client.send_str(*our_address, "hello there").await; + client + .send_plain_message(*our_address, "hello there") + .await + .unwrap(); println!("Waiting for message"); if let Some(received) = client.wait_for_messages().await { diff --git a/sdk/rust/nym-sdk/examples/builder_with_storage.rs b/sdk/rust/nym-sdk/examples/builder_with_storage.rs index ca7a732e5b..900b2d323f 100644 --- a/sdk/rust/nym-sdk/examples/builder_with_storage.rs +++ b/sdk/rust/nym-sdk/examples/builder_with_storage.rs @@ -1,4 +1,5 @@ use nym_sdk::mixnet; +use nym_sdk::mixnet::MixnetMessageSender; use std::path::PathBuf; #[tokio::main] @@ -26,7 +27,10 @@ async fn main() { println!("Our client nym address is: {our_address}"); // Send a message throught the mixnet to ourselves - client.send_str(*our_address, "hello there").await; + client + .send_plain_message(*our_address, "hello there") + .await + .unwrap(); println!("Waiting for message"); if let Some(received) = client.wait_for_messages().await { diff --git a/sdk/rust/nym-sdk/examples/custom_topology_provider.rs b/sdk/rust/nym-sdk/examples/custom_topology_provider.rs index 2ddf9d8ea0..7232f5ac58 100644 --- a/sdk/rust/nym-sdk/examples/custom_topology_provider.rs +++ b/sdk/rust/nym-sdk/examples/custom_topology_provider.rs @@ -2,6 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 use nym_sdk::mixnet; +use nym_sdk::mixnet::MixnetMessageSender; use nym_topology::provider_trait::{async_trait, TopologyProvider}; use nym_topology::{nym_topology_from_detailed, NymTopology}; use url::Url; @@ -69,7 +70,10 @@ async fn main() { println!("Our client nym address is: {our_address}"); // Send a message through the mixnet to ourselves - client.send_str(*our_address, "hello there").await; + client + .send_plain_message(*our_address, "hello there") + .await + .unwrap(); println!("Waiting for message (ctrl-c to exit)"); client diff --git a/sdk/rust/nym-sdk/examples/manually_handle_storage.rs b/sdk/rust/nym-sdk/examples/manually_handle_storage.rs index 3c5ef4f450..6130af5a25 100644 --- a/sdk/rust/nym-sdk/examples/manually_handle_storage.rs +++ b/sdk/rust/nym-sdk/examples/manually_handle_storage.rs @@ -3,6 +3,7 @@ use nym_client_core::client::base_client::storage::gateway_details::{ }; use nym_sdk::mixnet::{ self, EmptyReplyStorage, EphemeralCredentialStorage, KeyManager, KeyStore, MixnetClientStorage, + MixnetMessageSender, }; use nym_topology::provider_trait::async_trait; @@ -26,7 +27,10 @@ async fn main() { println!("Our client nym address is: {our_address}"); // Send important info up the pipe to a buddy - client.send_str(*our_address, "hello there").await; + client + .send_plain_message(*our_address, "hello there") + .await + .unwrap(); println!("Waiting for message"); if let Some(received) = client.wait_for_messages().await { diff --git a/sdk/rust/nym-sdk/examples/manually_overwrite_topology.rs b/sdk/rust/nym-sdk/examples/manually_overwrite_topology.rs index 3ef7e2257f..bad1c9651b 100644 --- a/sdk/rust/nym-sdk/examples/manually_overwrite_topology.rs +++ b/sdk/rust/nym-sdk/examples/manually_overwrite_topology.rs @@ -2,6 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 use nym_sdk::mixnet; +use nym_sdk::mixnet::MixnetMessageSender; use nym_topology::mix::Layer; use nym_topology::{mix, NymTopology}; use std::collections::BTreeMap; @@ -81,7 +82,10 @@ async fn main() { println!("Our client nym address is: {our_address}"); // Send a message through the mixnet to ourselves - client.send_str(*our_address, "hello there").await; + client + .send_plain_message(*our_address, "hello there") + .await + .unwrap(); println!("Waiting for message (ctrl-c to exit)"); client diff --git a/sdk/rust/nym-sdk/examples/parallel_sending_and_receiving.rs b/sdk/rust/nym-sdk/examples/parallel_sending_and_receiving.rs new file mode 100644 index 0000000000..e3690ce9e0 --- /dev/null +++ b/sdk/rust/nym-sdk/examples/parallel_sending_and_receiving.rs @@ -0,0 +1,55 @@ +// Copyright 2023 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use futures::StreamExt; +use nym_sdk::mixnet; +use nym_sdk::mixnet::MixnetMessageSender; +use std::sync::Arc; +use tokio::sync::Notify; + +#[tokio::main] +async fn main() { + nym_bin_common::logging::setup_logging(); + + // Passing no config makes the client fire up an ephemeral session and figure stuff out on its own + let mut client = mixnet::MixnetClient::connect_new().await.unwrap(); + + // Be able to get our client address + let our_address = *client.nym_address(); + println!("Our client nym address is: {our_address}"); + + let receive_notify = Arc::new(Notify::new()); + let receive_notify2 = receive_notify.clone(); + let send_notify = Arc::new(Notify::new()); + let send_notify2 = send_notify.clone(); + + let sender = client.split_sender(); + + // receiving task + tokio::spawn(async move { + if let Some(received) = client.next().await { + for r in received { + println!("Received: {}", String::from_utf8_lossy(&r.message)); + } + } + + client.disconnect().await; + // notify that we're done and leave the task! + receive_notify.notify_waiters(); + }); + + // sending task + tokio::spawn(async move { + sender + .send_plain_message(our_address, "hello from a different task!") + .await + .unwrap(); + + send_notify.notify_waiters(); + }); + + // wait for both tasks to be done + println!("waiting for shutdown"); + send_notify2.notified().await; + receive_notify2.notified().await; +} diff --git a/sdk/rust/nym-sdk/examples/simple.rs b/sdk/rust/nym-sdk/examples/simple.rs index f2325e6048..14720ba24c 100644 --- a/sdk/rust/nym-sdk/examples/simple.rs +++ b/sdk/rust/nym-sdk/examples/simple.rs @@ -1,4 +1,5 @@ use nym_sdk::mixnet; +use nym_sdk::mixnet::MixnetMessageSender; #[tokio::main] async fn main() { @@ -12,7 +13,10 @@ async fn main() { println!("Our client nym address is: {our_address}"); // Send a message throught the mixnet to ourselves - client.send_str(*our_address, "hello there").await; + client + .send_plain_message(*our_address, "hello there") + .await + .unwrap(); println!("Waiting for message (ctrl-c to exit)"); client diff --git a/sdk/rust/nym-sdk/examples/surb-reply.rs b/sdk/rust/nym-sdk/examples/surb-reply.rs index 38c7e8257a..abe955b2b9 100644 --- a/sdk/rust/nym-sdk/examples/surb-reply.rs +++ b/sdk/rust/nym-sdk/examples/surb-reply.rs @@ -1,5 +1,6 @@ use nym_sdk::mixnet::{ - AnonymousSenderTag, MixnetClientBuilder, ReconstructedMessage, StoragePaths, + AnonymousSenderTag, MixnetClientBuilder, MixnetMessageSender, ReconstructedMessage, + StoragePaths, }; use std::path::PathBuf; @@ -28,7 +29,10 @@ async fn main() { println!("\nOur client nym address is: {our_address}"); // Send a message through the mixnet to ourselves using our nym address - client.send_str(*our_address, "hello there").await; + client + .send_plain_message(*our_address, "hello there") + .await + .unwrap(); // we're going to parse the sender_tag (AnonymousSenderTag) from the incoming message and use it to 'reply' to ourselves instead of our Nym address. // we know there will be a sender_tag since the sdk sends SURBs along with messages by default. @@ -57,7 +61,10 @@ async fn main() { // reply to self with it: note we use `send_str_reply` instead of `send_str` println!("Replying with using SURBs"); - client.send_str_reply(return_recipient, "hi an0n!").await; + client + .send_reply(return_recipient, "hi an0n!") + .await + .unwrap(); println!("Waiting for message (once you see it, ctrl-c to exit)\n"); client diff --git a/sdk/rust/nym-sdk/src/error.rs b/sdk/rust/nym-sdk/src/error.rs index 48469271e4..a56d94cd4e 100644 --- a/sdk/rust/nym-sdk/src/error.rs +++ b/sdk/rust/nym-sdk/src/error.rs @@ -89,6 +89,9 @@ pub enum Error { #[error("loaded shared gateway key without providing information about what gateway it corresponds to")] GatewayWithUnknownEndpoint, + + #[error("failed to send the provided message")] + MessageSendingFailure, } pub type Result = std::result::Result; diff --git a/sdk/rust/nym-sdk/src/mixnet.rs b/sdk/rust/nym-sdk/src/mixnet.rs index c1b85aaff3..d082756cd6 100644 --- a/sdk/rust/nym-sdk/src/mixnet.rs +++ b/sdk/rust/nym-sdk/src/mixnet.rs @@ -36,6 +36,7 @@ mod connection_state; mod native_client; mod paths; mod socks5_client; +mod traits; pub use client::{DisconnectedMixnetClient, IncludedSurbs, MixnetClientBuilder}; pub use config::{Config, KeyMode}; @@ -73,3 +74,4 @@ pub use nym_sphinx::{ pub use nym_topology::{provider_trait::TopologyProvider, NymTopology}; pub use paths::StoragePaths; pub use socks5_client::Socks5MixnetClient; +pub use traits::MixnetMessageSender; diff --git a/sdk/rust/nym-sdk/src/mixnet/native_client.rs b/sdk/rust/nym-sdk/src/mixnet/native_client.rs index 774b5c9868..e03d585ebb 100644 --- a/sdk/rust/nym-sdk/src/mixnet/native_client.rs +++ b/sdk/rust/nym-sdk/src/mixnet/native_client.rs @@ -1,5 +1,7 @@ -use crate::mixnet::client::{IncludedSurbs, MixnetClientBuilder}; -use crate::Result; +use crate::mixnet::client::MixnetClientBuilder; +use crate::mixnet::traits::MixnetMessageSender; +use crate::{Error, Result}; +use async_trait::async_trait; use futures::{Stream, StreamExt}; use nym_client_core::client::{ base_client::{ClientInput, ClientOutput, ClientState}, @@ -7,10 +9,9 @@ use nym_client_core::client::{ received_buffer::ReconstructedMessagesReceiver, }; use nym_sphinx::addressing::clients::Recipient; -use nym_sphinx::anonymous_replies::requests::AnonymousSenderTag; use nym_sphinx::{params::PacketType, receiver::ReconstructedMessage}; use nym_task::{ - connections::{ConnectionCommandSender, LaneQueueLengths, TransmissionLane}, + connections::{ConnectionCommandSender, LaneQueueLengths}, TaskManager, }; use nym_topology::NymTopology; @@ -74,9 +75,10 @@ impl MixnetClient { /// Get a shallow clone of [`MixnetClientSender`]. Useful if you want split the send and /// receive logic in different locations. - pub fn sender(&self) -> MixnetClientSender { + pub fn split_sender(&self) -> MixnetClientSender { MixnetClientSender { client_input: self.client_input.clone(), + packet_type: self.packet_type, } } @@ -112,132 +114,6 @@ impl MixnetClient { self.client_state.topology_accessor.release_manual_control() } - /// Sends stringy data to the supplied Nym address - /// - /// # Example - /// - /// ```no_run - /// use nym_sdk::mixnet; - /// - /// #[tokio::main] - /// async fn main() { - /// let address = "foobar"; - /// let recipient = mixnet::Recipient::try_from_base58_string(address).unwrap(); - /// let mut client = mixnet::MixnetClient::connect_new().await.unwrap(); - /// client.send_str(recipient, "hi").await; - /// } - /// ``` - pub async fn send_str(&self, address: Recipient, message: &str) { - self.send_bytes(address, message, IncludedSurbs::default()) - .await; - } - - /// Sends bytes to the supplied Nym address. There is the option to specify the number of - /// reply-SURBs to include. - /// - /// # Example - /// - /// ```no_run - /// use nym_sdk::mixnet; - /// - /// #[tokio::main] - /// async fn main() { - /// let address = "foobar"; - /// let recipient = mixnet::Recipient::try_from_base58_string(address).unwrap(); - /// let mut client = mixnet::MixnetClient::connect_new().await.unwrap(); - /// let surbs = mixnet::IncludedSurbs::default(); - /// client.send_bytes(recipient, "hi".to_owned().into_bytes(), surbs).await; - /// } - /// ``` - pub async fn send_bytes>( - &self, - address: Recipient, - message: M, - surbs: IncludedSurbs, - ) { - let lane = TransmissionLane::General; - let input_msg = match surbs { - IncludedSurbs::Amount(surbs) => InputMessage::new_anonymous( - address, - message.as_ref().to_vec(), - surbs, - lane, - self.packet_type, - ), - IncludedSurbs::ExposeSelfAddress => InputMessage::new_regular( - address, - message.as_ref().to_vec(), - lane, - self.packet_type, - ), - }; - self.send(input_msg).await - } - - /// Sends stringy reply data to the supplied anonymous recipient. - /// - /// # Example - /// - /// ```no_run - /// use nym_sdk::mixnet; - /// - /// #[tokio::main] - /// async fn main() { - /// let mut client = mixnet::MixnetClient::connect_new().await.unwrap(); - /// // note: the tag is something you would have received from a remote client sending you surbs! - /// let tag = mixnet::AnonymousSenderTag::try_from_base58_string("foobar").unwrap(); - /// client.send_str_reply(tag, "hi").await; - /// } - /// ``` - pub async fn send_str_reply(&self, recipient_tag: AnonymousSenderTag, message: &str) { - self.send_reply(recipient_tag, message).await; - } - - /// Sends binary reply data to the supplied anonymous recipient. - /// - /// # Example - /// - /// ```no_run - /// use nym_sdk::mixnet; - /// - /// #[tokio::main] - /// async fn main() { - /// let mut client = mixnet::MixnetClient::connect_new().await.unwrap(); - /// // note: the tag is something you would have received from a remote client sending you surbs! - /// let tag = mixnet::AnonymousSenderTag::try_from_base58_string("foobar").unwrap(); - /// client.send_reply(tag, b"hi").await; - /// } - /// ``` - pub async fn send_reply>(&self, recipient_tag: AnonymousSenderTag, message: M) { - let lane = TransmissionLane::General; - let input_msg = InputMessage::new_reply( - recipient_tag, - message.as_ref().to_vec(), - lane, - self.packet_type, - ); - self.send(input_msg).await - } - - /// Sends a [`InputMessage`] to the mixnet. This is the most low-level sending function, for - /// full customization. - async fn send(&self, message: InputMessage) { - if self.client_input.send(message).await.is_err() { - log::error!("Failed to send message"); - } - } - - /// Sends a [`InputMessage`] to the mixnet. This is the most low-level sending function, for - /// full customization. - /// - /// Waits until the message is actually sent, or close to being sent, until returning. - /// - /// NOTE: this not yet implemented. - #[allow(unused)] - async fn send_wait(&self, _message: InputMessage) { - todo!(); - } - /// Wait for messages from the mixnet pub async fn wait_for_messages(&mut self) -> Option> { self.reconstructed_receiver.next().await @@ -265,14 +141,7 @@ impl MixnetClient { pub struct MixnetClientSender { client_input: ClientInput, -} - -impl MixnetClientSender { - pub async fn send_input_message(&mut self, message: InputMessage) { - if self.client_input.send(message).await.is_err() { - log::error!("Failed to send message"); - } - } + packet_type: Option, } impl Stream for MixnetClient { @@ -282,3 +151,31 @@ impl Stream for MixnetClient { Pin::new(&mut self.reconstructed_receiver).poll_next(cx) } } + +#[async_trait] +impl MixnetMessageSender for MixnetClient { + fn packet_type(&self) -> Option { + self.packet_type + } + + async fn send(&self, message: InputMessage) -> Result<()> { + self.client_input + .send(message) + .await + .map_err(|_| Error::MessageSendingFailure) + } +} + +#[async_trait] +impl MixnetMessageSender for MixnetClientSender { + fn packet_type(&self) -> Option { + self.packet_type + } + + async fn send(&self, message: InputMessage) -> Result<()> { + self.client_input + .send(message) + .await + .map_err(|_| Error::MessageSendingFailure) + } +} diff --git a/sdk/rust/nym-sdk/src/mixnet/traits.rs b/sdk/rust/nym-sdk/src/mixnet/traits.rs new file mode 100644 index 0000000000..8eb78109f7 --- /dev/null +++ b/sdk/rust/nym-sdk/src/mixnet/traits.rs @@ -0,0 +1,119 @@ +// Copyright 2023 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use crate::mixnet::{AnonymousSenderTag, IncludedSurbs, Recipient}; +use crate::Result; +use async_trait::async_trait; +use nym_client_core::client::inbound_messages::InputMessage; +use nym_sphinx::params::PacketType; +use nym_task::connections::TransmissionLane; + +// defined to guarantee common interface regardless of whether you're using the full client +// or just the sending handler +#[async_trait] +pub trait MixnetMessageSender { + fn packet_type(&self) -> Option { + None + } + + /// Sends a [`InputMessage`] to the mixnet. This is the most low-level sending function, for + /// full customization. + async fn send(&self, message: InputMessage) -> Result<()>; + + /// Sends data to the supplied Nym address with the default surb behaviour. + /// + /// # Example + /// + /// ```no_run + /// use nym_sdk::mixnet::{self, MixnetMessageSender}; + /// + /// #[tokio::main] + /// async fn main() { + /// let address = "foobar"; + /// let recipient = mixnet::Recipient::try_from_base58_string(address).unwrap(); + /// let mut client = mixnet::MixnetClient::connect_new().await.unwrap(); + /// client.send_plain_message(recipient, "hi").await.unwrap(); + /// } + /// ``` + async fn send_plain_message(&self, address: Recipient, message: M) -> Result<()> + where + M: AsRef<[u8]> + Send, + { + self.send_message(address, message, IncludedSurbs::default()) + .await + } + + /// Sends bytes to the supplied Nym address. There is the option to specify the number of + /// reply-SURBs to include. + /// + /// # Example + /// + /// ```no_run + /// use nym_sdk::mixnet::{self, MixnetMessageSender}; + /// + /// #[tokio::main] + /// async fn main() { + /// let address = "foobar"; + /// let recipient = mixnet::Recipient::try_from_base58_string(address).unwrap(); + /// let mut client = mixnet::MixnetClient::connect_new().await.unwrap(); + /// let surbs = mixnet::IncludedSurbs::default(); + /// client.send_message(recipient, "hi".to_owned().into_bytes(), surbs).await.unwrap(); + /// } + /// ``` + async fn send_message( + &self, + address: Recipient, + message: M, + surbs: IncludedSurbs, + ) -> Result<()> + where + M: AsRef<[u8]> + Send, + { + let lane = TransmissionLane::General; + let input_msg = match surbs { + IncludedSurbs::Amount(surbs) => InputMessage::new_anonymous( + address, + message.as_ref().to_vec(), + surbs, + lane, + self.packet_type(), + ), + IncludedSurbs::ExposeSelfAddress => InputMessage::new_regular( + address, + message.as_ref().to_vec(), + lane, + self.packet_type(), + ), + }; + self.send(input_msg).await + } + + /// Sends reply data to the supplied anonymous recipient. + /// + /// # Example + /// + /// ```no_run + /// use nym_sdk::mixnet::{self, MixnetMessageSender}; + /// + /// #[tokio::main] + /// async fn main() { + /// let mut client = mixnet::MixnetClient::connect_new().await.unwrap(); + /// // note: the tag is something you would have received from a remote client sending you surbs! + /// let tag = mixnet::AnonymousSenderTag::try_from_base58_string("foobar").unwrap(); + /// client.send_reply(tag, b"hi").await.unwrap(); + /// } + /// ``` + async fn send_reply(&self, recipient_tag: AnonymousSenderTag, message: M) -> Result<()> + where + M: AsRef<[u8]> + Send, + { + let lane = TransmissionLane::General; + let input_msg = InputMessage::new_reply( + recipient_tag, + message.as_ref().to_vec(), + lane, + self.packet_type(), + ); + self.send(input_msg).await + } +} diff --git a/service-providers/common/examples/control_requests.rs b/service-providers/common/examples/control_requests.rs index 17af10448c..14886390f8 100644 --- a/service-providers/common/examples/control_requests.rs +++ b/service-providers/common/examples/control_requests.rs @@ -3,7 +3,9 @@ // use nym_client::client::config::{BaseClientConfig, Config, GatewayEndpointConfig}; // use nym_client::client::{DirectClient, KeyManager, Recipient, ReconstructedMessage, SocketClient}; -use nym_sdk::mixnet::{IncludedSurbs, MixnetClient, Recipient, ReconstructedMessage}; +use nym_sdk::mixnet::{ + IncludedSurbs, MixnetClient, MixnetMessageSender, Recipient, ReconstructedMessage, +}; use nym_service_providers_common::interface::{ ControlRequest, ControlResponse, ProviderInterfaceVersion, Request, Response, ResponseContent, }; @@ -51,34 +53,34 @@ async fn main() -> anyhow::Result<()> { // TODO: currently we HAVE TO use surbs unfortunately println!("Sending 'Health' request..."); client - .send_bytes( + .send_message( provider, full_request_health.into_bytes(), IncludedSurbs::new(10), ) - .await; + .await?; let response = wait_for_control_response(&mut client).await; println!("response to 'Health' request: {response:#?}"); println!("Sending 'BinaryInfo' request..."); client - .send_bytes( + .send_message( provider, full_request_binary_info.into_bytes(), IncludedSurbs::none(), ) - .await; + .await?; let response = wait_for_control_response(&mut client).await; println!("response to 'BinaryInfo' request: {response:#?}"); println!("Sending 'SupportedRequestVersions' request..."); client - .send_bytes( + .send_message( provider, full_request_versions.into_bytes(), IncludedSurbs::none(), ) - .await; + .await?; let response = wait_for_control_response(&mut client).await; println!("response to 'SupportedRequestVersions' request: {response:#?}"); diff --git a/service-providers/network-requester/examples/query.rs b/service-providers/network-requester/examples/query.rs index 5b89e6924b..87f410ee75 100644 --- a/service-providers/network-requester/examples/query.rs +++ b/service-providers/network-requester/examples/query.rs @@ -1,7 +1,9 @@ // Copyright 2023 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use nym_sdk::mixnet::{IncludedSurbs, MixnetClient, Recipient, ReconstructedMessage}; +use nym_sdk::mixnet::{ + IncludedSurbs, MixnetClient, MixnetMessageSender, Recipient, ReconstructedMessage, +}; use nym_service_providers_common::interface::{ ProviderInterfaceVersion, Request, Response, ResponseContent, }; @@ -47,23 +49,23 @@ async fn main() -> anyhow::Result<()> { ); println!("Sending 'OpenProxy' query..."); client - .send_bytes( + .send_message( provider, open_proxy_request.into_bytes(), IncludedSurbs::new(10), ) - .await; + .await?; let response = wait_for_response(&mut client).await; println!("response to 'OpenProxy' query: {response:#?}"); println!("Sending 'Description' query..."); client - .send_bytes( + .send_message( provider, description_request.into_bytes(), IncludedSurbs::none(), ) - .await; + .await?; let response = wait_for_response(&mut client).await; println!("response to 'Description' query: {response:#?}"); diff --git a/service-providers/network-requester/src/core.rs b/service-providers/network-requester/src/core.rs index 92ab7af231..cd9b57f2dd 100644 --- a/service-providers/network-requester/src/core.rs +++ b/service-providers/network-requester/src/core.rs @@ -16,6 +16,7 @@ use log::warn; use nym_bin_common::bin_info_owned; use nym_client_core::config::disk_persistence::CommonClientPaths; use nym_network_defaults::NymNetworkDetails; +use nym_sdk::mixnet::MixnetMessageSender; use nym_service_providers_common::interface::{ BinaryInformation, ProviderInterfaceVersion, Request, RequestVersion, }; @@ -223,7 +224,7 @@ impl NRServiceProviderBuilder { }; let stats_collector_clone = stats_collector.clone(); - let mixnet_client_sender = mixnet_client.sender(); + let mixnet_client_sender = mixnet_client.split_sender(); let self_address = *mixnet_client.nym_address(); // start the listener for mix messages @@ -297,7 +298,7 @@ impl NRServiceProvider { /// Listens for any messages from `mix_reader` that should be written back to the mix network /// via the `websocket_writer`. async fn mixnet_response_listener( - mut mixnet_client_sender: nym_sdk::mixnet::MixnetClientSender, + mixnet_client_sender: nym_sdk::mixnet::MixnetClientSender, mut mix_input_reader: MixProxyReader, stats_collector: Option, ) { @@ -321,7 +322,7 @@ impl NRServiceProvider { } let response_message = msg.into_input_message(); - mixnet_client_sender.send_input_message(response_message).await; + mixnet_client_sender.send(response_message).await.unwrap(); } else { log::error!("Exiting: channel closed!"); break; diff --git a/tools/nym-nr-query/src/main.rs b/tools/nym-nr-query/src/main.rs index 97d376f6d5..2de1c08468 100644 --- a/tools/nym-nr-query/src/main.rs +++ b/tools/nym-nr-query/src/main.rs @@ -2,7 +2,7 @@ use std::fmt; use clap::{Parser, ValueEnum}; use nym_bin_common::output_format::OutputFormat; -use nym_sdk::mixnet::{self, IncludedSurbs}; +use nym_sdk::mixnet::{self, IncludedSurbs, MixnetMessageSender}; use nym_service_providers_common::interface::{ ControlRequest, ControlResponse, ProviderInterfaceVersion, Request, Response, ResponseContent, }; @@ -154,34 +154,37 @@ impl QueryClient { async fn query_bin_info(&mut self) -> ControlResponse { self.client - .send_bytes( + .send_message( self.provider, new_bin_info_request().into_bytes(), IncludedSurbs::new(10), ) - .await; + .await + .unwrap(); wait_for_control_response(&mut self.client).await } async fn query_supported_versions(&mut self) -> ControlResponse { self.client - .send_bytes( + .send_message( self.provider, new_supported_request_versions_request().into_bytes(), IncludedSurbs::new(10), ) - .await; + .await + .unwrap(); wait_for_control_response(&mut self.client).await } async fn query_open_proxy(&mut self) -> QueryResponse { self.client - .send_bytes( + .send_message( self.provider, new_open_proxy_request().into_bytes(), IncludedSurbs::new(10), ) - .await; + .await + .unwrap(); let response = wait_for_socks5_response(&mut self.client).await; response .content @@ -193,12 +196,13 @@ impl QueryClient { async fn ping(&mut self) -> PingResponse { let now = std::time::Instant::now(); self.client - .send_bytes( + .send_message( self.provider, new_ping_request().into_bytes(), IncludedSurbs::new(5), ) - .await; + .await + .unwrap(); let response = wait_for_control_response(&mut self.client).await; assert!(matches!(response, ControlResponse::Health)); let elapsed = now.elapsed(); From de6c1ba1f0791cb6a3aa2abcfb0f6f2b5cd16088 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C4=99drzej=20Stuczy=C5=84ski?= Date: Wed, 9 Aug 2023 14:39:57 +0100 Subject: [PATCH 117/361] made the Stream return a single message at each call --- .../parallel_sending_and_receiving.rs | 4 +- sdk/rust/nym-sdk/src/mixnet/client.rs | 8 +-- sdk/rust/nym-sdk/src/mixnet/native_client.rs | 53 +++++++++++++++++-- 3 files changed, 55 insertions(+), 10 deletions(-) diff --git a/sdk/rust/nym-sdk/examples/parallel_sending_and_receiving.rs b/sdk/rust/nym-sdk/examples/parallel_sending_and_receiving.rs index e3690ce9e0..4c048e27ae 100644 --- a/sdk/rust/nym-sdk/examples/parallel_sending_and_receiving.rs +++ b/sdk/rust/nym-sdk/examples/parallel_sending_and_receiving.rs @@ -28,9 +28,7 @@ async fn main() { // receiving task tokio::spawn(async move { if let Some(received) = client.next().await { - for r in received { - println!("Received: {}", String::from_utf8_lossy(&r.message)); - } + println!("Received: {}", String::from_utf8_lossy(&received.message)); } client.disconnect().await; diff --git a/sdk/rust/nym-sdk/src/mixnet/client.rs b/sdk/rust/nym-sdk/src/mixnet/client.rs index 33185ccd88..4340b79174 100644 --- a/sdk/rust/nym-sdk/src/mixnet/client.rs +++ b/sdk/rust/nym-sdk/src/mixnet/client.rs @@ -496,15 +496,15 @@ where let reconstructed_receiver = client_output.register_receiver()?; - Ok(MixnetClient { + Ok(MixnetClient::new( nym_address, client_input, client_output, client_state, reconstructed_receiver, - task_manager: started_client.task_manager, - packet_type: None, - }) + started_client.task_manager, + None, + )) } } diff --git a/sdk/rust/nym-sdk/src/mixnet/native_client.rs b/sdk/rust/nym-sdk/src/mixnet/native_client.rs index e03d585ebb..3266b37506 100644 --- a/sdk/rust/nym-sdk/src/mixnet/native_client.rs +++ b/sdk/rust/nym-sdk/src/mixnet/native_client.rs @@ -2,7 +2,8 @@ use crate::mixnet::client::MixnetClientBuilder; use crate::mixnet::traits::MixnetMessageSender; use crate::{Error, Result}; use async_trait::async_trait; -use futures::{Stream, StreamExt}; +use futures::{ready, Stream, StreamExt}; +use log::error; use nym_client_core::client::{ base_client::{ClientInput, ClientOutput, ClientState}, inbound_messages::InputMessage, @@ -42,9 +43,33 @@ pub struct MixnetClient { /// The task manager that controlls all the spawned tasks that the clients uses to do it's job. pub(crate) task_manager: TaskManager, pub(crate) packet_type: Option, + + // internal state used for the `Stream` implementation + _buffered: Vec, } impl MixnetClient { + pub(crate) fn new( + nym_address: Recipient, + client_input: ClientInput, + client_output: ClientOutput, + client_state: ClientState, + reconstructed_receiver: ReconstructedMessagesReceiver, + task_manager: TaskManager, + packet_type: Option, + ) -> Self { + Self { + nym_address, + client_input, + client_output, + client_state, + reconstructed_receiver, + task_manager, + packet_type, + _buffered: Vec::new(), + } + } + /// Create a new client and connect to the mixnet using ephemeral in-memory keys that are /// discarded at application close. /// @@ -145,10 +170,32 @@ pub struct MixnetClientSender { } impl Stream for MixnetClient { - type Item = Vec; + type Item = ReconstructedMessage; fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { - Pin::new(&mut self.reconstructed_receiver).poll_next(cx) + if let Some(next) = self._buffered.pop() { + cx.waker().wake_by_ref(); + return Poll::Ready(Some(next)); + } + match ready!(Pin::new(&mut self.reconstructed_receiver).poll_next(cx)) { + None => Poll::Ready(None), + Some(mut msgs) => { + // the vector itself should never be empty + if let Some(next) = msgs.pop() { + // there's more than a single message - buffer them and wake the waker + // to get polled again immediately + if !msgs.is_empty() { + self._buffered = msgs; + cx.waker().wake_by_ref(); + } + Poll::Ready(Some(next)) + } else { + error!("the reconstructed messages vector is empty - please let the developers know if you see this message"); + cx.waker().wake_by_ref(); + Poll::Pending + } + } + } } } From b63f5409db9f1c30c2d0f3227868b6d3f97a0b01 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C4=99drzej=20Stuczy=C5=84ski?= Date: Wed, 9 Aug 2023 14:43:43 +0100 Subject: [PATCH 118/361] cargo fmt --- sdk/rust/nym-sdk/src/error.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdk/rust/nym-sdk/src/error.rs b/sdk/rust/nym-sdk/src/error.rs index a56d94cd4e..6d4196e491 100644 --- a/sdk/rust/nym-sdk/src/error.rs +++ b/sdk/rust/nym-sdk/src/error.rs @@ -89,7 +89,7 @@ pub enum Error { #[error("loaded shared gateway key without providing information about what gateway it corresponds to")] GatewayWithUnknownEndpoint, - + #[error("failed to send the provided message")] MessageSendingFailure, } From 75bb9140b0f5fac678ea4a32c7c7eeedbfa0b8f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C4=99drzej=20Stuczy=C5=84ski?= Date: Wed, 9 Aug 2023 14:49:16 +0100 Subject: [PATCH 119/361] fixed doc tests --- sdk/rust/nym-sdk/src/bandwidth.rs | 4 ++-- sdk/rust/nym-sdk/src/mixnet.rs | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/sdk/rust/nym-sdk/src/bandwidth.rs b/sdk/rust/nym-sdk/src/bandwidth.rs index 5b95d5aae6..e9cfb0f8fb 100644 --- a/sdk/rust/nym-sdk/src/bandwidth.rs +++ b/sdk/rust/nym-sdk/src/bandwidth.rs @@ -6,7 +6,7 @@ //! # Basic example //! //! ```no_run -//! use nym_sdk::mixnet; +//! use nym_sdk::mixnet::{self, MixnetMessageSender}; //! //! #[tokio::main] //! async fn main() { @@ -27,7 +27,7 @@ //! let our_address = client.nym_address(); //! //! // Send a message throughout the mixnet to ourselves -//! client.send_str(*our_address, "hello there").await; +//! client.send_plain_message(*our_address, "hello there").await.unwrap(); //! //! println!("Waiting for message"); //! if let Some(received) = client.wait_for_messages().await { diff --git a/sdk/rust/nym-sdk/src/mixnet.rs b/sdk/rust/nym-sdk/src/mixnet.rs index d082756cd6..88a8604d6b 100644 --- a/sdk/rust/nym-sdk/src/mixnet.rs +++ b/sdk/rust/nym-sdk/src/mixnet.rs @@ -4,20 +4,20 @@ //! # Basic example //! //! ```no_run -//! use nym_sdk::mixnet; +//! use nym_sdk::mixnet::{self, MixnetMessageSender}; //! //! #[tokio::main] //! async fn main() { //! // Passing no config makes the client fire up an ephemeral session and figure stuff out on //! // its own -//! let mut client = mixnet::MixnetClient::connect_new().await.unwrap(); +//! let mut client = mixnet::MixnetClient::connect_new().await.unwrap(); //! //! // Be able to get our client address //! let our_address = client.nym_address(); //! println!("Our client nym address is: {our_address}"); //! //! // Send a message throught the mixnet to ourselves -//! client.send_str(*our_address, "hello there").await; +//! client.send_plain_message(*our_address, "hello there").await.unwrap(); //! //! println!("Waiting for message"); //! if let Some(received) = client.wait_for_messages().await { From 60ab58998d9aae026bb059fa59cd98b5feb14ae9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bogdan-=C8=98tefan=20Neac=C5=9Fu?= Date: Wed, 9 Aug 2023 18:12:59 +0300 Subject: [PATCH 120/361] Print warning when coconut verif endpoint doesn't work (#3755) --- .../websocket/connection_handler/coconut.rs | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/gateway/src/node/client_handling/websocket/connection_handler/coconut.rs b/gateway/src/node/client_handling/websocket/connection_handler/coconut.rs index cc8c552b07..e19f4a14fe 100644 --- a/gateway/src/node/client_handling/websocket/connection_handler/coconut.rs +++ b/gateway/src/node/client_handling/websocket/connection_handler/coconut.rs @@ -118,8 +118,15 @@ impl CoconutVerifier { revoke_fee.clone(), ) .await?; - if !ret?.verification_result { - debug!("Validator {} didn't accept the credential. It will probably vote No on the spending proposal", client.api_client.nym_api_client.current_url()); + match ret { + Ok(res) => { + if !res.verification_result { + debug!("Validator {} didn't accept the credential. It will probably vote No on the spending proposal", client.api_client.nym_api_client.current_url()); + } + } + Err(e) => { + warn!("Validator {} could not be reached. There might be a problem with the coconut endpoint - {:?}", client.api_client.nym_api_client.current_url(), e); + } } } From d49ba6e025f72958ad8c8f3e9aa3c3743348d02a Mon Sep 17 00:00:00 2001 From: Gala Date: Wed, 9 Aug 2023 17:56:36 +0200 Subject: [PATCH 121/361] theme change --- documentation/dev-portal/book.toml | 6 ++-- documentation/dev-portal/custom.css | 38 +++++++++++++++++++++++ documentation/dev-portal/last-changed.css | 6 ---- 3 files changed, 41 insertions(+), 9 deletions(-) create mode 100644 documentation/dev-portal/custom.css delete mode 100644 documentation/dev-portal/last-changed.css diff --git a/documentation/dev-portal/book.toml b/documentation/dev-portal/book.toml index 3b34e41d86..d9853f839c 100644 --- a/documentation/dev-portal/book.toml +++ b/documentation/dev-portal/book.toml @@ -17,7 +17,7 @@ src = "src" # - run `mdbook build`: this will rebuild the `./theme` directory # - change value of `turn-off` back to `true` [preprocessor.theme] -pagetoc = true +pagetoc = true # some variables related (defined in theme/css/variables.css) # `content-max-width` + `pagetoc-width` = 95% seems the best pagetoc-width = "13%" @@ -37,7 +37,7 @@ nav-chapters-min-width = "auto" chapter-line-height = "2em" section-line-height = "1.5em" -# # if true, never read and touch the files in theme dir +# if true, never read and touch the files in theme dir turn-off = false [preprocessor.admonish] @@ -77,7 +77,7 @@ curly-quotes = true # mathjax-support = false # useful if we want to pull equations in ? copy-fonts = true no-section-label = false -additional-css = ["theme/pagetoc.css", "././mdbook-admonish.css", "last-changed.css"] +additional-css = ["theme/pagetoc.css", "././mdbook-admonish.css", "custom.css"] additional-js = ["theme/pagetoc.js"] git-repository-url = "https://github.com/nymtech/nym" git-repository-icon = "fa-github" diff --git a/documentation/dev-portal/custom.css b/documentation/dev-portal/custom.css new file mode 100644 index 0000000000..0174c7796c --- /dev/null +++ b/documentation/dev-portal/custom.css @@ -0,0 +1,38 @@ +:root { + --mono-font: "Open Sans", Consolas, "Ubuntu Mono", Menlo, "DejaVu Sans Mono", + monospace, monospace; +} +.coal { + --bg: #121726; + --fg: #f2f2f2; + + --sidebar-bg: #121726; + --sidebar-fg: #f2f2f2; + --sidebar-active: #fb6e4e; + + --icons: #f2f2f2; + --icons-hover: #fb6e4e; + + --links: #fb6e4e; +} + +.light { + --bg: #f4f6f8; + --fg: #121726; + + --sidebar-bg: #f4f6f8; + --sidebar-fg: #121726; + --sidebar-active: #fb6e4e; + + --icons: #121726; + --icons-hover: #fb6e4e; + + --links: #fb6e4e; +} + +footer { + font-size: 0.8em; + text-align: center; + border-top: 1px solid black; + padding: 5px 0; +} \ No newline at end of file diff --git a/documentation/dev-portal/last-changed.css b/documentation/dev-portal/last-changed.css deleted file mode 100644 index 0c4228de32..0000000000 --- a/documentation/dev-portal/last-changed.css +++ /dev/null @@ -1,6 +0,0 @@ -footer { - font-size: 0.8em; - text-align: center; - border-top: 1px solid black; - padding: 5px 0; - } \ No newline at end of file From ae79dc4ec68d4bde9a6e14b38493d004f0b2e1c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C4=99drzej=20Stuczy=C5=84ski?= Date: Thu, 10 Aug 2023 14:40:15 +0100 Subject: [PATCH 122/361] using stricter version requirements for mdbook and mdbook-variables --- .github/workflows/ci-dev.yml | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci-dev.yml b/.github/workflows/ci-dev.yml index 82068fce76..16a4dda0b4 100644 --- a/.github/workflows/ci-dev.yml +++ b/.github/workflows/ci-dev.yml @@ -22,10 +22,13 @@ jobs: with: toolchain: stable - name: Install mdbook - run: (test -x $HOME/.cargo/bin/mdbook || cargo install --vers "^0.4" mdbook) + # to make sure we don't run into toml dependency issue, make sure to pull fixed mdbook that depends on old 0.5 + # and OLD mdbook-variables that uses the same dependency + # (mdbook-variables 0.2.3 updated to 0.7 which was used by mdbook incorrectly and extremely briefly) + run: (test -x $HOME/.cargo/bin/mdbook || cargo install --vers "^0.4.33" mdbook) - name: Install mdbook plugins run: | - cargo install --vers "^0.2.0" mdbook-variables && cargo install \ + cargo install --vers "=0.2.2" mdbook-variables && cargo install \ --vers "^1.8.0" mdbook-admonish && cargo install --vers \ "^0.1.2" mdbook-last-changed && cargo install --vers "^0.1.2" mdbook-theme \ && cargo install --vers "^0.7.7" mdbook-linkcheck From de2b106568ade99471f460a3e67bebbb1b084768 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=9A=A1=EF=B8=8F2FakTor=E2=9A=A1=EF=B8=8F?= Date: Tue, 8 Aug 2023 18:18:57 +0200 Subject: [PATCH 123/361] Update community-applications-and-guides.md --- .../community-resources/community-applications-and-guides.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/documentation/dev-portal/src/community-resources/community-applications-and-guides.md b/documentation/dev-portal/src/community-resources/community-applications-and-guides.md index 90e0801e80..cd89113896 100644 --- a/documentation/dev-portal/src/community-resources/community-applications-and-guides.md +++ b/documentation/dev-portal/src/community-resources/community-applications-and-guides.md @@ -85,9 +85,9 @@ We love seeing our developer community create applications using Nym. If you wou -## Minibolt +## MiniBolt -> Anonymize your p2p inventory messages and mempool for your Bitcoin & Lightning full nodes on consumer PCs! +> Proxies the clearnet connections of a Bitcoin node and its associated tools using the NYM mixnet! * [Github](https://github.com/minibolt-guide/minibolt) * [Documentation](https://v2.minibolt.info/bonus-guides/system/nym-mixnet#proxying-bitcoin-core) From 59e4567342e93b4a75da4a6ed8d3b63315508528 Mon Sep 17 00:00:00 2001 From: twofaktor Date: Tue, 8 Aug 2023 18:33:11 +0200 Subject: [PATCH 124/361] Update minibolt_pp.png --- .../images/profile_picture/minibolt_pp.png | Bin 13877 -> 23061 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/documentation/dev-portal/src/images/profile_picture/minibolt_pp.png b/documentation/dev-portal/src/images/profile_picture/minibolt_pp.png index ed059e6055840dcd148f5760efc06b2eb5e331d9..455243cc221e6350d5575ccc8cd8c9461cc672f4 100644 GIT binary patch literal 23061 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 literal 13877 zcmV-5HpL{T8P z03<-{jo#49+uys(IcNU3_jNx24UhyHASv+_y889&#(npm@A>VUu-5Wpv-tYm={k2si~io&o;&+W=pG>U;=z7_;<7e;eS}xT+bTk6Z_~d%f174CpUpGW>0TFGH=L z%7CrBMfHMu2K4X0S*ZHW0Ps%WH-KN|$7_8id#*PWX!!=S;zxm#KZf9vJ=4kT8LK~O z06&tQ?7zp4|N48`vp({Zz@iUi^64&qGS{I@&v^F7k%E5%IQSC`F4?mm%=Y5PhJt_6 zsrC(}=Ic$tpCa}8ybbQg)ckn1M{h}h9|eBzEo{@?h*in<=+U
  • {J-bRK*gzt^>% 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 DYWw7D From 062f4911e11ecb49b70c9c436ced614218cc6421 Mon Sep 17 00:00:00 2001 From: pierre Date: Wed, 9 Aug 2023 19:44:51 +0200 Subject: [PATCH 125/361] add user feedback page --- nym-connect/desktop/package.json | 4 +- .../{settings => }/PrivacyLevelSettings.tsx | 2 +- nym-connect/desktop/src/pages/menu/index.tsx | 2 +- .../ErrorReporting.tsx} | 24 ++-- .../src/pages/menu/reporting/UserFeedback.tsx | 104 ++++++++++++++++++ .../src/pages/menu/reporting/index.tsx | 26 +++++ nym-connect/desktop/src/routes/index.tsx | 12 +- yarn.lock | 17 ++- 8 files changed, 169 insertions(+), 22 deletions(-) rename nym-connect/desktop/src/pages/menu/{settings => }/PrivacyLevelSettings.tsx (96%) rename nym-connect/desktop/src/pages/menu/{settings/MonitoringSettings.tsx => reporting/ErrorReporting.tsx} (62%) create mode 100644 nym-connect/desktop/src/pages/menu/reporting/UserFeedback.tsx create mode 100644 nym-connect/desktop/src/pages/menu/reporting/index.tsx diff --git a/nym-connect/desktop/package.json b/nym-connect/desktop/package.json index 96d9e83438..2c374a6997 100644 --- a/nym-connect/desktop/package.json +++ b/nym-connect/desktop/package.json @@ -47,7 +47,7 @@ "react-markdown": "^8.0.4", "react-router-dom": "^6.7.0", "semver": "^6.3.0", - "yup": "^0.32.9" + "yup": "^1.2.0" }, "devDependencies": { "@babel/core": "^7.15.0", @@ -115,4 +115,4 @@ "webpack-merge": "^5.8.0", "yaml-loader": "^0.8.0" } -} \ No newline at end of file +} diff --git a/nym-connect/desktop/src/pages/menu/settings/PrivacyLevelSettings.tsx b/nym-connect/desktop/src/pages/menu/PrivacyLevelSettings.tsx similarity index 96% rename from nym-connect/desktop/src/pages/menu/settings/PrivacyLevelSettings.tsx rename to nym-connect/desktop/src/pages/menu/PrivacyLevelSettings.tsx index a97e7fdfdf..9935ef8f4c 100644 --- a/nym-connect/desktop/src/pages/menu/settings/PrivacyLevelSettings.tsx +++ b/nym-connect/desktop/src/pages/menu/PrivacyLevelSettings.tsx @@ -1,7 +1,7 @@ import React, { ChangeEvent, useState } from 'react'; import * as Sentry from '@sentry/react'; import { Box, FormControl, FormControlLabel, FormHelperText, Stack, Switch, Typography } from '@mui/material'; -import { useClientContext } from 'src/context/main'; +import { useClientContext } from '../../context/main'; export const PrivacyLevelSettings = () => { const { userData, setPrivacyLevel } = useClientContext(); diff --git a/nym-connect/desktop/src/pages/menu/index.tsx b/nym-connect/desktop/src/pages/menu/index.tsx index 67576dc88c..285d368061 100644 --- a/nym-connect/desktop/src/pages/menu/index.tsx +++ b/nym-connect/desktop/src/pages/menu/index.tsx @@ -8,8 +8,8 @@ const menuSchema = [ { title: 'Supported apps', icon: Apps, path: 'apps' }, { title: 'How to connect guide', icon: HelpOutline, path: 'guide' }, { title: 'Privacy level', icon: PrivacyTip, path: 'privacy-level' }, - { title: 'Please help us improve the app', icon: BugReport, path: 'monitoring' }, { title: 'Settings', icon: Settings, path: 'settings' }, + { title: 'Help improve the app', icon: BugReport, path: 'reporting' }, ]; export const Menu = () => ( diff --git a/nym-connect/desktop/src/pages/menu/settings/MonitoringSettings.tsx b/nym-connect/desktop/src/pages/menu/reporting/ErrorReporting.tsx similarity index 62% rename from nym-connect/desktop/src/pages/menu/settings/MonitoringSettings.tsx rename to nym-connect/desktop/src/pages/menu/reporting/ErrorReporting.tsx index 8b899cbe72..baf465944c 100644 --- a/nym-connect/desktop/src/pages/menu/settings/MonitoringSettings.tsx +++ b/nym-connect/desktop/src/pages/menu/reporting/ErrorReporting.tsx @@ -1,9 +1,8 @@ import React, { ChangeEvent, useState } from 'react'; -import { Warning as WarningIcon } from '@mui/icons-material'; import { Box, FormControl, FormControlLabel, FormHelperText, Stack, Switch, Typography } from '@mui/material'; -import { useClientContext } from 'src/context/main'; +import { useClientContext } from '../../../context/main'; -export const MonitoringSettings = () => { +export const ErrorReporting = () => { const { userData, setMonitoring } = useClientContext(); const [enabled, setEnabled] = useState(userData?.monitoring || false); const [loading, setLoading] = useState(false); @@ -20,7 +19,7 @@ export const MonitoringSettings = () => { - Error reporting and performance monitoring + Turn on error reporting and performance monitoring { sx={{ ml: 1, mr: 1 }} /> } - label="Enable" + label={enabled ? 'On' : 'Off'} /> - Help Nym developers to fix errors, crashes and improve the application by enabling this option. If errors - occur or if the app crashes, it will automatically send a report. Also it tracks various performance - metrics. We use sentry.io service to handle this. + Help Nym developers fix errors, crashes and improve the application by enabling this option. If errors + occur or if the app crashes, it will automatically send a report. It also tracks various performance + metrics. We use Sentry.io service to handle this. - - - - You must restart the application for the change to take effect. - - + + You must restart the application for the change to take effect. + diff --git a/nym-connect/desktop/src/pages/menu/reporting/UserFeedback.tsx b/nym-connect/desktop/src/pages/menu/reporting/UserFeedback.tsx new file mode 100644 index 0000000000..1295d2159b --- /dev/null +++ b/nym-connect/desktop/src/pages/menu/reporting/UserFeedback.tsx @@ -0,0 +1,104 @@ +import React, { useState } from 'react'; +import { Alert, Box, Button, FormControl, Snackbar, Stack, TextField, Typography } from '@mui/material'; +import * as Sentry from '@sentry/browser'; +import { Controller, SubmitHandler, useForm } from 'react-hook-form'; +import { object, string } from 'yup'; +import { yupResolver } from '@hookform/resolvers/yup'; + +type FormValues = { + email?: string; + feedback: string; +}; + +const schema = object({ + email: string().email(), + feedback: string().required().min(20), +}).required(); + +export const UserFeedback = () => { + const [isBusy, setIsBusy] = useState(false); + + const { + handleSubmit, + control, + formState: { errors }, + reset, + } = useForm({ + defaultValues: { + email: '', + feedback: '', + }, + // inferred type is fucked, so use `any` to make TS happy + resolver: yupResolver(schema as any), + }); + + const onSubmit: SubmitHandler = (data) => { + Sentry.captureUserFeedback({ + event_id: 'user_feedback', + name: 'nym', + email: data.email, + comments: data.feedback, + }); + setIsBusy(true); + reset(); + }; + + const handleClose = () => { + setIsBusy(false); + }; + + return ( + + + + Feedback sent successfuly + + + + + + Send us your feedback about the app + +
    + + ( + + )} + name="email" + control={control} + /> + ( + + )} + name="feedback" + control={control} + /> + + +
    +
    +
    +
    + ); +}; diff --git a/nym-connect/desktop/src/pages/menu/reporting/index.tsx b/nym-connect/desktop/src/pages/menu/reporting/index.tsx new file mode 100644 index 0000000000..ccc2c0bd1f --- /dev/null +++ b/nym-connect/desktop/src/pages/menu/reporting/index.tsx @@ -0,0 +1,26 @@ +import React from 'react'; +import { Link as RouterLink } from 'react-router-dom'; +import { Link, List, ListItem, ListItemButton, ListItemText, Stack } from '@mui/material'; +import { AppVersion } from 'src/components/AppVersion'; + +const menuSchema = [ + { title: 'Turn on error reporting', path: 'error-reporting' }, + { title: 'Send us your feedback', path: 'user-feedback' }, +]; + +export const ReportingMenu = () => ( + + + {menuSchema.map((item) => ( + + + + {item.title} + + + + ))} + + + +); diff --git a/nym-connect/desktop/src/routes/index.tsx b/nym-connect/desktop/src/routes/index.tsx index f1e4b1da67..67bd471460 100644 --- a/nym-connect/desktop/src/routes/index.tsx +++ b/nym-connect/desktop/src/routes/index.tsx @@ -8,9 +8,11 @@ import { HelpGuide } from 'src/pages/menu/Guide'; import { SettingsMenu } from 'src/pages/menu/settings'; import { GatewaySettings } from 'src/pages/menu/settings/GatewaySettings'; import { ServiceProviderSettings } from 'src/pages/menu/settings/ServiceProviderSettings'; -import { MonitoringSettings } from '../pages/menu/settings/MonitoringSettings'; -import { PrivacyLevelSettings } from '../pages/menu/settings/PrivacyLevelSettings'; +import { ErrorReporting } from '../pages/menu/reporting/ErrorReporting'; +import { PrivacyLevelSettings } from '../pages/menu/PrivacyLevelSettings'; import { useClientContext } from '../context/main'; +import { ReportingMenu } from '../pages/menu/reporting'; +import { UserFeedback } from '../pages/menu/reporting/UserFeedback'; const SentryRoutes = Sentry.withSentryReactRouterV6Routing(Routes); @@ -27,12 +29,16 @@ export const AppRoutes = () => { } /> } /> } /> - } /> } /> } /> } /> + + } /> + } /> + } /> + ); diff --git a/yarn.lock b/yarn.lock index 178152e2f1..10f93b7eaf 100644 --- a/yarn.lock +++ b/yarn.lock @@ -17413,7 +17413,7 @@ prop-types@^15.0.0, prop-types@^15.6.0, prop-types@^15.6.2, prop-types@^15.7.2, object-assign "^4.1.1" react-is "^16.13.1" -property-expr@^2.0.4: +property-expr@^2.0.4, property-expr@^2.0.5: version "2.0.5" resolved "https://registry.yarnpkg.com/property-expr/-/property-expr-2.0.5.tgz#278bdb15308ae16af3e3b9640024524f4dc02cb4" integrity sha512-IJUkICM5dP5znhCckHSv30Q4b5/JA5enCtkRHYaOVOAocnH/1BQEYTC5NMfT3AVl/iXKdr3aqQbQn9DxyWknwA== @@ -20018,6 +20018,11 @@ timers-ext@^0.1.7: es5-ext "~0.10.46" next-tick "1" +tiny-case@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/tiny-case/-/tiny-case-1.0.3.tgz#d980d66bc72b5d5a9ca86fb7c9ffdb9c898ddd03" + integrity sha512-Eet/eeMhkO6TX8mnUteS9zgPbUMQa4I6Kkp5ORiBD5476/m+PIRiumP5tmh5ioJpH7k51Kehawy2UDfsnxxY8Q== + tiny-warning@^1.0.2: version "1.0.3" resolved "https://registry.yarnpkg.com/tiny-warning/-/tiny-warning-1.0.3.tgz#94a30db453df4c643d0fd566060d60a875d84754" @@ -21786,6 +21791,16 @@ yup@^0.32.9: property-expr "^2.0.4" toposort "^2.0.2" +yup@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/yup/-/yup-1.2.0.tgz#9e51af0c63bdfc9be0fdc6c10aa0710899d8aff6" + integrity sha512-PPqYKSAXjpRCgLgLKVGPA33v5c/WgEx3wi6NFjIiegz90zSwyMpvTFp/uGcVnnbx6to28pgnzp/q8ih3QRjLMQ== + dependencies: + property-expr "^2.0.5" + tiny-case "^1.0.3" + toposort "^2.0.2" + type-fest "^2.19.0" + zod@^3.21.4: version "3.21.4" resolved "https://registry.yarnpkg.com/zod/-/zod-3.21.4.tgz#10882231d992519f0a10b5dd58a38c9dabbb64db" From aaef36a5d79a5e838fc67c1d48398df4b9d03722 Mon Sep 17 00:00:00 2001 From: pierre Date: Thu, 10 Aug 2023 10:48:00 +0200 Subject: [PATCH 126/361] display a notice to enable error reporting first --- .../src/pages/menu/reporting/UserFeedback.tsx | 26 ++++++++++++++++--- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/nym-connect/desktop/src/pages/menu/reporting/UserFeedback.tsx b/nym-connect/desktop/src/pages/menu/reporting/UserFeedback.tsx index 1295d2159b..d82ed27d82 100644 --- a/nym-connect/desktop/src/pages/menu/reporting/UserFeedback.tsx +++ b/nym-connect/desktop/src/pages/menu/reporting/UserFeedback.tsx @@ -1,9 +1,11 @@ import React, { useState } from 'react'; -import { Alert, Box, Button, FormControl, Snackbar, Stack, TextField, Typography } from '@mui/material'; -import * as Sentry from '@sentry/browser'; +import { Link as RouterLink } from 'react-router-dom'; +import { Alert, Box, Button, FormControl, Link, Snackbar, Stack, TextField, Typography } from '@mui/material'; +import * as Sentry from '@sentry/react'; import { Controller, SubmitHandler, useForm } from 'react-hook-form'; import { object, string } from 'yup'; import { yupResolver } from '@hookform/resolvers/yup'; +import { useClientContext } from '../../../context/main'; type FormValues = { email?: string; @@ -17,6 +19,7 @@ const schema = object({ export const UserFeedback = () => { const [isBusy, setIsBusy] = useState(false); + const { userData } = useClientContext(); const { handleSubmit, @@ -33,8 +36,9 @@ export const UserFeedback = () => { }); const onSubmit: SubmitHandler = (data) => { + const eventId = Sentry.captureMessage('user feedback'); Sentry.captureUserFeedback({ - event_id: 'user_feedback', + event_id: eventId, name: 'nym', email: data.email, comments: data.feedback, @@ -47,6 +51,20 @@ export const UserFeedback = () => { setIsBusy(false); }; + if (!userData?.monitoring) { + return ( + + + The error reporting option must be enabled in order to report feedback. Turn it on{' '} + + here + + . + + + ); + } + return ( @@ -92,7 +110,7 @@ export const UserFeedback = () => { name="feedback" control={control} /> - From c4c4355a5421646df619deed81b86b94627942eb Mon Sep 17 00:00:00 2001 From: pierre Date: Thu, 10 Aug 2023 11:22:49 +0200 Subject: [PATCH 127/361] add feedback note --- nym-connect/desktop/src/context/main.tsx | 6 ++ .../desktop/src/context/mocks/main.tsx | 2 + .../src/pages/connection/Disconnected.tsx | 76 +++++++++++-------- .../src/pages/menu/reporting/UserFeedback.tsx | 12 +-- 4 files changed, 61 insertions(+), 35 deletions(-) diff --git a/nym-connect/desktop/src/context/main.tsx b/nym-connect/desktop/src/context/main.tsx index 46d3312fac..dfa489d30c 100644 --- a/nym-connect/desktop/src/context/main.tsx +++ b/nym-connect/desktop/src/context/main.tsx @@ -33,6 +33,7 @@ export type TClientContext = { userDefinedSPAddress: UserDefinedSPAddress; serviceProviders?: ServiceProvider[]; gateways?: Gateway[]; + showFeedbackNote: boolean; setMode: (mode: ModeType) => void; clearError: () => void; setConnectionStatus: (connectionStatus: ConnectionStatusKind) => void; @@ -47,6 +48,7 @@ export type TClientContext = { setUserDefinedSPAddress: React.Dispatch>; setMonitoring: (value: boolean) => Promise; setPrivacyLevel: (value: PrivacyLevel) => Promise; + setShowFeedbackNote: (value: boolean) => void; }; function getRandomFromList(items: T[]): T { @@ -77,6 +79,7 @@ export const ClientContextProvider: FCWithChildren = ({ children }) => { address: undefined, }); const [userData, setUserData] = useState(); + const [showFeedbackNote, setShowFeedbackNote] = useState(true); const getAppVersion = async () => { const version = await getVersion(); @@ -240,6 +243,7 @@ export const ClientContextProvider: FCWithChildren = ({ children }) => { serviceProviders, connectedSince, userData, + showFeedbackNote, setConnectedSince, setServiceProvider, setGateway, @@ -253,6 +257,7 @@ export const ClientContextProvider: FCWithChildren = ({ children }) => { setUserDefinedSPAddress, setMonitoring, setPrivacyLevel, + setShowFeedbackNote, }), [ mode, @@ -270,6 +275,7 @@ export const ClientContextProvider: FCWithChildren = ({ children }) => { userDefinedGateway, userDefinedSPAddress, userData, + showFeedbackNote, ], ); diff --git a/nym-connect/desktop/src/context/mocks/main.tsx b/nym-connect/desktop/src/context/mocks/main.tsx index 46a5f50298..0b6db34aaf 100644 --- a/nym-connect/desktop/src/context/mocks/main.tsx +++ b/nym-connect/desktop/src/context/mocks/main.tsx @@ -12,6 +12,7 @@ const mockValues: TClientContext = { userDefinedGateway: { isActive: false, gateway: '' }, userDefinedSPAddress: { isActive: false, address: '' }, userData: { monitoring: false, privacy_level: 'High' }, + showFeedbackNote: false, setShowInfoModal: () => {}, setMode: () => {}, clearError: () => {}, @@ -26,6 +27,7 @@ const mockValues: TClientContext = { setUserDefinedSPAddress: () => {}, setMonitoring: async () => {}, setPrivacyLevel: async () => {}, + setShowFeedbackNote: () => {}, }; export const MockProvider: FCWithChildren<{ diff --git a/nym-connect/desktop/src/pages/connection/Disconnected.tsx b/nym-connect/desktop/src/pages/connection/Disconnected.tsx index 127a245d4e..2fe4db5c09 100644 --- a/nym-connect/desktop/src/pages/connection/Disconnected.tsx +++ b/nym-connect/desktop/src/pages/connection/Disconnected.tsx @@ -1,5 +1,6 @@ import React from 'react'; -import { Stack, Typography } from '@mui/material'; +import { Alert, Link, Stack, Typography } from '@mui/material'; +import { Link as RouterLink } from 'react-router-dom'; import { ConnectionStatus } from 'src/components/ConnectionStatus'; import { ConnectionTimer } from 'src/components/ConntectionTimer'; import { InfoModal } from 'src/components/InfoModal'; @@ -10,6 +11,7 @@ import { ConnectionStatusKind } from 'src/types'; import { PowerButton } from 'src/components/PowerButton/PowerButton'; import { Box } from '@mui/system'; import { ConnectionLayout } from 'src/layouts/ConnectionLayout'; +import { useClientContext } from '../../context/main'; export const Disconnected: FCWithChildren<{ error?: Error; @@ -20,32 +22,46 @@ export const Disconnected: FCWithChildren<{ serviceProvider?: ServiceProvider; clearError: () => void; onConnectClick: (status: ConnectionStatusKind) => void; -}> = ({ status, error, onConnectClick, clearError }) => ( - <> - {error && } - - - - - } - ConnectButton={} - BottomContent={ - - - You are not protected - - - - } - /> - -); +}> = ({ status, error, onConnectClick, clearError }) => { + const { showFeedbackNote, setShowFeedbackNote } = useClientContext(); + + return ( + <> + {error && } + + + + + } + ConnectButton={} + BottomContent={ + + + You are not protected + + + {showFeedbackNote ? ( + setShowFeedbackNote(false)}> + Help improve NymConnect{' '} + + Send feedback + + + ) : ( + + )} + + } + /> + + ); +}; diff --git a/nym-connect/desktop/src/pages/menu/reporting/UserFeedback.tsx b/nym-connect/desktop/src/pages/menu/reporting/UserFeedback.tsx index d82ed27d82..cec6596e96 100644 --- a/nym-connect/desktop/src/pages/menu/reporting/UserFeedback.tsx +++ b/nym-connect/desktop/src/pages/menu/reporting/UserFeedback.tsx @@ -74,17 +74,16 @@ export const UserFeedback = () => { - + Send us your feedback about the app
    - + ( { name="email" control={control} /> + + ( { name="feedback" control={control} /> + + - +
    From d57e8efff25f3d7feadea83219d3d63b32185f90 Mon Sep 17 00:00:00 2001 From: pierre Date: Thu, 10 Aug 2023 16:10:46 +0200 Subject: [PATCH 128/361] revert upstream changes --- .../community-applications-and-guides.md | 4 ++-- .../images/profile_picture/minibolt_pp.png | Bin 23061 -> 13877 bytes 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/documentation/dev-portal/src/community-resources/community-applications-and-guides.md b/documentation/dev-portal/src/community-resources/community-applications-and-guides.md index cd89113896..90e0801e80 100644 --- a/documentation/dev-portal/src/community-resources/community-applications-and-guides.md +++ b/documentation/dev-portal/src/community-resources/community-applications-and-guides.md @@ -85,9 +85,9 @@ We love seeing our developer community create applications using Nym. If you wou -## MiniBolt +## Minibolt -> Proxies the clearnet connections of a Bitcoin node and its associated tools using the NYM mixnet! +> Anonymize your p2p inventory messages and mempool for your Bitcoin & Lightning full nodes on consumer PCs! * [Github](https://github.com/minibolt-guide/minibolt) * [Documentation](https://v2.minibolt.info/bonus-guides/system/nym-mixnet#proxying-bitcoin-core) diff --git a/documentation/dev-portal/src/images/profile_picture/minibolt_pp.png b/documentation/dev-portal/src/images/profile_picture/minibolt_pp.png index 455243cc221e6350d5575ccc8cd8c9461cc672f4..ed059e6055840dcd148f5760efc06b2eb5e331d9 100644 GIT binary patch literal 13877 zcmV-5HpL{T8P z03<-{jo#49+uys(IcNU3_jNx24UhyHASv+_y889&#(npm@A>VUu-5Wpv-tYm={k2si~io&o;&+W=pG>U;=z7_;<7e;eS}xT+bTk6Z_~d%f174CpUpGW>0TFGH=L z%7CrBMfHMu2K4X0S*ZHW0Ps%WH-KN|$7_8id#*PWX!!=S;zxm#KZf9vJ=4kT8LK~O z06&tQ?7zp4|N48`vp({Zz@iUi^64&qGS{I@&v^F7k%E5%IQSC`F4?mm%=Y5PhJt_6 zsrC(}=Ic$tpCa}8ybbQg)ckn1M{h}h9|eBzEo{@?h*in<=+U
  • {J-bRK*gzt^>% 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 DYWw7D literal 23061 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 From 0f777204b89412d7280b2f0a5b884b7c28cd3d54 Mon Sep 17 00:00:00 2001 From: serinko <97586125+serinko@users.noreply.github.com> Date: Thu, 10 Aug 2023 18:31:56 +0200 Subject: [PATCH 129/361] picked theme changes:dev-portal -> operators; theme finished --- documentation/operators/book.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/documentation/operators/book.toml b/documentation/operators/book.toml index c39af18a65..52d1379fa8 100644 --- a/documentation/operators/book.toml +++ b/documentation/operators/book.toml @@ -18,7 +18,7 @@ src = "src" # - run `mdbook build`: this will rebuild the `./theme` directory # - change value of `turn-off` back to `true` [preprocessor.theme] -pagetoc = true +pagetoc = true # some variables related (defined in theme/css/variables.css) pagetoc-width = "13%" content-max-width = "70%" # 82 @@ -87,7 +87,7 @@ curly-quotes = true # mathjax-support = false # useful if we want to pull equations in copy-fonts = true no-section-label = false -additional-css = ["theme/pagetoc.css", "././mdbook-admonish.css","./last-changed.css"] +additional-css = ["theme/pagetoc.css", "././mdbook-admonish.css","./custom.css"] additional-js = ["theme/pagetoc.js"] git-repository-url = "https://github.com/nymtech/nym" git-repository-icon = "fa-github" From c5c6d3cc2784be46b651197786d9a42f2ff1f755 Mon Sep 17 00:00:00 2001 From: serinko <97586125+serinko@users.noreply.github.com> Date: Thu, 10 Aug 2023 18:32:13 +0200 Subject: [PATCH 130/361] picked theme changes:dev-portal -> operators; theme finished --- documentation/operators/custom.css | 38 ++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 documentation/operators/custom.css diff --git a/documentation/operators/custom.css b/documentation/operators/custom.css new file mode 100644 index 0000000000..170e91dd26 --- /dev/null +++ b/documentation/operators/custom.css @@ -0,0 +1,38 @@ +:root { + --mono-font: "Open Sans", Consolas, "Ubuntu Mono", Menlo, "DejaVu Sans Mono", + monospace, monospace; +} +.coal { + --bg: #121726; + --fg: #f2f2f2; + + --sidebar-bg: #121726; + --sidebar-fg: #f2f2f2; + --sidebar-active: #fb6e4e; + + --icons: #f2f2f2; + --icons-hover: #fb6e4e; + + --links: #fb6e4e; +} + +.light { + --bg: #f4f6f8; + --fg: #121726; + + --sidebar-bg: #f4f6f8; + --sidebar-fg: #121726; + --sidebar-active: #fb6e4e; + + --icons: #121726; + --icons-hover: #fb6e4e; + + --links: #fb6e4e; +} + +footer { + font-size: 0.8em; + text-align: center; + border-top: 1px solid black; + padding: 5px 0; +} From 9966d0fa5fc292903a5997e31764b05729c4fa96 Mon Sep 17 00:00:00 2001 From: serinko <97586125+serinko@users.noreply.github.com> Date: Thu, 10 Aug 2023 18:39:04 +0200 Subject: [PATCH 131/361] applied theme changes:dev-portal -> docs --- documentation/docs/book.toml | 2 +- documentation/docs/custom.css | 38 +++++++++++++++++++++++++++++++++++ 2 files changed, 39 insertions(+), 1 deletion(-) create mode 100644 documentation/docs/custom.css diff --git a/documentation/docs/book.toml b/documentation/docs/book.toml index cda7e3db07..ce666bddf2 100644 --- a/documentation/docs/book.toml +++ b/documentation/docs/book.toml @@ -85,7 +85,7 @@ curly-quotes = true # mathjax-support = false # useful if we want to pull equations in copy-fonts = true no-section-label = false -additional-css = ["theme/pagetoc.css", "././mdbook-admonish.css", "last-changed.css"] +additional-css = ["theme/pagetoc.css", "././mdbook-admonish.css", "./custom.css"] additional-js = ["theme/pagetoc.js"] git-repository-url = "https://github.com/nymtech/nym" git-repository-icon = "fa-github" diff --git a/documentation/docs/custom.css b/documentation/docs/custom.css new file mode 100644 index 0000000000..170e91dd26 --- /dev/null +++ b/documentation/docs/custom.css @@ -0,0 +1,38 @@ +:root { + --mono-font: "Open Sans", Consolas, "Ubuntu Mono", Menlo, "DejaVu Sans Mono", + monospace, monospace; +} +.coal { + --bg: #121726; + --fg: #f2f2f2; + + --sidebar-bg: #121726; + --sidebar-fg: #f2f2f2; + --sidebar-active: #fb6e4e; + + --icons: #f2f2f2; + --icons-hover: #fb6e4e; + + --links: #fb6e4e; +} + +.light { + --bg: #f4f6f8; + --fg: #121726; + + --sidebar-bg: #f4f6f8; + --sidebar-fg: #121726; + --sidebar-active: #fb6e4e; + + --icons: #121726; + --icons-hover: #fb6e4e; + + --links: #fb6e4e; +} + +footer { + font-size: 0.8em; + text-align: center; + border-top: 1px solid black; + padding: 5px 0; +} From b1b276e8ba3363f4448aa4b169c4b67a94461388 Mon Sep 17 00:00:00 2001 From: mfahampshire Date: Fri, 11 Aug 2023 10:16:37 +0200 Subject: [PATCH 132/361] pull fix from ci-dev.yml to ci-docs.yml --- .github/workflows/ci-docs.yml | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/.github/workflows/ci-docs.yml b/.github/workflows/ci-docs.yml index 2523013204..5d7874833b 100644 --- a/.github/workflows/ci-docs.yml +++ b/.github/workflows/ci-docs.yml @@ -27,14 +27,13 @@ jobs: command: build args: --workspace --release --all - name: Install mdbook - run: (test -x $HOME/.cargo/bin/mdbook || cargo install --vers "^0.4" mdbook) + run: (test -x $HOME/.cargo/bin/mdbook || cargo install --vers "^0.4.33" mdbook) - name: Install mdbook plugins run: | - cargo install --vers "^0.2.0" mdbook-variables && cargo install \ + cargo install --vers "=0.2.2" mdbook-variables && cargo install \ --vers "^1.8.0" mdbook-admonish && cargo install --vers \ - "^0.1.2" mdbook-last-changed && cargo install --vers "^0.1.2" \ - mdbook-theme && cargo install --vers "^0.7.7" mdbook-linkcheck && \ - cargo install --vers "^0.5.0" mdbook-cmdrun + "^0.1.2" mdbook-last-changed && cargo install --vers "^0.1.2" mdbook-theme \ + && cargo install --vers "^0.7.7" mdbook-linkcheck - name: Clean website run: cd documentation/docs && mdbook clean - name: Build website From 0e37c572f02aad3c81010ce3d67a26654ebb1115 Mon Sep 17 00:00:00 2001 From: mx <33262279+mfahampshire@users.noreply.github.com> Date: Fri, 11 Aug 2023 08:53:12 +0000 Subject: [PATCH 133/361] Update ci-dev.yml added workflow_dispatch --- .github/workflows/ci-dev.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/ci-dev.yml b/.github/workflows/ci-dev.yml index 82068fce76..80688c2ccc 100644 --- a/.github/workflows/ci-dev.yml +++ b/.github/workflows/ci-dev.yml @@ -1,6 +1,7 @@ name: CI dev-portal on: + workflow_dispatch: push: branches-ignore: master paths: From 3714ee76ff430d791f5a7e85636bd70af4adc8be Mon Sep 17 00:00:00 2001 From: mfahampshire Date: Fri, 11 Aug 2023 10:53:34 +0200 Subject: [PATCH 134/361] added workflow_dispatch to ci for dev-portal and docs --- .github/workflows/cd-dev.yml | 1 + .github/workflows/cd-docs.yml | 1 + 2 files changed, 2 insertions(+) diff --git a/.github/workflows/cd-dev.yml b/.github/workflows/cd-dev.yml index 0ca463345f..664f3cd79e 100644 --- a/.github/workflows/cd-dev.yml +++ b/.github/workflows/cd-dev.yml @@ -1,6 +1,7 @@ name: CD dev-portal on: + workflow_dispatch: push: branches: master paths: diff --git a/.github/workflows/cd-docs.yml b/.github/workflows/cd-docs.yml index 782a2c196d..b3f15ab265 100644 --- a/.github/workflows/cd-docs.yml +++ b/.github/workflows/cd-docs.yml @@ -1,6 +1,7 @@ name: CD docs on: + workflow_dispatch: push: branches: master paths: From af51ab3f71ffed544626df173d4b55bc8bbefa9d Mon Sep 17 00:00:00 2001 From: pierre Date: Fri, 11 Aug 2023 11:55:01 +0200 Subject: [PATCH 135/361] add max chars limit to feedback text --- nym-connect/desktop/src/pages/connection/Disconnected.tsx | 3 ++- nym-connect/desktop/src/pages/menu/reporting/UserFeedback.tsx | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/nym-connect/desktop/src/pages/connection/Disconnected.tsx b/nym-connect/desktop/src/pages/connection/Disconnected.tsx index 2fe4db5c09..33df98c90e 100644 --- a/nym-connect/desktop/src/pages/connection/Disconnected.tsx +++ b/nym-connect/desktop/src/pages/connection/Disconnected.tsx @@ -51,7 +51,8 @@ export const Disconnected: FCWithChildren<{ {showFeedbackNote ? ( setShowFeedbackNote(false)}> - Help improve NymConnect{' '} + Help improve NymConnect +
    Send feedback diff --git a/nym-connect/desktop/src/pages/menu/reporting/UserFeedback.tsx b/nym-connect/desktop/src/pages/menu/reporting/UserFeedback.tsx index cec6596e96..67b37fcc5f 100644 --- a/nym-connect/desktop/src/pages/menu/reporting/UserFeedback.tsx +++ b/nym-connect/desktop/src/pages/menu/reporting/UserFeedback.tsx @@ -14,7 +14,7 @@ type FormValues = { const schema = object({ email: string().email(), - feedback: string().required().min(20), + feedback: string().required().min(20).max(512), }).required(); export const UserFeedback = () => { From 9a5ce73dfd9f2407496c08dad7f73b499a3e4f71 Mon Sep 17 00:00:00 2001 From: pierre Date: Fri, 11 Aug 2023 12:23:44 +0200 Subject: [PATCH 136/361] close send feedback modal on click --- nym-connect/desktop/src/pages/connection/Disconnected.tsx | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/nym-connect/desktop/src/pages/connection/Disconnected.tsx b/nym-connect/desktop/src/pages/connection/Disconnected.tsx index 33df98c90e..00e51b9221 100644 --- a/nym-connect/desktop/src/pages/connection/Disconnected.tsx +++ b/nym-connect/desktop/src/pages/connection/Disconnected.tsx @@ -53,7 +53,13 @@ export const Disconnected: FCWithChildren<{ setShowFeedbackNote(false)}> Help improve NymConnect
    - + setShowFeedbackNote(false)} + component={RouterLink} + color="secondary" + underline="hover" + > Send feedback
    From f9108ea4009e9227935dc7780aa9d145e9801008 Mon Sep 17 00:00:00 2001 From: mfahampshire Date: Fri, 11 Aug 2023 12:29:20 +0200 Subject: [PATCH 137/361] reintroduced linkchecker + fixed broken links --- documentation/docs/book.toml | 4 ++-- documentation/docs/src/architecture/network-overview.md | 2 +- documentation/docs/src/binaries/building-nym.md | 2 +- documentation/docs/src/clients/socks5-client.md | 6 +++--- documentation/docs/src/nyx/ledger-live.md | 2 +- documentation/docs/src/nyx/rpc-node.md | 2 +- documentation/docs/src/wallet/cli-wallet.md | 4 ++-- documentation/operators/book.toml | 4 ++-- documentation/operators/src/nodes/maintenance.md | 2 +- 9 files changed, 14 insertions(+), 14 deletions(-) diff --git a/documentation/docs/book.toml b/documentation/docs/book.toml index ce666bddf2..6d22440ee3 100644 --- a/documentation/docs/book.toml +++ b/documentation/docs/book.toml @@ -119,5 +119,5 @@ expand = true # partial words will match longer terms heading-split-level = 3 # link results to heading levels copy-js = true # include Javascript code for search -# [output.linkcheck] -# warning-policy = "warn" +[output.linkcheck] +warning-policy = "warn" diff --git a/documentation/docs/src/architecture/network-overview.md b/documentation/docs/src/architecture/network-overview.md index fab4655e09..3a69f78be7 100644 --- a/documentation/docs/src/architecture/network-overview.md +++ b/documentation/docs/src/architecture/network-overview.md @@ -26,7 +26,7 @@ The mixnet - the different pieces of software that your traffic will pass throug * **Gateways** act as message storage for clients which may go offline and come back online again, and defend against denial of service attacks. The default gateway implementation included in the Nym platform code holds packets for later retrieval. For many applications (such as simple chat), this is usable out of the box, as it provides a place that potentially offline clients can retrieve packets from. The access token allows clients to pull messages from the gateway node. -* **Services** are applications that communicate with nym clients, listening and sending traffic to the mixnet. This is an umbrella term for a variety of different pieces of code, such as the [network requester](../nodes/network-requester-setup.md) binary. +* **Services** are applications that communicate with nym clients, listening and sending traffic to the mixnet. This is an umbrella term for a variety of different pieces of code, such as the [network requester](../nodes/network-requester.md) binary. * **Nyx Blockchain Validators** secure the network with proof-of-stake Sybil defenses, determine which nodes are included within the network, and work together to create Coconut threshold credentials which provide anonymous access to data and resources. They also produce blocks and secure the Nyx Blockchain. Initially, this chain was used only to house the CosmWasm smart contracts keeping track of Nym's network topology, token vesting contracts, and the `NYM` token itself. In recent months, we've decided to expand the role of Nyx and instead expand its role by making it an open smart contract platform for anyone to upload CosmWasm smart contracts to. Validators also provide privacy-enhanced credentials based on the testimony of a set of decentralized, blockchain-based issuing authorities. Nym validators use the [Coconut](https://arxiv.org/abs/1802.07344) [signature scheme](https://en.wikipedia.org/wiki/Digital_signature) to issue credentials. This allows privacy apps to generate anonymous resource claims through decentralised authorities, then use them with Service Providers. diff --git a/documentation/docs/src/binaries/building-nym.md b/documentation/docs/src/binaries/building-nym.md index a02e378ebb..a79af72c3d 100644 --- a/documentation/docs/src/binaries/building-nym.md +++ b/documentation/docs/src/binaries/building-nym.md @@ -8,7 +8,7 @@ Nym has two main codebases: - the [Nym platform](https://github.com/nymtech/nym), written in Rust. This contains all of our code _except_ for the validators. - the [Nym validators](https://github.com/nymtech/nyxd), written in Go. -> This page details how to build the main Nym platform code. **If you want to build and run a validator, [go here](../nodes/validator-setup.md) instead.** +> This page details how to build the main Nym platform code. **If you want to build and run a validator, [go here](../nodes/validator.md) instead.** ## Prerequisites - Debian/Ubuntu: `pkg-config`, `build-essential`, `libssl-dev`, `curl`, `jq`, `git` diff --git a/documentation/docs/src/clients/socks5-client.md b/documentation/docs/src/clients/socks5-client.md index e8bce0e948..e3a5759685 100644 --- a/documentation/docs/src/clients/socks5-client.md +++ b/documentation/docs/src/clients/socks5-client.md @@ -60,7 +60,7 @@ There are 2 pieces of software that work together to send SOCKS traffic through The `nym-socks5-client` allows you to do the following from your local machine: * Take a TCP data stream from a application that can send traffic via SOCKS5. * Chop up the TCP stream into multiple Sphinx packets, assigning sequence numbers to them, while leaving the TCP connection open for more data -* Send the Sphinx packets through the mixnet to a [network requester](../nodes/network-requester-setup.md). Packets are shuffled and mixed as they transit the mixnet. +* Send the Sphinx packets through the mixnet to a [network requester](../nodes/network-requester.md). Packets are shuffled and mixed as they transit the mixnet. The `nym-network-requester` then reassembles the original TCP stream using the packets' sequence numbers, and make the intended request. It will then chop up the response into Sphinx packets and send them back through the mixnet to your `nym-socks5-client`. The application will then receive its data, without even noticing that it wasn't talking to a "normal" SOCKS5 proxy! @@ -100,9 +100,9 @@ Before you can use the client, you need to initalise a new instance of it, which The `--id` in the example above is a local identifier so that you can name your clients and keep track of them on your local system; it is **never** transmitted over the network. -The `--provider` field needs to be filled with the Nym address of a Network Requester that can make network requests on your behalf. If you don't want to [run your own](../nodes/network-requester-setup.md) you can select one from the [mixnet explorer](https://explorer.nymtech.net/network-components/service-providers) by copying its `Client ID` and using this as the value of the `--provider` flag. Alternatively, you could use [this list](https://harbourmaster.nymtech.net/). +The `--provider` field needs to be filled with the Nym address of a Network Requester that can make network requests on your behalf. If you don't want to [run your own](../nodes/network-requester.md) you can select one from the [mixnet explorer](https://explorer.nymtech.net/network-components/service-providers) by copying its `Client ID` and using this as the value of the `--provider` flag. Alternatively, you could use [this list](https://harbourmaster.nymtech.net/). -Since the nodes on this list are the infrastructure for [Nymconnect](https://nymtech.net/developers/quickstart/nymconnect-gui.html) they will support all apps on the [default whitelist](../nodes/network-requester-setup.md#network-requester-whitelist): Keybase, Telegram, Electrum, Blockstream Green, and Helios. +Since the nodes on this list are the infrastructure for [Nymconnect](https://nymtech.net/developers/quickstart/nymconnect-gui.html) they will support all apps on the [default whitelist](../nodes/network-requester.md#network-requester-whitelist): Keybase, Telegram, Electrum, Blockstream Green, and Helios. #### Choosing a Gateway By default - as in the example above - your client will choose a random gateway to connect to. diff --git a/documentation/docs/src/nyx/ledger-live.md b/documentation/docs/src/nyx/ledger-live.md index f7559b6fd2..031834fa24 100644 --- a/documentation/docs/src/nyx/ledger-live.md +++ b/documentation/docs/src/nyx/ledger-live.md @@ -4,7 +4,7 @@ Use the following instructions to interact with the Nyx blockchain - either with ## Prerequisites * Download and install [Ledger Live](https://www.ledger.com/ledger-live). -* Compile the `nyxd` binary as per the instructions [here](../nodes/validator-setup.md#building-the-nym-validator). Stop after you can successfully run `nyxd` and get the helptext in your console output. +* Compile the `nyxd` binary as per the instructions [here](https://nymtech.net/operators/nodes/validator-setup.md#building-the-nym-validator). Stop after you can successfully run `nyxd` and get the helptext in your console output. ## Prepare your Ledger App * Plug in your Ledger device diff --git a/documentation/docs/src/nyx/rpc-node.md b/documentation/docs/src/nyx/rpc-node.md index af76f1f927..dc48d68409 100644 --- a/documentation/docs/src/nyx/rpc-node.md +++ b/documentation/docs/src/nyx/rpc-node.md @@ -4,6 +4,6 @@ RPC Nodes (which might otherwise be referred to as 'Lite Nodes' or just 'Full No You may want to set up an RPC Node for querying the blockchain, or in order to have an endpoint that your app can use to send transactions. -In order to set up an RPC Node, simply follow the instructions to set up a [Validator](../nodes/validator-setup.md), but **exclude the `nyxd tx staking create-validator` command**. +In order to set up an RPC Node, simply follow the instructions to set up a [Validator](https://nymtech.net/operators/nodes/validator-setup.md), but **exclude the `nyxd tx staking create-validator` command**. If you want to fast-sync your node, check out the Polkachu snapshot and their other [resources](https://polkachu.com/seeds/nym). \ No newline at end of file diff --git a/documentation/docs/src/wallet/cli-wallet.md b/documentation/docs/src/wallet/cli-wallet.md index 3f713b1937..36670190a3 100644 --- a/documentation/docs/src/wallet/cli-wallet.md +++ b/documentation/docs/src/wallet/cli-wallet.md @@ -1,8 +1,8 @@ # CLI Wallet -If you have already read our validator setup and maintenance [documentation](../nodes/validator-setup.md) you will have seen that we compile and use the `nyxd` binary primarily for our validators. This binary can however be used for many other tasks, such as creating and using keypairs for wallets, or automated setups that require the signing and broadcasting of transactions. +If you have already read our validator setup and maintenance [documentation](../nodes/validator.md) you will have seen that we compile and use the `nyxd` binary primarily for our validators. This binary can however be used for many other tasks, such as creating and using keypairs for wallets, or automated setups that require the signing and broadcasting of transactions. ### Using `nyxd` binary as a CLI wallet -You can use the `nyxd` as a minimal CLI wallet if you want to set up an account (or multiple accounts). Just compile the binary as per the documentation, **stopping after** the [building your validator](../nodes/validator-setup.md#building-your-validator) step is complete. You can then run `nyxd keys --help` to see how you can set up and store different keypairs with which to interact with the Nyx blockchain. +You can use the `nyxd` as a minimal CLI wallet if you want to set up an account (or multiple accounts). Just compile the binary as per the documentation, **stopping after** the [building your validator](../nodes/validator.md) step is complete. You can then run `nyxd keys --help` to see how you can set up and store different keypairs with which to interact with the Nyx blockchain. For more on interacting with the chain, see the [Interacting with Nyx Chain and Smart Contracts](../nyx/interacting-with-chain.md) page. diff --git a/documentation/operators/book.toml b/documentation/operators/book.toml index 52d1379fa8..be7233c069 100644 --- a/documentation/operators/book.toml +++ b/documentation/operators/book.toml @@ -121,5 +121,5 @@ expand = true # partial words will match longer terms heading-split-level = 3 # link results to heading levels copy-js = true # include Javascript code for search -# [output.linkcheck] -# warning-policy = "warn" +[output.linkcheck] +warning-policy = "warn" diff --git a/documentation/operators/src/nodes/maintenance.md b/documentation/operators/src/nodes/maintenance.md index bb7a0f0ea7..c955975118 100644 --- a/documentation/operators/src/nodes/maintenance.md +++ b/documentation/operators/src/nodes/maintenance.md @@ -46,7 +46,7 @@ You can either do this graphically via the Desktop Wallet, or the CLI. ![Node Settings Page](../images/wallet-screenshots/node_settings.png) ### Updating node information via the CLI -If you want to bond your `` via the CLI, then check out the [relevant section in the Nym CLI](../../documentation/docs/src/tools/nym-cli.md#upgrade-a-mix-node) docs. +If you want to bond your `` via the CLI, then check out the [relevant section in the Nym CLI](https://nymtech.net/docs/tools/nym-cli.md#upgrade-a-mix-node) docs. ### Upgrading Network Requester to >= v1.1.10 from Date: Fri, 11 Aug 2023 12:29:30 +0200 Subject: [PATCH 138/361] rough staging sh script --- documentation/staging.sh | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100755 documentation/staging.sh diff --git a/documentation/staging.sh b/documentation/staging.sh new file mode 100755 index 0000000000..07ca2de1d2 --- /dev/null +++ b/documentation/staging.sh @@ -0,0 +1,35 @@ +#!/bin/bash + +# commands assume you run script from `nym/documentation/` + +# array of project dirs +declare -a projects=("docs" "dev-portal" "operators") + +## now loop through the above array +for i in "${projects[@]}" +do + # cd to project dir + cd "./$i" && + # little sanity checks + echo $(pwd) && echo $(mdbook --version) && + # clean old book + rm -rf ./book/ + # build book + mdbook build + # check for destination, if ! then mkdir & check again else echo thumbs up + if [ ! -d ../../dist/docs/$i ]; then + echo "dest doesn't exist: creating dir" + mkdir -p ../../dist/docs/$i + fi + if [ -d ../../dist/docs/$i ]; then + echo "cp destination exists, all good" + fi + # clean old dist/$i + rm -rf ../../dist/docs/$i + # move newly rendered book/ to dist + cp -r ./book/ ../../dist/docs/$i + # sanity check + ls -a ../../dist/docs/$i/html + # cd back to ../documentation/ + cd ../ +done \ No newline at end of file From 6a54a6653c724f0507675077f5688b15f37b61ef Mon Sep 17 00:00:00 2001 From: mfahampshire Date: Fri, 11 Aug 2023 12:30:41 +0200 Subject: [PATCH 139/361] removed unnecessary linkcheck rendering output from dist/ --- documentation/staging.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/documentation/staging.sh b/documentation/staging.sh index 07ca2de1d2..64fec07e47 100755 --- a/documentation/staging.sh +++ b/documentation/staging.sh @@ -27,9 +27,9 @@ do # clean old dist/$i rm -rf ../../dist/docs/$i # move newly rendered book/ to dist - cp -r ./book/ ../../dist/docs/$i + cp -r ./book/html/ ../../dist/docs/$i # sanity check - ls -a ../../dist/docs/$i/html + ls -a ../../dist/docs/$i/ # cd back to ../documentation/ cd ../ done \ No newline at end of file From 6c83b8824722de18d368999048cffd2552efaae7 Mon Sep 17 00:00:00 2001 From: mfahampshire Date: Fri, 11 Aug 2023 13:08:19 +0200 Subject: [PATCH 140/361] updated readme with soon to be operator doc URL --- documentation/README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/documentation/README.md b/documentation/README.md index 3330b3e969..e074f7cdfe 100644 --- a/documentation/README.md +++ b/documentation/README.md @@ -2,5 +2,6 @@ * `docs` contains technical documentation hosted at [https://nymtech.net/docs](https://nymtech.net/docs) * `dev-portal` contains developer documentation hosted at [https://nymtech.net/developers](https://nymtech.net/developers) +* `operators` contains node setup and maintenance guides hosted at [https://nymtech.net/operators](https://nymtech.net/operators) Each directory contains a readme with more information about running and contributing to the projects. Each is built with [`mdbook`](https://rust-lang.github.io/mdBook/index.html) - use `mdbook serve` to build and serve them (defaults to `localhost:3000`). From c81a5ac0028514004b82db73ab6f01acbcfe48ba Mon Sep 17 00:00:00 2001 From: Fouad Date: Mon, 14 Aug 2023 09:42:27 +0100 Subject: [PATCH 141/361] Reset gateway performance on disconnect (#3620) * rest gateway performance on disconnect * update mobile context --- nym-connect/desktop/src/context/main.tsx | 6 ++++++ nym-connect/mobile/src/context/main.tsx | 6 ++++++ 2 files changed, 12 insertions(+) diff --git a/nym-connect/desktop/src/context/main.tsx b/nym-connect/desktop/src/context/main.tsx index dfa489d30c..ba6377e90b 100644 --- a/nym-connect/desktop/src/context/main.tsx +++ b/nym-connect/desktop/src/context/main.tsx @@ -164,9 +164,15 @@ export const ClientContextProvider: FCWithChildren = ({ children }) => { } }, []); + const afterDisconnection = useCallback(async () => { + setGatewayPerformance('Good'); + setConnectedSince(undefined); + }, []); + const startDisconnecting = useCallback(async () => { try { await invoke('start_disconnecting'); + afterDisconnection(); } catch (e) { console.log(e); Sentry.captureException(e); diff --git a/nym-connect/mobile/src/context/main.tsx b/nym-connect/mobile/src/context/main.tsx index b999b957a2..10f6525685 100644 --- a/nym-connect/mobile/src/context/main.tsx +++ b/nym-connect/mobile/src/context/main.tsx @@ -116,9 +116,15 @@ export const ClientContextProvider: FCWithChildren = ({ children }) => { } }, []); + const afterDisconnection = useCallback(async () => { + setGatewayPerformance('Good'); + setConnectedSince(undefined); + }, []); + const startDisconnecting = useCallback(async () => { try { await invoke('start_disconnecting'); + afterDisconnection(); } catch (e) { console.log(e); } From d0ba4056d682881fcbfaf9b342136372544a5c35 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C4=99drzej=20Stuczy=C5=84ski?= Date: Fri, 11 Aug 2023 10:15:55 +0100 Subject: [PATCH 142/361] Bugfix/contract types serde (#3753) * additional logs in abci queries * added serde aliases for deserialization of contract types using their old format * installing rust toolchain during the schema CI * making the workflow be executed on our custom runner * checking for diff only in the schema directory --- .github/workflows/check-contract-schema.yml | 12 +++++++++--- .../src/nyxd/cosmwasm_client/client.rs | 5 +++++ .../src/spend_credential.rs | 2 ++ .../mixnet-contract/src/interval.rs | 4 ++++ .../mixnet-contract/src/mixnode.rs | 2 ++ .../mixnet-contract/src/pending_events.rs | 9 +++++++++ .../vesting-contract/src/types.rs | 5 +++++ 7 files changed, 36 insertions(+), 3 deletions(-) diff --git a/.github/workflows/check-contract-schema.yml b/.github/workflows/check-contract-schema.yml index 700440bdd5..6de645dfa5 100644 --- a/.github/workflows/check-contract-schema.yml +++ b/.github/workflows/check-contract-schema.yml @@ -13,13 +13,19 @@ on: jobs: check-schema: name: Generate and check schema - runs-on: ubuntu-latest + runs-on: custom-runner-linux steps: - name: Check out repository code uses: actions/checkout@v2 - + + - name: Install Rust toolchain + uses: actions-rs/toolchain@v1 + with: + toolchain: stable + + - name: Generate the schema run: make contract-schema - name: Check for diff - run: git diff --exit-code + run: git diff --exit-code -- contracts/*/schema diff --git a/common/client-libs/validator-client/src/nyxd/cosmwasm_client/client.rs b/common/client-libs/validator-client/src/nyxd/cosmwasm_client/client.rs index 4a512a02db..9ca497e1c8 100644 --- a/common/client-libs/validator-client/src/nyxd/cosmwasm_client/client.rs +++ b/common/client-libs/validator-client/src/nyxd/cosmwasm_client/client.rs @@ -28,6 +28,7 @@ use cosmrs::proto::cosmwasm::wasm::v1::{ }; use cosmrs::tendermint::{block, chain, Hash}; use cosmrs::{AccountId, Coin as CosmosCoin, Tx}; +use log::trace; use prost::Message; use serde::{Deserialize, Serialize}; use std::convert::TryFrom; @@ -69,6 +70,9 @@ pub trait CosmWasmClient: TendermintClient { Req: Message, Res: Message + Default, { + if let Some(ref abci_path) = path { + trace!("performing query on abci path {abci_path}") + } let mut buf = Vec::with_capacity(req.encoded_len()); req.encode(&mut buf)?; @@ -477,6 +481,7 @@ pub trait CosmWasmClient: TendermintClient { .make_abci_query::<_, QuerySmartContractStateResponse>(path, req) .await?; + trace!("raw query response: {}", String::from_utf8_lossy(&res.data)); Ok(serde_json::from_slice(&res.data)?) } diff --git a/common/cosmwasm-smart-contracts/coconut-bandwidth-contract/src/spend_credential.rs b/common/cosmwasm-smart-contracts/coconut-bandwidth-contract/src/spend_credential.rs index d8fc108d14..835d6175de 100644 --- a/common/cosmwasm-smart-contracts/coconut-bandwidth-contract/src/spend_credential.rs +++ b/common/cosmwasm-smart-contracts/coconut-bandwidth-contract/src/spend_credential.rs @@ -39,7 +39,9 @@ impl SpendCredentialData { #[cw_serde] #[derive(Copy)] pub enum SpendCredentialStatus { + #[serde(alias = "InProgress")] InProgress, + #[serde(alias = "Spent")] Spent, } diff --git a/common/cosmwasm-smart-contracts/mixnet-contract/src/interval.rs b/common/cosmwasm-smart-contracts/mixnet-contract/src/interval.rs index cee4cf02be..6352c138c6 100644 --- a/common/cosmwasm-smart-contracts/mixnet-contract/src/interval.rs +++ b/common/cosmwasm-smart-contracts/mixnet-contract/src/interval.rs @@ -159,10 +159,12 @@ impl EpochStatus { pub enum EpochState { /// Represents the state of an epoch that's in progress (well, duh.) /// All actions are allowed to be issued. + #[serde(alias = "InProgress")] InProgress, /// Represents the state of an epoch when the rewarding entity has been decided on, /// and the mixnodes are in the process of being rewarded for their work in this epoch. + #[serde(alias = "Rewarding")] Rewarding { /// The id of the last node that has already received its rewards. last_rewarded: MixId, @@ -174,10 +176,12 @@ pub enum EpochState { /// Represents the state of an epoch when all mixnodes have already been rewarded for their work in this epoch /// and all issued actions should now get resolved before being allowed to advance into the next epoch. + #[serde(alias = "ReconcilingEvents")] ReconcilingEvents, /// Represents the state of an epoch when all mixnodes have already been rewarded for their work in this epoch, /// all issued actions got resolved and the epoch should now be advanced whilst assigning new rewarded set. + #[serde(alias = "AdvancingEpoch")] AdvancingEpoch, } diff --git a/common/cosmwasm-smart-contracts/mixnet-contract/src/mixnode.rs b/common/cosmwasm-smart-contracts/mixnet-contract/src/mixnode.rs index 676098fce6..a09d21dd2d 100644 --- a/common/cosmwasm-smart-contracts/mixnet-contract/src/mixnode.rs +++ b/common/cosmwasm-smart-contracts/mixnet-contract/src/mixnode.rs @@ -26,9 +26,11 @@ use serde_repr::{Deserialize_repr, Serialize_repr}; #[derive(Copy)] pub enum RewardedSetNodeStatus { /// Node that is currently active, i.e. is expected to be used by clients for mixing packets. + #[serde(alias = "Active")] Active, /// Node that is currently in standby, i.e. it's present in the rewarded set but is not active. + #[serde(alias = "Standby")] Standby, } diff --git a/common/cosmwasm-smart-contracts/mixnet-contract/src/pending_events.rs b/common/cosmwasm-smart-contracts/mixnet-contract/src/pending_events.rs index 595839c99f..fd681cbed4 100644 --- a/common/cosmwasm-smart-contracts/mixnet-contract/src/pending_events.rs +++ b/common/cosmwasm-smart-contracts/mixnet-contract/src/pending_events.rs @@ -37,6 +37,7 @@ pub enum PendingEpochEventKind { // `cumulative_reward_ratio` ahead of time /// Request to create a delegation towards particular mixnode. /// Note that if such delegation already exists, it will get updated with the provided token amount. + #[serde(alias = "Delegate")] Delegate { /// The address of the owner of the delegation. owner: Addr, @@ -53,6 +54,7 @@ pub enum PendingEpochEventKind { }, /// Request to remove delegation from particular mixnode. + #[serde(alias = "Undelegate")] Undelegate { /// The address of the owner of the delegation. owner: Addr, @@ -66,6 +68,7 @@ pub enum PendingEpochEventKind { }, /// Request to pledge more tokens (by the node operator) towards its node. + #[serde(alias = "PledgeMore")] PledgeMore { /// The id of the mixnode that will have its pledge updated. mix_id: MixId, @@ -75,6 +78,7 @@ pub enum PendingEpochEventKind { }, /// Request to decrease amount of pledged tokens (by the node operator) from its node. + #[serde(alias = "DecreasePledge")] DecreasePledge { /// The id of the mixnode that will have its pledge updated. mix_id: MixId, @@ -84,12 +88,14 @@ pub enum PendingEpochEventKind { }, /// Request to unbond a mixnode and completely remove it from the network. + #[serde(alias = "UnbondMixnode")] UnbondMixnode { /// The id of the mixnode that will get unbonded. mix_id: MixId, }, /// Request to update the current size of the active set. + #[serde(alias = "UpdateActiveSetSize")] UpdateActiveSetSize { /// The new desired size of the active set. new_size: u32, @@ -138,6 +144,7 @@ pub struct PendingIntervalEventData { #[cw_serde] pub enum PendingIntervalEventKind { /// Request to update cost parameters of given mixnode. + #[serde(alias = "PendingIntervalEventKind")] ChangeMixCostParams { /// The id of the mixnode that will have its cost parameters updated. mix_id: MixId, @@ -147,12 +154,14 @@ pub enum PendingIntervalEventKind { }, /// Request to update the underlying rewarding parameters used by the system + #[serde(alias = "UpdateRewardingParams")] UpdateRewardingParams { /// The detailed specification of the update. update: IntervalRewardingParamsUpdate, }, /// Request to change the next interval configuration. + #[serde(alias = "UpdateIntervalConfig")] UpdateIntervalConfig { /// The new number of epochs in intervals. epochs_in_interval: u32, diff --git a/common/cosmwasm-smart-contracts/vesting-contract/src/types.rs b/common/cosmwasm-smart-contracts/vesting-contract/src/types.rs index 60fd3c0aea..e4c1644a94 100644 --- a/common/cosmwasm-smart-contracts/vesting-contract/src/types.rs +++ b/common/cosmwasm-smart-contracts/vesting-contract/src/types.rs @@ -16,12 +16,15 @@ use std::str::FromStr; /// The vesting period. pub enum Period { /// Defines a pre-vesting period. + #[serde(alias = "Before")] Before, /// Defines currently active vesting period. + #[serde(alias = "In")] In(usize), /// Defines a post-vesting period. + #[serde(alias = "After")] After, } @@ -53,9 +56,11 @@ impl PledgeData { #[cw_serde] pub enum PledgeCap { /// Specifies a percent-based pledge cap, i.e. only given % of tokens could be pledged/staked. + #[serde(alias = "Percent")] Percent(Percent), /// Specifies an absolute pledge cap, i.e. an explicit value that could be pledged/staked. + #[serde(alias = "Absolute")] Absolute(Uint128), // This has to be in unym } From 69e4b1ae1c1fab073c5c86f0d0bc1273f55bc82f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jon=20H=C3=A4ggblad?= Date: Mon, 14 Aug 2023 12:18:53 +0200 Subject: [PATCH 143/361] Update Cargo.lock files --- contracts/Cargo.lock | 6 - nym-connect/desktop/Cargo.lock | 2 +- nym-wallet/Cargo.lock | 274 +++++++++++++++++++++++++++------ 3 files changed, 227 insertions(+), 55 deletions(-) diff --git a/contracts/Cargo.lock b/contracts/Cargo.lock index 0f56b6f7a9..c87480ad1b 100644 --- a/contracts/Cargo.lock +++ b/contracts/Cargo.lock @@ -290,12 +290,6 @@ dependencies = [ "libc", ] -[[package]] -name = "crunchy" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" - [[package]] name = "crypto-bigint" version = "0.4.9" diff --git a/nym-connect/desktop/Cargo.lock b/nym-connect/desktop/Cargo.lock index c522d297d1..6ef76b497a 100644 --- a/nym-connect/desktop/Cargo.lock +++ b/nym-connect/desktop/Cargo.lock @@ -3656,7 +3656,7 @@ dependencies = [ [[package]] name = "nym-connect" -version = "1.1.15" +version = "1.1.16" dependencies = [ "anyhow", "bip39", diff --git a/nym-wallet/Cargo.lock b/nym-wallet/Cargo.lock index b9d551fd84..da53aee71f 100644 --- a/nym-wallet/Cargo.lock +++ b/nym-wallet/Cargo.lock @@ -21,7 +21,20 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0" dependencies = [ "crypto-common", - "generic-array", + "generic-array 0.14.7", +] + +[[package]] +name = "aes" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e8b47f52ea9bae42228d07ec09eb676433d7c4ed1ebdf0f1d1c29ed446f1ab8" +dependencies = [ + "cfg-if", + "cipher 0.3.0", + "cpufeatures", + "ctr 0.8.0", + "opaque-debug 0.3.0", ] [[package]] @@ -31,7 +44,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "433cfd6710c9986c576a25ca913c39d66a6474107b406f34f91d4a8923395241" dependencies = [ "cfg-if", - "cipher", + "cipher 0.4.4", "cpufeatures", ] @@ -42,11 +55,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "82e1366e0c69c9f927b1fa5ce2c7bf9eafc8f9268c0b9800729e8b267612447c" dependencies = [ "aead", - "aes", - "cipher", - "ctr", + "aes 0.8.2", + "cipher 0.4.4", + "ctr 0.9.2", "ghash", - "subtle", + "subtle 2.4.1", ] [[package]] @@ -97,7 +110,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "95c2fcf79ad1932ac6269a738109997a83c227c09b75842ae564dc8ede6a861c" dependencies = [ "base64ct", - "blake2", + "blake2 0.10.6", "password-hash", ] @@ -212,14 +225,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7e141fb0f8be1c7b45887af94c88b182472b57c96b56773250ae00cd6a14a164" dependencies = [ "bs58 0.5.0", - "hmac", + "hmac 0.12.1", "k256 0.13.1", "once_cell", "pbkdf2", "rand_core 0.6.4", "ripemd", "sha2 0.10.6", - "subtle", + "subtle 2.4.1", "zeroize", ] @@ -261,6 +274,18 @@ dependencies = [ "wyz", ] +[[package]] +name = "blake2" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94cb07b0da6a73955f8fb85d24c466778e70cda767a568229b104f0264089330" +dependencies = [ + "byte-tools", + "crypto-mac 0.7.0", + "digest 0.8.1", + "opaque-debug 0.2.3", +] + [[package]] name = "blake2" version = "0.10.6" @@ -282,7 +307,7 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" dependencies = [ - "generic-array", + "generic-array 0.14.7", ] [[package]] @@ -291,7 +316,7 @@ version = "0.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69cce20737498f97b993470a6e536b8523f0af7892a4f928cceb1ac5e52ebe7e" dependencies = [ - "generic-array", + "generic-array 0.14.7", ] [[package]] @@ -304,7 +329,7 @@ dependencies = [ "group 0.11.0", "pairing", "rand_core 0.6.4", - "subtle", + "subtle 2.4.1", "zeroize", ] @@ -366,6 +391,12 @@ version = "3.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0d261e256854913907f67ed06efbc3338dfe6179796deefc1ff763fc1aee5535" +[[package]] +name = "byte-tools" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3b5ca7a04898ad4bcd41c90c5285445ff5b791899bb1b0abdd2a2aa791211d7" + [[package]] name = "bytemuck" version = "1.13.0" @@ -480,6 +511,15 @@ dependencies = [ "keystream", ] +[[package]] +name = "cipher" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ee52072ec15386f770805afd189a01c8841be8696bed250fa2f13c4c0d6dfb7" +dependencies = [ + "generic-array 0.14.7", +] + [[package]] name = "cipher" version = "0.4.4" @@ -809,9 +849,9 @@ version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ef2b4b23cddf68b89b8f8069890e8c270d54e2d5fe1b143820234805e4cb17ef" dependencies = [ - "generic-array", + "generic-array 0.14.7", "rand_core 0.6.4", - "subtle", + "subtle 2.4.1", "zeroize", ] @@ -821,9 +861,9 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf4c2f4e1afd912bc40bfd6fed5d9dc1f288e0ba01bfcc835cc5bc3eb13efe15" dependencies = [ - "generic-array", + "generic-array 0.14.7", "rand_core 0.6.4", - "subtle", + "subtle 2.4.1", "zeroize", ] @@ -833,11 +873,31 @@ version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" dependencies = [ - "generic-array", + "generic-array 0.14.7", "rand_core 0.6.4", "typenum", ] +[[package]] +name = "crypto-mac" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4434400df11d95d556bac068ddfedd482915eb18fe8bea89bc80b6e4b1c179e5" +dependencies = [ + "generic-array 0.12.4", + "subtle 1.0.0", +] + +[[package]] +name = "crypto-mac" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1d1a86f49236c215f271d40892d5fc950490551400b02ef360692c29815c714" +dependencies = [ + "generic-array 0.14.7", + "subtle 2.4.1", +] + [[package]] name = "cssparser" version = "0.27.2" @@ -884,13 +944,22 @@ dependencies = [ "syn 1.0.107", ] +[[package]] +name = "ctr" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "049bb91fb4aaf0e3c7efa6cd5ef877dbbbd15b39dad06d9948de4ec8a75761ea" +dependencies = [ + "cipher 0.3.0", +] + [[package]] name = "ctr" version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0369ee1ad671834580515889b80f2ea915f23b8be8d0daa4bbaf2ac5c7590835" dependencies = [ - "cipher", + "cipher 0.4.4", ] [[package]] @@ -908,7 +977,7 @@ dependencies = [ "byteorder", "digest 0.9.0", "rand_core 0.5.1", - "subtle", + "subtle 2.4.1", "zeroize", ] @@ -1100,13 +1169,22 @@ dependencies = [ "syn 1.0.107", ] +[[package]] +name = "digest" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3d0c8c8752312f9713efd397ff63acb9f85585afbf179282e720e7704954dd5" +dependencies = [ + "generic-array 0.12.4", +] + [[package]] name = "digest" version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" dependencies = [ - "generic-array", + "generic-array 0.14.7", ] [[package]] @@ -1118,7 +1196,7 @@ dependencies = [ "block-buffer 0.10.3", "const-oid", "crypto-common", - "subtle", + "subtle 2.4.1", ] [[package]] @@ -1326,12 +1404,12 @@ dependencies = [ "der 0.6.1", "digest 0.10.7", "ff 0.12.1", - "generic-array", + "generic-array 0.14.7", "group 0.12.1", "pkcs8 0.9.0", "rand_core 0.6.4", "sec1 0.3.0", - "subtle", + "subtle 2.4.1", "zeroize", ] @@ -1345,12 +1423,12 @@ dependencies = [ "crypto-bigint 0.5.2", "digest 0.10.7", "ff 0.13.0", - "generic-array", + "generic-array 0.14.7", "group 0.13.0", "pkcs8 0.10.2", "rand_core 0.6.4", "sec1 0.7.3", - "subtle", + "subtle 2.4.1", "zeroize", ] @@ -1459,7 +1537,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "131655483be284720a17d74ff97592b8e76576dc25563148601df2d7c9080924" dependencies = [ "rand_core 0.6.4", - "subtle", + "subtle 2.4.1", ] [[package]] @@ -1469,7 +1547,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d013fc25338cc558c5c2cfbad646908fb23591e2404481826742b651c9af7160" dependencies = [ "rand_core 0.6.4", - "subtle", + "subtle 2.4.1", ] [[package]] @@ -1479,7 +1557,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ded41244b729663b1e574f1b4fb731469f69f79c17667b5d776b16cda0479449" dependencies = [ "rand_core 0.6.4", - "subtle", + "subtle 2.4.1", ] [[package]] @@ -1759,6 +1837,15 @@ dependencies = [ "windows 0.39.0", ] +[[package]] +name = "generic-array" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffdf9f34f1447443d37393cc6c2b8313aebddcd96906caf34e54c68d8e57d7bd" +dependencies = [ + "typenum", +] + [[package]] name = "generic-array" version = "0.14.7" @@ -1814,7 +1901,7 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d930750de5717d2dd0b8c0d42c076c0e884c81a73e6cab859bbd2339c71e3e40" dependencies = [ - "opaque-debug", + "opaque-debug 0.3.0", "polyval", ] @@ -1945,7 +2032,7 @@ dependencies = [ "byteorder", "ff 0.11.1", "rand_core 0.6.4", - "subtle", + "subtle 2.4.1", ] [[package]] @@ -1956,7 +2043,7 @@ checksum = "5dfbfb3a6cfbd390d5c9564ab283a0349b9b9fcd46a706c1eb10e0db70bfbac7" dependencies = [ "ff 0.12.1", "rand_core 0.6.4", - "subtle", + "subtle 2.4.1", ] [[package]] @@ -1967,7 +2054,7 @@ checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" dependencies = [ "ff 0.13.0", "rand_core 0.6.4", - "subtle", + "subtle 2.4.1", ] [[package]] @@ -2143,6 +2230,26 @@ version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7ebdb29d2ea9ed0083cd8cece49bbd968021bd99b0849edb4a9a7ee0fdf6a4e0" +[[package]] +name = "hkdf" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01706d578d5c281058480e673ae4086a9f4710d8df1ad80a5b03e39ece5f886b" +dependencies = [ + "digest 0.9.0", + "hmac 0.11.0", +] + +[[package]] +name = "hmac" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a2a2320eb7ec0ebe8da8f744d7812d9fc4cb4d09344ac01898dbcb6a20ae69b" +dependencies = [ + "crypto-mac 0.11.1", + "digest 0.9.0", +] + [[package]] name = "hmac" version = "0.12.1" @@ -2394,7 +2501,7 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5" dependencies = [ - "generic-array", + "generic-array 0.14.7", ] [[package]] @@ -2553,6 +2660,12 @@ dependencies = [ "signature 2.1.0", ] +[[package]] +name = "keystream" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c33070833c9ee02266356de0c43f723152bd38bd96ddf52c82b3af10c9138b28" + [[package]] name = "kuchiki" version = "0.8.1" @@ -2589,6 +2702,12 @@ dependencies = [ "pkg-config", ] +[[package]] +name = "libm" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7012b1bbb0719e1097c47611d3898568c546d597c2e74d66f6087edd5233ff4" + [[package]] name = "libz-sys" version = "1.1.8" @@ -2616,6 +2735,18 @@ version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f051f77a7c8e6957c0696eac88f26b0117e54f52d3fc682ab19397a8812846a4" +[[package]] +name = "lioness" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ae926706ba42c425c9457121178330d75e273df2e82e28b758faf3de3a9acb9" +dependencies = [ + "arrayref", + "blake2 0.8.1", + "chacha", + "keystream", +] + [[package]] name = "lock_api" version = "0.4.9" @@ -2859,6 +2990,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" dependencies = [ "autocfg", + "libm", ] [[package]] @@ -3146,7 +3278,7 @@ version = "0.1.0" dependencies = [ "aes-gcm", "argon2", - "generic-array", + "generic-array 0.14.7", "getrandom 0.2.10", "rand 0.8.5", "serde", @@ -3364,6 +3496,12 @@ version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6f61fba1741ea2b3d6a1e3178721804bb716a68a6aeba1149b5d52e3d464ea66" +[[package]] +name = "opaque-debug" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2839e79665f131bdb5782e51f2c6c9599c133c6098982a54c794358bf432529c" + [[package]] name = "opaque-debug" version = "0.3.0" @@ -3518,7 +3656,7 @@ checksum = "346f04948ba92c43e8469c1ee6736c7563d71012b17d40745260fe106aac2166" dependencies = [ "base64ct", "rand_core 0.6.4", - "subtle", + "subtle 2.4.1", ] [[package]] @@ -3540,7 +3678,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8ed6a7761f76e3b9f92dfb0a60a6a6477c61024b775147ff0973a02653abaf2" dependencies = [ "digest 0.10.7", - "hmac", + "hmac 0.12.1", ] [[package]] @@ -3821,7 +3959,7 @@ checksum = "7ef234e08c11dfcb2e56f79fd70f6f2eb7f025c0ce2333e82f4f0518ecad30c6" dependencies = [ "cfg-if", "cpufeatures", - "opaque-debug", + "opaque-debug 0.3.0", "universal-hash", ] @@ -4027,6 +4165,16 @@ dependencies = [ "getrandom 0.2.10", ] +[[package]] +name = "rand_distr" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9e9532ada3929fb8b2e9dbe28d1e06c9b2cc65813f074fcb6bd5fbefeff9d56" +dependencies = [ + "num-traits", + "rand 0.7.3", +] + [[package]] name = "rand_hc" version = "0.2.0" @@ -4153,7 +4301,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7743f17af12fa0b03b803ba12cd6a8d9483a587e89c69445e3909655c0b9fabb" dependencies = [ "crypto-bigint 0.4.9", - "hmac", + "hmac 0.12.1", "zeroize", ] @@ -4163,8 +4311,8 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2" dependencies = [ - "hmac", - "subtle", + "hmac 0.12.1", + "subtle 2.4.1", ] [[package]] @@ -4363,9 +4511,9 @@ checksum = "3be24c1842290c45df0a7bf069e0c268a747ad05a192f2fd7dcfdbc1cba40928" dependencies = [ "base16ct 0.1.1", "der 0.6.1", - "generic-array", + "generic-array 0.14.7", "pkcs8 0.9.0", - "subtle", + "subtle 2.4.1", "zeroize", ] @@ -4377,9 +4525,9 @@ checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc" dependencies = [ "base16ct 0.2.0", "der 0.7.7", - "generic-array", + "generic-array 0.14.7", "pkcs8 0.10.2", - "subtle", + "subtle 2.4.1", "zeroize", ] @@ -4620,7 +4768,7 @@ dependencies = [ "cfg-if", "cpufeatures", "digest 0.9.0", - "opaque-debug", + "opaque-debug 0.3.0", ] [[package]] @@ -4731,6 +4879,30 @@ dependencies = [ "system-deps 5.0.0", ] +[[package]] +name = "sphinx-packet" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc43eda802856ee82a7555c7b75ceb9e07451741c7a2f5f23d036020e01189d4" +dependencies = [ + "aes 0.7.5", + "arrayref", + "blake2 0.8.1", + "bs58 0.4.0", + "byteorder", + "chacha", + "curve25519-dalek", + "digest 0.9.0", + "hkdf", + "hmac 0.11.0", + "lioness", + "log", + "rand 0.7.3", + "rand_distr", + "sha2 0.9.9", + "subtle 2.4.1", +] + [[package]] name = "spin" version = "0.5.2" @@ -4826,6 +4998,12 @@ dependencies = [ "syn 1.0.107", ] +[[package]] +name = "subtle" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d67a5a62ba6e01cb2192ff309324cb4875d0c451d55fe2319433abe7a05a8ee" + [[package]] name = "subtle" version = "2.4.1" @@ -5185,7 +5363,7 @@ dependencies = [ "serde_repr", "sha2 0.10.6", "signature 2.1.0", - "subtle", + "subtle 2.4.1", "subtle-encoding", "tendermint-proto", "time", @@ -5245,7 +5423,7 @@ dependencies = [ "serde", "serde_bytes", "serde_json", - "subtle", + "subtle 2.4.1", "subtle-encoding", "tendermint", "tendermint-config", @@ -5643,7 +5821,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7d3160b73c9a19f7e2939a2fdad446c57c1bbbbf4d919d3213ff1267a580d8b5" dependencies = [ "crypto-common", - "subtle", + "subtle 2.4.1", ] [[package]] From c6519ffdffd4f7e9c9b589d10d120ea8804e2aba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bogdan-=C8=98tefan=20Neac=C8=99u?= Date: Mon, 14 Aug 2023 13:39:43 +0300 Subject: [PATCH 144/361] Add NC .env configuration --- .../validator-client/src/nym_api/mod.rs | 23 +++++++++++++++---- contracts/Cargo.lock | 6 ----- envs/qa-qwerty.env | 2 +- envs/qa.env | 2 +- envs/sandbox.env | 2 +- nym-connect/desktop/Cargo.lock | 3 ++- nym-connect/desktop/src-tauri/Cargo.toml | 1 + nym-connect/desktop/src-tauri/src/error.rs | 15 ++++++++++++ nym-connect/desktop/src-tauri/src/main.rs | 4 +++- .../src-tauri/src/operations/directory/mod.rs | 13 +++++------ 10 files changed, 48 insertions(+), 23 deletions(-) diff --git a/common/client-libs/validator-client/src/nym_api/mod.rs b/common/client-libs/validator-client/src/nym_api/mod.rs index 67b62decd9..a7d2f1c1c1 100644 --- a/common/client-libs/validator-client/src/nym_api/mod.rs +++ b/common/client-libs/validator-client/src/nym_api/mod.rs @@ -7,11 +7,11 @@ use nym_api_requests::coconut::{ BlindSignRequestBody, BlindedSignatureResponse, VerifyCredentialBody, VerifyCredentialResponse, }; use nym_api_requests::models::{ - ComputeRewardEstParam, GatewayCoreStatusResponse, GatewayStatusReportResponse, - GatewayUptimeHistoryResponse, InclusionProbabilityResponse, MixNodeBondAnnotated, - MixnodeCoreStatusResponse, MixnodeStatusReportResponse, MixnodeStatusResponse, - MixnodeUptimeHistoryResponse, RequestError, RewardEstimationResponse, StakeSaturationResponse, - UptimeResponse, + ComputeRewardEstParam, GatewayBondAnnotated, GatewayCoreStatusResponse, + GatewayStatusReportResponse, GatewayUptimeHistoryResponse, InclusionProbabilityResponse, + MixNodeBondAnnotated, MixnodeCoreStatusResponse, MixnodeStatusReportResponse, + MixnodeStatusResponse, MixnodeUptimeHistoryResponse, RequestError, RewardEstimationResponse, + StakeSaturationResponse, UptimeResponse, }; use nym_mixnet_contract_common::mixnode::MixNodeDetails; use nym_mixnet_contract_common::{GatewayBond, IdentityKeyRef, MixId}; @@ -148,6 +148,19 @@ impl Client { .await } + pub async fn get_gateways_detailed(&self) -> Result, NymAPIError> { + self.query_nym_api( + &[ + routes::API_VERSION, + routes::STATUS, + routes::GATEWAYS, + routes::DETAILED, + ], + NO_PARAMS, + ) + .await + } + pub async fn get_mixnodes_detailed_unfiltered( &self, ) -> Result, NymAPIError> { diff --git a/contracts/Cargo.lock b/contracts/Cargo.lock index 0f56b6f7a9..c87480ad1b 100644 --- a/contracts/Cargo.lock +++ b/contracts/Cargo.lock @@ -290,12 +290,6 @@ dependencies = [ "libc", ] -[[package]] -name = "crunchy" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" - [[package]] name = "crypto-bigint" version = "0.4.9" diff --git a/envs/qa-qwerty.env b/envs/qa-qwerty.env index aba57bb0ed..e485501274 100644 --- a/envs/qa-qwerty.env +++ b/envs/qa-qwerty.env @@ -20,6 +20,6 @@ SERVICE_PROVIDER_DIRECTORY_CONTRACT_ADDRESS=n1qsn2655eflc0nx2uwqtwyv5kad5dwm4c0g NAME_SERVICE_CONTRACT_ADDRESS=n1cm2u5vfjd3zalfw0p65xyh4tcrw3hjlm0960gzhewga449h4mgas77mjkl STATISTICS_SERVICE_DOMAIN_ADDRESS="https://mainnet-stats.nymte.ch:8090" NYXD="https://qwerty-validator.qa.nymte.ch/" -NYM_API="https://qwerty-validator-api.qa.nymte.ch/api" +NYM_API="https://qwerty-validator-api.qa.nymte.ch/api/" DKG_TIME_CONFIGURATION="600,300,300,60,60,1209600" diff --git a/envs/qa.env b/envs/qa.env index f09ee6baf1..2d90089a06 100644 --- a/envs/qa.env +++ b/envs/qa.env @@ -18,6 +18,6 @@ COCONUT_DKG_CONTRACT_ADDRESS=n17p9rzwnnfxcjp32un9ug7yhhzgtkhvl988qccs REWARDING_VALIDATOR_ADDRESS=n1tfzd4qz3a45u8p4mr5zmzv66457uwjgcl05jdq STATISTICS_SERVICE_DOMAIN_ADDRESS="http://0.0.0.0" NYXD="https://qa-validator.nymtech.net" -NYM_API="https://qa-validator-api.nymtech.net/api" +NYM_API="https://qa-validator-api.nymtech.net/api/" DKG_TIME_CONFIGURATION="600,300,300,60,60,1209600" \ No newline at end of file diff --git a/envs/sandbox.env b/envs/sandbox.env index 70aa96ff42..52a05c5ab5 100644 --- a/envs/sandbox.env +++ b/envs/sandbox.env @@ -20,4 +20,4 @@ COCONUT_DKG_CONTRACT_ADDRESS=n1ahg0erc2fs6xx3j5m8sfx3ryuzdjh6kf6qm9plsf865flteky STATISTICS_SERVICE_DOMAIN_ADDRESS="http://0.0.0.0" NYXD="https://sandbox-validator1.nymtech.net" -NYM_API="https://sandbox-nym-api1.nymtech.net/api" \ No newline at end of file +NYM_API="https://sandbox-nym-api1.nymtech.net/api/" \ No newline at end of file diff --git a/nym-connect/desktop/Cargo.lock b/nym-connect/desktop/Cargo.lock index c522d297d1..d43ef62b10 100644 --- a/nym-connect/desktop/Cargo.lock +++ b/nym-connect/desktop/Cargo.lock @@ -3656,7 +3656,7 @@ dependencies = [ [[package]] name = "nym-connect" -version = "1.1.15" +version = "1.1.16" dependencies = [ "anyhow", "bip39", @@ -3678,6 +3678,7 @@ dependencies = [ "nym-socks5-client-core", "nym-sphinx", "nym-task", + "nym-validator-client", "pretty_env_logger", "rand 0.8.5", "reqwest", diff --git a/nym-connect/desktop/src-tauri/Cargo.toml b/nym-connect/desktop/src-tauri/Cargo.toml index 0d1089132d..e900bd0542 100644 --- a/nym-connect/desktop/src-tauri/Cargo.toml +++ b/nym-connect/desktop/src-tauri/Cargo.toml @@ -58,6 +58,7 @@ nym-bin-common = { path = "../../../common/bin-common"} nym-socks5-client-core = { path = "../../../common/socks5-client-core" } nym-sphinx = { path = "../../../common/nymsphinx" } nym-task = { path = "../../../common/task" } +nym-validator-client = { path = "../../../common/client-libs/validator-client" } [dev-dependencies] ts-rs = "6.1.2" diff --git a/nym-connect/desktop/src-tauri/src/error.rs b/nym-connect/desktop/src-tauri/src/error.rs index 651e741aea..1f3b38864b 100644 --- a/nym-connect/desktop/src-tauri/src/error.rs +++ b/nym-connect/desktop/src-tauri/src/error.rs @@ -45,6 +45,21 @@ pub enum BackendError { #[from] source: crate::operations::growth::api_client::ApiClientError, }, + #[error("{source}")] + EnvError { + #[from] + source: std::env::VarError, + }, + #[error("{source}")] + UrlError { + #[from] + source: url::ParseError, + }, + #[error("{source}")] + APIError { + #[from] + source: nym_validator_client::nym_api::error::NymAPIError, + }, #[error("could not send disconnect signal to the SOCKS5 client")] CoundNotSendDisconnectSignal, diff --git a/nym-connect/desktop/src-tauri/src/main.rs b/nym-connect/desktop/src-tauri/src/main.rs index 12c7ad06ac..32a6758239 100644 --- a/nym-connect/desktop/src-tauri/src/main.rs +++ b/nym-connect/desktop/src-tauri/src/main.rs @@ -4,6 +4,7 @@ )] use std::env; +use std::path::PathBuf; use std::sync::Arc; use nym_config::defaults::setup_env; @@ -30,7 +31,8 @@ mod window; fn main() { dotenvy::dotenv().ok(); - setup_env(None); + + setup_env(env::args().nth(1).map(PathBuf::from).as_ref()); println!("Starting up..."); // As per breaking change description here diff --git a/nym-connect/desktop/src-tauri/src/operations/directory/mod.rs b/nym-connect/desktop/src-tauri/src/operations/directory/mod.rs index a4f8bf53a9..575020ff62 100644 --- a/nym-connect/desktop/src-tauri/src/operations/directory/mod.rs +++ b/nym-connect/desktop/src-tauri/src/operations/directory/mod.rs @@ -8,9 +8,13 @@ use crate::{ }; use itertools::Itertools; use nym_api_requests::models::GatewayBondAnnotated; +use nym_config::defaults::var_names::NYM_API; use nym_contracts_common::types::Percent; +use nym_validator_client::nym_api::Client as ApiClient; +use std::str::FromStr; use std::sync::Arc; use tokio::sync::RwLock; +use url::Url; static SERVICE_PROVIDER_WELLKNOWN_URL: &str = "https://nymtech.net/.wellknown/connect/service-providers.json"; @@ -25,9 +29,6 @@ static HARBOUR_MASTER_URL: &str = "https://harbourmaster.nymtech.net/v1/services // We only consider network requesters with a routing score above this threshold const SERVICE_ROUTING_SCORE_THRESHOLD: f32 = 0.9; -static GATEWAYS_DETAILED_URL: &str = - "https://validator.nymtech.net/api/v1/status/gateways/detailed"; - // Only use gateways with a performnnce score above this const GATEWAY_PERFORMANCE_SCORE_THRESHOLD: u64 = 90; @@ -116,10 +117,8 @@ fn filter_out_inactive_services( } async fn fetch_gateways() -> Result> { - Ok(reqwest::get(GATEWAYS_DETAILED_URL) - .await? - .json::>() - .await?) + let api_client = ApiClient::new(Url::from_str(&std::env::var(NYM_API)?)?); + Ok(api_client.get_gateways_detailed().await?) } #[tauri::command] From ed3535d510e89d6f366133dde3b3664d970b50da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bogdan-=C8=98tefan=20Neac=C8=99u?= Date: Fri, 11 Aug 2023 12:45:07 +0300 Subject: [PATCH 145/361] Add network name to .envs --- common/network-defaults/src/lib.rs | 10 + common/network-defaults/src/mainnet.rs | 4 + common/network-defaults/src/var_names.rs | 2 + .../docs/src/bandwidth-credentials.md | 2 + envs/local.env | 2 + envs/mainnet.env | 2 + envs/qa-qwerty.env | 2 + envs/qa.env | 2 + envs/sandbox.env | 2 + nym-wallet/Cargo.lock | 274 +++++++++++++++--- nym-wallet/nym-wallet-types/src/network/qa.rs | 3 + .../nym-wallet-types/src/network/sandbox.rs | 3 + 12 files changed, 260 insertions(+), 48 deletions(-) diff --git a/common/network-defaults/src/lib.rs b/common/network-defaults/src/lib.rs index 019c894f96..77b1761d1c 100644 --- a/common/network-defaults/src/lib.rs +++ b/common/network-defaults/src/lib.rs @@ -37,6 +37,7 @@ pub struct NymContracts { // with `NetworkDetails` defined in all.rs... #[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)] pub struct NymNetworkDetails { + pub network_name: String, pub chain_details: ChainDetails, pub endpoints: Vec, pub contracts: NymContracts, @@ -52,6 +53,7 @@ impl Default for NymNetworkDetails { impl NymNetworkDetails { pub fn new_empty() -> Self { NymNetworkDetails { + network_name: Default::default(), chain_details: ChainDetails { bech32_account_prefix: Default::default(), mix_denom: DenomDetailsOwned { @@ -80,6 +82,7 @@ impl NymNetworkDetails { } NymNetworkDetails::new_empty() + .with_network_name(var(var_names::NETWORK_NAME).expect("network name not set")) .with_bech32_account_prefix( var(var_names::BECH32_PREFIX).expect("bech32 prefix not set"), ) @@ -137,6 +140,7 @@ impl NymNetworkDetails { // Consider caching this process (lazy static) NymNetworkDetails { + network_name: mainnet::NETWORK_NAME.into(), chain_details: ChainDetails { bech32_account_prefix: mainnet::BECH32_PREFIX.into(), mix_denom: mainnet::MIX_DENOM.into(), @@ -160,6 +164,12 @@ impl NymNetworkDetails { } } + #[must_use] + pub fn with_network_name(mut self, network_name: String) -> Self { + self.network_name = network_name; + self + } + #[must_use] pub fn with_bech32_account_prefix>(mut self, prefix: S) -> Self { self.chain_details.bech32_account_prefix = prefix.into(); diff --git a/common/network-defaults/src/mainnet.rs b/common/network-defaults/src/mainnet.rs index 453f6695ec..f2ec070533 100644 --- a/common/network-defaults/src/mainnet.rs +++ b/common/network-defaults/src/mainnet.rs @@ -4,6 +4,8 @@ use crate::var_names; use crate::{DenomDetails, ValidatorDetails}; +pub(crate) const NETWORK_NAME: &str = "mainnet"; + pub(crate) const BECH32_PREFIX: &str = "n"; pub const MIX_DENOM: DenomDetails = DenomDetails::new("unym", "nym", 6); @@ -54,6 +56,7 @@ pub fn read_var_if_not_default(var: &str) -> Option { pub fn export_to_env() { set_var_to_default(var_names::CONFIGURED, "true"); + set_var_to_default(var_names::NETWORK_NAME, NETWORK_NAME); set_var_to_default(var_names::BECH32_PREFIX, BECH32_PREFIX); set_var_to_default(var_names::MIX_DENOM, MIX_DENOM.base); set_var_to_default(var_names::MIX_DENOM_DISPLAY, MIX_DENOM.display); @@ -95,6 +98,7 @@ pub fn export_to_env() { pub fn export_to_env_if_not_set() { set_var_conditionally_to_default(var_names::CONFIGURED, "true"); + set_var_conditionally_to_default(var_names::NETWORK_NAME, NETWORK_NAME); set_var_conditionally_to_default(var_names::BECH32_PREFIX, BECH32_PREFIX); set_var_conditionally_to_default(var_names::MIX_DENOM, MIX_DENOM.base); set_var_conditionally_to_default(var_names::MIX_DENOM_DISPLAY, MIX_DENOM.display); diff --git a/common/network-defaults/src/var_names.rs b/common/network-defaults/src/var_names.rs index c7ff9bb680..7639efa8e4 100644 --- a/common/network-defaults/src/var_names.rs +++ b/common/network-defaults/src/var_names.rs @@ -4,6 +4,8 @@ // Environment variable that, if set, shows the environment is currently configured pub const CONFIGURED: &str = "CONFIGURED"; +pub const NETWORK_NAME: &str = "NETWORK_NAME"; + pub const BECH32_PREFIX: &str = "BECH32_PREFIX"; pub const MIX_DENOM: &str = "MIX_DENOM"; pub const MIX_DENOM_DISPLAY: &str = "MIX_DENOM_DISPLAY"; diff --git a/documentation/docs/src/bandwidth-credentials.md b/documentation/docs/src/bandwidth-credentials.md index 6423dcf035..f267cb045d 100644 --- a/documentation/docs/src/bandwidth-credentials.md +++ b/documentation/docs/src/bandwidth-credentials.md @@ -7,6 +7,8 @@ Create a `sandbox.env` file with the following details: ``` CONFIGURED=true +NETWORK_NAME=sandbox + RUST_LOG=info RUST_BACKTRACE=1 diff --git a/envs/local.env b/envs/local.env index 7c450b8aec..7efbad719e 100644 --- a/envs/local.env +++ b/envs/local.env @@ -1,5 +1,7 @@ CONFIGURED=true +NETWORK_NAME=local + RUST_LOG=info RUST_BACKTRACE=1 diff --git a/envs/mainnet.env b/envs/mainnet.env index 3ace54478b..4092b50ef8 100644 --- a/envs/mainnet.env +++ b/envs/mainnet.env @@ -1,5 +1,7 @@ CONFIGURED=true +NETWORK_NAME=mainnet + RUST_LOG=info RUST_BACKTRACE=1 diff --git a/envs/qa-qwerty.env b/envs/qa-qwerty.env index e485501274..219d9c7a6c 100644 --- a/envs/qa-qwerty.env +++ b/envs/qa-qwerty.env @@ -1,5 +1,7 @@ CONFIGURED=true +NETWORK_NAME=qa-qwerty + RUST_LOG=info RUST_BACKTRACE=1 diff --git a/envs/qa.env b/envs/qa.env index 2d90089a06..fbaa9fd556 100644 --- a/envs/qa.env +++ b/envs/qa.env @@ -1,5 +1,7 @@ CONFIGURED=true +NETWORK_NAME=qa + RUST_LOG=info RUST_BACKTRACE=1 diff --git a/envs/sandbox.env b/envs/sandbox.env index 52a05c5ab5..921bc5e215 100644 --- a/envs/sandbox.env +++ b/envs/sandbox.env @@ -1,5 +1,7 @@ CONFIGURED=true +NETWORK_NAME=sandbox + RUST_LOG=info RUST_BACKTRACE=1 diff --git a/nym-wallet/Cargo.lock b/nym-wallet/Cargo.lock index b9d551fd84..da53aee71f 100644 --- a/nym-wallet/Cargo.lock +++ b/nym-wallet/Cargo.lock @@ -21,7 +21,20 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0" dependencies = [ "crypto-common", - "generic-array", + "generic-array 0.14.7", +] + +[[package]] +name = "aes" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e8b47f52ea9bae42228d07ec09eb676433d7c4ed1ebdf0f1d1c29ed446f1ab8" +dependencies = [ + "cfg-if", + "cipher 0.3.0", + "cpufeatures", + "ctr 0.8.0", + "opaque-debug 0.3.0", ] [[package]] @@ -31,7 +44,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "433cfd6710c9986c576a25ca913c39d66a6474107b406f34f91d4a8923395241" dependencies = [ "cfg-if", - "cipher", + "cipher 0.4.4", "cpufeatures", ] @@ -42,11 +55,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "82e1366e0c69c9f927b1fa5ce2c7bf9eafc8f9268c0b9800729e8b267612447c" dependencies = [ "aead", - "aes", - "cipher", - "ctr", + "aes 0.8.2", + "cipher 0.4.4", + "ctr 0.9.2", "ghash", - "subtle", + "subtle 2.4.1", ] [[package]] @@ -97,7 +110,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "95c2fcf79ad1932ac6269a738109997a83c227c09b75842ae564dc8ede6a861c" dependencies = [ "base64ct", - "blake2", + "blake2 0.10.6", "password-hash", ] @@ -212,14 +225,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7e141fb0f8be1c7b45887af94c88b182472b57c96b56773250ae00cd6a14a164" dependencies = [ "bs58 0.5.0", - "hmac", + "hmac 0.12.1", "k256 0.13.1", "once_cell", "pbkdf2", "rand_core 0.6.4", "ripemd", "sha2 0.10.6", - "subtle", + "subtle 2.4.1", "zeroize", ] @@ -261,6 +274,18 @@ dependencies = [ "wyz", ] +[[package]] +name = "blake2" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94cb07b0da6a73955f8fb85d24c466778e70cda767a568229b104f0264089330" +dependencies = [ + "byte-tools", + "crypto-mac 0.7.0", + "digest 0.8.1", + "opaque-debug 0.2.3", +] + [[package]] name = "blake2" version = "0.10.6" @@ -282,7 +307,7 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" dependencies = [ - "generic-array", + "generic-array 0.14.7", ] [[package]] @@ -291,7 +316,7 @@ version = "0.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69cce20737498f97b993470a6e536b8523f0af7892a4f928cceb1ac5e52ebe7e" dependencies = [ - "generic-array", + "generic-array 0.14.7", ] [[package]] @@ -304,7 +329,7 @@ dependencies = [ "group 0.11.0", "pairing", "rand_core 0.6.4", - "subtle", + "subtle 2.4.1", "zeroize", ] @@ -366,6 +391,12 @@ version = "3.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0d261e256854913907f67ed06efbc3338dfe6179796deefc1ff763fc1aee5535" +[[package]] +name = "byte-tools" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3b5ca7a04898ad4bcd41c90c5285445ff5b791899bb1b0abdd2a2aa791211d7" + [[package]] name = "bytemuck" version = "1.13.0" @@ -480,6 +511,15 @@ dependencies = [ "keystream", ] +[[package]] +name = "cipher" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ee52072ec15386f770805afd189a01c8841be8696bed250fa2f13c4c0d6dfb7" +dependencies = [ + "generic-array 0.14.7", +] + [[package]] name = "cipher" version = "0.4.4" @@ -809,9 +849,9 @@ version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ef2b4b23cddf68b89b8f8069890e8c270d54e2d5fe1b143820234805e4cb17ef" dependencies = [ - "generic-array", + "generic-array 0.14.7", "rand_core 0.6.4", - "subtle", + "subtle 2.4.1", "zeroize", ] @@ -821,9 +861,9 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf4c2f4e1afd912bc40bfd6fed5d9dc1f288e0ba01bfcc835cc5bc3eb13efe15" dependencies = [ - "generic-array", + "generic-array 0.14.7", "rand_core 0.6.4", - "subtle", + "subtle 2.4.1", "zeroize", ] @@ -833,11 +873,31 @@ version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" dependencies = [ - "generic-array", + "generic-array 0.14.7", "rand_core 0.6.4", "typenum", ] +[[package]] +name = "crypto-mac" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4434400df11d95d556bac068ddfedd482915eb18fe8bea89bc80b6e4b1c179e5" +dependencies = [ + "generic-array 0.12.4", + "subtle 1.0.0", +] + +[[package]] +name = "crypto-mac" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1d1a86f49236c215f271d40892d5fc950490551400b02ef360692c29815c714" +dependencies = [ + "generic-array 0.14.7", + "subtle 2.4.1", +] + [[package]] name = "cssparser" version = "0.27.2" @@ -884,13 +944,22 @@ dependencies = [ "syn 1.0.107", ] +[[package]] +name = "ctr" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "049bb91fb4aaf0e3c7efa6cd5ef877dbbbd15b39dad06d9948de4ec8a75761ea" +dependencies = [ + "cipher 0.3.0", +] + [[package]] name = "ctr" version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0369ee1ad671834580515889b80f2ea915f23b8be8d0daa4bbaf2ac5c7590835" dependencies = [ - "cipher", + "cipher 0.4.4", ] [[package]] @@ -908,7 +977,7 @@ dependencies = [ "byteorder", "digest 0.9.0", "rand_core 0.5.1", - "subtle", + "subtle 2.4.1", "zeroize", ] @@ -1100,13 +1169,22 @@ dependencies = [ "syn 1.0.107", ] +[[package]] +name = "digest" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3d0c8c8752312f9713efd397ff63acb9f85585afbf179282e720e7704954dd5" +dependencies = [ + "generic-array 0.12.4", +] + [[package]] name = "digest" version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" dependencies = [ - "generic-array", + "generic-array 0.14.7", ] [[package]] @@ -1118,7 +1196,7 @@ dependencies = [ "block-buffer 0.10.3", "const-oid", "crypto-common", - "subtle", + "subtle 2.4.1", ] [[package]] @@ -1326,12 +1404,12 @@ dependencies = [ "der 0.6.1", "digest 0.10.7", "ff 0.12.1", - "generic-array", + "generic-array 0.14.7", "group 0.12.1", "pkcs8 0.9.0", "rand_core 0.6.4", "sec1 0.3.0", - "subtle", + "subtle 2.4.1", "zeroize", ] @@ -1345,12 +1423,12 @@ dependencies = [ "crypto-bigint 0.5.2", "digest 0.10.7", "ff 0.13.0", - "generic-array", + "generic-array 0.14.7", "group 0.13.0", "pkcs8 0.10.2", "rand_core 0.6.4", "sec1 0.7.3", - "subtle", + "subtle 2.4.1", "zeroize", ] @@ -1459,7 +1537,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "131655483be284720a17d74ff97592b8e76576dc25563148601df2d7c9080924" dependencies = [ "rand_core 0.6.4", - "subtle", + "subtle 2.4.1", ] [[package]] @@ -1469,7 +1547,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d013fc25338cc558c5c2cfbad646908fb23591e2404481826742b651c9af7160" dependencies = [ "rand_core 0.6.4", - "subtle", + "subtle 2.4.1", ] [[package]] @@ -1479,7 +1557,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ded41244b729663b1e574f1b4fb731469f69f79c17667b5d776b16cda0479449" dependencies = [ "rand_core 0.6.4", - "subtle", + "subtle 2.4.1", ] [[package]] @@ -1759,6 +1837,15 @@ dependencies = [ "windows 0.39.0", ] +[[package]] +name = "generic-array" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffdf9f34f1447443d37393cc6c2b8313aebddcd96906caf34e54c68d8e57d7bd" +dependencies = [ + "typenum", +] + [[package]] name = "generic-array" version = "0.14.7" @@ -1814,7 +1901,7 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d930750de5717d2dd0b8c0d42c076c0e884c81a73e6cab859bbd2339c71e3e40" dependencies = [ - "opaque-debug", + "opaque-debug 0.3.0", "polyval", ] @@ -1945,7 +2032,7 @@ dependencies = [ "byteorder", "ff 0.11.1", "rand_core 0.6.4", - "subtle", + "subtle 2.4.1", ] [[package]] @@ -1956,7 +2043,7 @@ checksum = "5dfbfb3a6cfbd390d5c9564ab283a0349b9b9fcd46a706c1eb10e0db70bfbac7" dependencies = [ "ff 0.12.1", "rand_core 0.6.4", - "subtle", + "subtle 2.4.1", ] [[package]] @@ -1967,7 +2054,7 @@ checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" dependencies = [ "ff 0.13.0", "rand_core 0.6.4", - "subtle", + "subtle 2.4.1", ] [[package]] @@ -2143,6 +2230,26 @@ version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7ebdb29d2ea9ed0083cd8cece49bbd968021bd99b0849edb4a9a7ee0fdf6a4e0" +[[package]] +name = "hkdf" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01706d578d5c281058480e673ae4086a9f4710d8df1ad80a5b03e39ece5f886b" +dependencies = [ + "digest 0.9.0", + "hmac 0.11.0", +] + +[[package]] +name = "hmac" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a2a2320eb7ec0ebe8da8f744d7812d9fc4cb4d09344ac01898dbcb6a20ae69b" +dependencies = [ + "crypto-mac 0.11.1", + "digest 0.9.0", +] + [[package]] name = "hmac" version = "0.12.1" @@ -2394,7 +2501,7 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5" dependencies = [ - "generic-array", + "generic-array 0.14.7", ] [[package]] @@ -2553,6 +2660,12 @@ dependencies = [ "signature 2.1.0", ] +[[package]] +name = "keystream" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c33070833c9ee02266356de0c43f723152bd38bd96ddf52c82b3af10c9138b28" + [[package]] name = "kuchiki" version = "0.8.1" @@ -2589,6 +2702,12 @@ dependencies = [ "pkg-config", ] +[[package]] +name = "libm" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7012b1bbb0719e1097c47611d3898568c546d597c2e74d66f6087edd5233ff4" + [[package]] name = "libz-sys" version = "1.1.8" @@ -2616,6 +2735,18 @@ version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f051f77a7c8e6957c0696eac88f26b0117e54f52d3fc682ab19397a8812846a4" +[[package]] +name = "lioness" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ae926706ba42c425c9457121178330d75e273df2e82e28b758faf3de3a9acb9" +dependencies = [ + "arrayref", + "blake2 0.8.1", + "chacha", + "keystream", +] + [[package]] name = "lock_api" version = "0.4.9" @@ -2859,6 +2990,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" dependencies = [ "autocfg", + "libm", ] [[package]] @@ -3146,7 +3278,7 @@ version = "0.1.0" dependencies = [ "aes-gcm", "argon2", - "generic-array", + "generic-array 0.14.7", "getrandom 0.2.10", "rand 0.8.5", "serde", @@ -3364,6 +3496,12 @@ version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6f61fba1741ea2b3d6a1e3178721804bb716a68a6aeba1149b5d52e3d464ea66" +[[package]] +name = "opaque-debug" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2839e79665f131bdb5782e51f2c6c9599c133c6098982a54c794358bf432529c" + [[package]] name = "opaque-debug" version = "0.3.0" @@ -3518,7 +3656,7 @@ checksum = "346f04948ba92c43e8469c1ee6736c7563d71012b17d40745260fe106aac2166" dependencies = [ "base64ct", "rand_core 0.6.4", - "subtle", + "subtle 2.4.1", ] [[package]] @@ -3540,7 +3678,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8ed6a7761f76e3b9f92dfb0a60a6a6477c61024b775147ff0973a02653abaf2" dependencies = [ "digest 0.10.7", - "hmac", + "hmac 0.12.1", ] [[package]] @@ -3821,7 +3959,7 @@ checksum = "7ef234e08c11dfcb2e56f79fd70f6f2eb7f025c0ce2333e82f4f0518ecad30c6" dependencies = [ "cfg-if", "cpufeatures", - "opaque-debug", + "opaque-debug 0.3.0", "universal-hash", ] @@ -4027,6 +4165,16 @@ dependencies = [ "getrandom 0.2.10", ] +[[package]] +name = "rand_distr" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9e9532ada3929fb8b2e9dbe28d1e06c9b2cc65813f074fcb6bd5fbefeff9d56" +dependencies = [ + "num-traits", + "rand 0.7.3", +] + [[package]] name = "rand_hc" version = "0.2.0" @@ -4153,7 +4301,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7743f17af12fa0b03b803ba12cd6a8d9483a587e89c69445e3909655c0b9fabb" dependencies = [ "crypto-bigint 0.4.9", - "hmac", + "hmac 0.12.1", "zeroize", ] @@ -4163,8 +4311,8 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2" dependencies = [ - "hmac", - "subtle", + "hmac 0.12.1", + "subtle 2.4.1", ] [[package]] @@ -4363,9 +4511,9 @@ checksum = "3be24c1842290c45df0a7bf069e0c268a747ad05a192f2fd7dcfdbc1cba40928" dependencies = [ "base16ct 0.1.1", "der 0.6.1", - "generic-array", + "generic-array 0.14.7", "pkcs8 0.9.0", - "subtle", + "subtle 2.4.1", "zeroize", ] @@ -4377,9 +4525,9 @@ checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc" dependencies = [ "base16ct 0.2.0", "der 0.7.7", - "generic-array", + "generic-array 0.14.7", "pkcs8 0.10.2", - "subtle", + "subtle 2.4.1", "zeroize", ] @@ -4620,7 +4768,7 @@ dependencies = [ "cfg-if", "cpufeatures", "digest 0.9.0", - "opaque-debug", + "opaque-debug 0.3.0", ] [[package]] @@ -4731,6 +4879,30 @@ dependencies = [ "system-deps 5.0.0", ] +[[package]] +name = "sphinx-packet" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc43eda802856ee82a7555c7b75ceb9e07451741c7a2f5f23d036020e01189d4" +dependencies = [ + "aes 0.7.5", + "arrayref", + "blake2 0.8.1", + "bs58 0.4.0", + "byteorder", + "chacha", + "curve25519-dalek", + "digest 0.9.0", + "hkdf", + "hmac 0.11.0", + "lioness", + "log", + "rand 0.7.3", + "rand_distr", + "sha2 0.9.9", + "subtle 2.4.1", +] + [[package]] name = "spin" version = "0.5.2" @@ -4826,6 +4998,12 @@ dependencies = [ "syn 1.0.107", ] +[[package]] +name = "subtle" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d67a5a62ba6e01cb2192ff309324cb4875d0c451d55fe2319433abe7a05a8ee" + [[package]] name = "subtle" version = "2.4.1" @@ -5185,7 +5363,7 @@ dependencies = [ "serde_repr", "sha2 0.10.6", "signature 2.1.0", - "subtle", + "subtle 2.4.1", "subtle-encoding", "tendermint-proto", "time", @@ -5245,7 +5423,7 @@ dependencies = [ "serde", "serde_bytes", "serde_json", - "subtle", + "subtle 2.4.1", "subtle-encoding", "tendermint", "tendermint-config", @@ -5643,7 +5821,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7d3160b73c9a19f7e2939a2fdad446c57c1bbbbf4d919d3213ff1267a580d8b5" dependencies = [ "crypto-common", - "subtle", + "subtle 2.4.1", ] [[package]] diff --git a/nym-wallet/nym-wallet-types/src/network/qa.rs b/nym-wallet/nym-wallet-types/src/network/qa.rs index d333d71067..1c1ca33ae8 100644 --- a/nym-wallet/nym-wallet-types/src/network/qa.rs +++ b/nym-wallet/nym-wallet-types/src/network/qa.rs @@ -3,6 +3,8 @@ use nym_network_defaults::{ChainDetails, DenomDetails, NymContracts, ValidatorDe // -- Chain details -- +pub(crate) const NETWORK_NAME: &str = "qa"; + pub(crate) const BECH32_PREFIX: &str = "n"; pub(crate) const MIX_DENOM: DenomDetails = DenomDetails::new("unym", "nym", 6); pub(crate) const STAKE_DENOM: DenomDetails = DenomDetails::new("unyx", "nyx", 6); @@ -37,6 +39,7 @@ pub(crate) fn validators() -> Vec { pub(crate) fn network_details() -> nym_network_defaults::NymNetworkDetails { nym_network_defaults::NymNetworkDetails { + network_name: NETWORK_NAME.into(), chain_details: ChainDetails { bech32_account_prefix: BECH32_PREFIX.to_string(), mix_denom: MIX_DENOM.into(), diff --git a/nym-wallet/nym-wallet-types/src/network/sandbox.rs b/nym-wallet/nym-wallet-types/src/network/sandbox.rs index aefbcc73b3..95caa7596b 100644 --- a/nym-wallet/nym-wallet-types/src/network/sandbox.rs +++ b/nym-wallet/nym-wallet-types/src/network/sandbox.rs @@ -3,6 +3,8 @@ use nym_network_defaults::{ChainDetails, DenomDetails, NymContracts, ValidatorDe // -- Chain details -- +pub(crate) const NETWORK_NAME: &str = "sandbox"; + pub(crate) const BECH32_PREFIX: &str = "nymt"; pub(crate) const MIX_DENOM: DenomDetails = DenomDetails::new("unymt", "nymt", 6); pub(crate) const STAKE_DENOM: DenomDetails = DenomDetails::new("unyxt", "nyxt", 6); @@ -28,6 +30,7 @@ pub(crate) fn validators() -> Vec { pub(crate) fn network_details() -> nym_network_defaults::NymNetworkDetails { nym_network_defaults::NymNetworkDetails { + network_name: NETWORK_NAME.into(), chain_details: ChainDetails { bech32_account_prefix: BECH32_PREFIX.to_string(), mix_denom: MIX_DENOM.into(), From 691ac6d5ea64d28589ef5193de6ccdba480c01c8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bogdan-=C8=98tefan=20Neac=C8=99u?= Date: Fri, 11 Aug 2023 14:26:07 +0300 Subject: [PATCH 146/361] NC to use network dependent endpoints --- .../src/operations/connection/health_check.rs | 8 ++++++-- .../src-tauri/src/operations/directory/mod.rs | 18 ++++++++++-------- nym-connect/desktop/src-tauri/tauri.conf.json | 2 +- 3 files changed, 17 insertions(+), 11 deletions(-) diff --git a/nym-connect/desktop/src-tauri/src/operations/connection/health_check.rs b/nym-connect/desktop/src-tauri/src/operations/connection/health_check.rs index 52b47057b1..8344b5805b 100644 --- a/nym-connect/desktop/src-tauri/src/operations/connection/health_check.rs +++ b/nym-connect/desktop/src-tauri/src/operations/connection/health_check.rs @@ -1,6 +1,8 @@ +use crate::operations::directory::WELLKNOWN_DIR; +use nym_config::defaults::var_names::NETWORK_NAME; use serde::{Deserialize, Serialize}; -static HEALTH_CHECK_URL: &str = "https://nymtech.net/.wellknown/connect/healthcheck.json"; +static HEALTH_CHECK_URL: &str = "connect/healthcheck.json"; #[derive(Serialize, Deserialize, Debug)] struct ConnectionSuccess { @@ -9,7 +11,9 @@ struct ConnectionSuccess { pub async fn run_health_check() -> bool { log::info!("Running network health check"); - match crate::operations::http::socks5_get::<_, ConnectionSuccess>(HEALTH_CHECK_URL).await { + let network_name = std::env::var(NETWORK_NAME).expect("network name not set"); + let url = format!("{}/{}/{}", WELLKNOWN_DIR, network_name, HEALTH_CHECK_URL); + match crate::operations::http::socks5_get::<_, ConnectionSuccess>(url).await { Ok(res) if res.status == "ok" => { log::info!("✅✅✅ Healthcheck success!"); true diff --git a/nym-connect/desktop/src-tauri/src/operations/directory/mod.rs b/nym-connect/desktop/src-tauri/src/operations/directory/mod.rs index 575020ff62..dc001dac2e 100644 --- a/nym-connect/desktop/src-tauri/src/operations/directory/mod.rs +++ b/nym-connect/desktop/src-tauri/src/operations/directory/mod.rs @@ -8,7 +8,7 @@ use crate::{ }; use itertools::Itertools; use nym_api_requests::models::GatewayBondAnnotated; -use nym_config::defaults::var_names::NYM_API; +use nym_config::defaults::var_names::{NETWORK_NAME, NYM_API}; use nym_contracts_common::types::Percent; use nym_validator_client::nym_api::Client as ApiClient; use std::str::FromStr; @@ -16,12 +16,12 @@ use std::sync::Arc; use tokio::sync::RwLock; use url::Url; -static SERVICE_PROVIDER_WELLKNOWN_URL: &str = - "https://nymtech.net/.wellknown/connect/service-providers.json"; +pub(crate) static WELLKNOWN_DIR: &str = "https://nymtech.net/.wellknown"; + +static SERVICE_PROVIDER_URL_PATH: &str = "connect/service-providers.json"; // List of network-requesters running with medium toggle enabled, for testing -static SERVICE_PROVIDER_WELLKNOWN_URL_MEDIUM: &str = - "https://nymtech.net/.wellknown/connect/service-providers-medium.json"; +static SERVICE_PROVIDER_MEDIUM_URL_PATH: &str = "connect/service-providers-medium.json"; // Harbour master is used to periodically keep track of which network-requesters are online static HARBOUR_MASTER_URL: &str = "https://harbourmaster.nymtech.net/v1/services/?size=100"; @@ -81,11 +81,13 @@ pub async fn get_services( async fn fetch_services(privacy_level: &PrivacyLevel) -> Result> { let services_url = match privacy_level { - PrivacyLevel::Medium => SERVICE_PROVIDER_WELLKNOWN_URL_MEDIUM, - _ => SERVICE_PROVIDER_WELLKNOWN_URL, + PrivacyLevel::Medium => SERVICE_PROVIDER_MEDIUM_URL_PATH, + _ => SERVICE_PROVIDER_URL_PATH, }; - let services_res = reqwest::get(services_url) + let network_name = std::env::var(NETWORK_NAME)?; + let url = format!("{}/{}/{}", WELLKNOWN_DIR, network_name, services_url); + let services_res = reqwest::get(url) .await? .json::>() .await?; diff --git a/nym-connect/desktop/src-tauri/tauri.conf.json b/nym-connect/desktop/src-tauri/tauri.conf.json index 5f5e86bec0..48cce39527 100644 --- a/nym-connect/desktop/src-tauri/tauri.conf.json +++ b/nym-connect/desktop/src-tauri/tauri.conf.json @@ -51,7 +51,7 @@ "updater": { "active": true, "endpoints": [ - "https://nymtech.net/.wellknown/connect/updater.json" + "https://nymtech.net/.wellknown/mainnet/connect/updater.json" ], "dialog": true, "pubkey": "dW50cnVzdGVkIGNvbW1lbnQ6IG1pbmlzaWduIHB1YmxpYyBrZXk6IENCNzQ2M0E5N0VFODE2NApSV1JrZ2U2WE9rYTNETTg1OTBKdE5uWUEra0hML2syOVUvQ2lxZmFZRzZ1T3NWbGM0eVRzUTVhVwo=" From 5385685b3fa28949ee48d52aeb84ef127c9b12d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bogdan-=C8=98tefan=20Neac=C8=99u?= Date: Mon, 14 Aug 2023 11:28:36 +0300 Subject: [PATCH 147/361] Copy sandbox.env values to wallet hard-coded ones --- .../nym-wallet-types/src/network/sandbox.rs | 27 +++++++++++-------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/nym-wallet/nym-wallet-types/src/network/sandbox.rs b/nym-wallet/nym-wallet-types/src/network/sandbox.rs index 95caa7596b..4c241248c7 100644 --- a/nym-wallet/nym-wallet-types/src/network/sandbox.rs +++ b/nym-wallet/nym-wallet-types/src/network/sandbox.rs @@ -5,26 +5,31 @@ use nym_network_defaults::{ChainDetails, DenomDetails, NymContracts, ValidatorDe pub(crate) const NETWORK_NAME: &str = "sandbox"; -pub(crate) const BECH32_PREFIX: &str = "nymt"; -pub(crate) const MIX_DENOM: DenomDetails = DenomDetails::new("unymt", "nymt", 6); -pub(crate) const STAKE_DENOM: DenomDetails = DenomDetails::new("unyxt", "nyxt", 6); +pub(crate) const BECH32_PREFIX: &str = "n"; +pub(crate) const MIX_DENOM: DenomDetails = DenomDetails::new("unym", "nym", 6); +pub(crate) const STAKE_DENOM: DenomDetails = DenomDetails::new("unyx", "nyx", 6); // -- Contract addresses -- -pub(crate) const MIXNET_CONTRACT_ADDRESS: &str = "nymt1ghd753shjuwexxywmgs4xz7x2q732vcnstz02j"; -pub(crate) const VESTING_CONTRACT_ADDRESS: &str = "nymt14ejqjyq8um4p3xfqj74yld5waqljf88fn549lh"; +pub(crate) const MIXNET_CONTRACT_ADDRESS: &str = + "n1xr3rq8yvd7qplsw5yx90ftsr2zdhg4e9z60h5duusgxpv72hud3sjkxkav"; +pub(crate) const VESTING_CONTRACT_ADDRESS: &str = + "n1unyuj8qnmygvzuex3dwmg9yzt9alhvyeat0uu0jedg2wj33efl5qackslz"; pub(crate) const COCONUT_BANDWIDTH_CONTRACT_ADDRESS: &str = - "nymt1nz0r0au8aj6dc00wmm3ufy4g4k86rjzlgq608r"; -pub(crate) const GROUP_CONTRACT_ADDRESS: &str = "nymt1k8re7jwz6rnnwrktnejdwkwnncte7ek7kk6fvg"; -pub(crate) const MULTISIG_CONTRACT_ADDRESS: &str = "nymt1k8re7jwz6rnnwrktnejdwkwnncte7ek7kk6fvg"; -pub(crate) const COCONUT_DKG_CONTRACT_ADDRESS: &str = "nymt1k8re7jwz6rnnwrktnejdwkwnncte7ek7kk6fvg"; + "n16a32stm6kknhq5cc8rx77elr66pygf2hfszw7wvpq746x3uffylqkjar4l"; +pub(crate) const GROUP_CONTRACT_ADDRESS: &str = + "n1pd7kfgvr5tpcv0xnlv46c4jsq9jg2r799xxrcwqdm4l2jhq2pjwqrmz5ju"; +pub(crate) const MULTISIG_CONTRACT_ADDRESS: &str = + "n14ph4e660eyqz0j36zlkaey4zgzexm5twkmjlqaequxr2cjm9eprqsmad6k"; +pub(crate) const COCONUT_DKG_CONTRACT_ADDRESS: &str = + "n1ahg0erc2fs6xx3j5m8sfx3ryuzdjh6kf6qm9plsf865fltekyrfsesac6a"; // -- Constructor functions -- pub(crate) fn validators() -> Vec { vec![ValidatorDetails::new( - "https://sandbox-validator.nymtech.net", - Some("https://sandbox-validator.nymtech.net/api"), + "https://sandbox-validator1.nymtech.net", + Some("https://sandbox-nym-api1.nymtech.net/api"), )] } From f12f6b29cd288c21c5b9e31061133e50620569cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C4=99drzej=20Stuczy=C5=84ski?= Date: Mon, 14 Aug 2023 12:17:54 +0100 Subject: [PATCH 148/361] Chore/rustsec (#3764) * RUSTSEC-2023-0044 * RUSTSEC-2023-0034 --- Cargo.lock | 1090 ++-- clients/webassembly/Cargo.lock | 821 ++- .../client-libs/validator-client/Cargo.toml | 4 +- common/socks5-client-core/Cargo.toml | 2 +- common/statistics/Cargo.toml | 2 +- common/types/Cargo.toml | 2 +- explorer-api/Cargo.toml | 2 +- nym-api/Cargo.toml | 2 +- nym-connect/desktop/Cargo.lock | 1874 ++++-- nym-connect/desktop/src-tauri/Cargo.toml | 2 +- nym-connect/mobile/src-tauri/Cargo.lock | 1149 ++-- nym-connect/mobile/src-tauri/Cargo.toml | 4 +- nym-wallet/Cargo.lock | 1559 ++--- nym-wallet/src-tauri/Cargo.toml | 2 +- sdk/lib/socks5-listener/Cargo.lock | 5331 ----------------- sdk/lib/socks5-listener/Cargo.toml | 2 +- sdk/rust/nym-sdk/Cargo.toml | 2 +- .../network-requester/Cargo.toml | 2 +- 18 files changed, 3851 insertions(+), 8001 deletions(-) delete mode 100644 sdk/lib/socks5-listener/Cargo.lock diff --git a/Cargo.lock b/Cargo.lock index 85ff104b27..0d67f0655c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8,6 +8,15 @@ version = "0.11.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fe438c63458706e03479442743baae6c88256498e6431708f6dfc520a26515d3" +[[package]] +name = "addr2line" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4fa78e18c64fce05e902adecd7a5eed15a5e0a3439f7b0e169f0252214865e3" +dependencies = [ + "gimli", +] + [[package]] name = "adler" version = "1.0.2" @@ -39,9 +48,9 @@ dependencies = [ [[package]] name = "aes" -version = "0.8.2" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "433cfd6710c9986c576a25ca913c39d66a6474107b406f34f91d4a8923395241" +checksum = "ac1f845298e95f983ff1944b728ae08b8cebab80d684f0a832ed0fc74dfa27e2" dependencies = [ "cfg-if", "cipher 0.4.4", @@ -50,12 +59,12 @@ dependencies = [ [[package]] name = "aes-gcm" -version = "0.10.1" +version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82e1366e0c69c9f927b1fa5ce2c7bf9eafc8f9268c0b9800729e8b267612447c" +checksum = "209b47e8954a928e1d72e86eca7000ebb6655fe1436d33eefc2201cad027e237" dependencies = [ "aead", - "aes 0.8.2", + "aes 0.8.3", "cipher 0.4.4", "ctr 0.9.2", "ghash", @@ -86,24 +95,36 @@ dependencies = [ [[package]] name = "aho-corasick" -version = "1.0.1" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67fc08ce920c31afb70f013dcce1bfc3a3195de6a228474e45e1f145b36f8d04" +checksum = "86b8f9420f797f2d9e935edf629310eb938a0d839f984e25327f3c7eed22300c" dependencies = [ "memchr", ] [[package]] -name = "android_log-sys" -version = "0.3.0" +name = "allocator-api2" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "27f0fc03f560e1aebde41c2398b691cb98b5ea5996a6184a7a67bbbb77448969" +checksum = "0942ffc6dcaadf03badf6e6a2d0228460359d5e34b57ccdc720b7382dfbd5ec5" + +[[package]] +name = "android-tzdata" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" + +[[package]] +name = "android_log-sys" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ecc8056bf6ab9892dcd53216c83d1597487d7dacac16c8df6b877d127df9937" [[package]] name = "android_logger" -version = "0.13.1" +version = "0.13.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fa490e751f3878eb9accb9f18988eca52c2337ce000a8bf31ef50d4c723ca9e" +checksum = "c494134f746c14dc653a35a4ea5aca24ac368529da5370ecf41fe0341c35772f" dependencies = [ "android_log-sys", "env_logger 0.10.0", @@ -143,15 +164,15 @@ dependencies = [ [[package]] name = "anstyle" -version = "1.0.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41ed9a86bf92ae6580e0a31281f65a1b1d867c0cc68d5346e2ae128dddfa6a7d" +checksum = "3a30da5c5f2d5e72842e00bcb57657162cdabef0931f40e2deb9b4140440cecd" [[package]] name = "anstyle-parse" -version = "0.2.0" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e765fd216e48e067936442276d1d57399e37bce53c264d6fefbe298080cb57ee" +checksum = "938874ff5980b03a87c5524b3ae5b59cf99b1d6bc836848df7bc5ada9643c333" dependencies = [ "utf8parse", ] @@ -167,9 +188,9 @@ dependencies = [ [[package]] name = "anstyle-wincon" -version = "1.0.1" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "180abfa45703aebe0093f79badacc01b8fd4ea2e35118747e5811127f926e188" +checksum = "c677ab05e09154296dd37acecd46420c17b9713e8366facafa8fc0885167cf4c" dependencies = [ "anstyle", "windows-sys 0.48.0", @@ -177,18 +198,19 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.71" +version = "1.0.72" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c7d0618f0e0b7e8ff11427422b64564d5fb0be1940354bfe2e0529b18a9d9b8" +checksum = "3b13c32d80ecc7ab747b80c3784bce54ee8a7a0cc4fbda9bf4cda2cf6fe90854" [[package]] name = "argon2" -version = "0.5.0" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95c2fcf79ad1932ac6269a738109997a83c227c09b75842ae564dc8ede6a861c" +checksum = "b2e554a8638bdc1e4eae9984845306cc95f8a9208ba8d49c3859fd958b46774d" dependencies = [ "base64ct", "blake2 0.10.6", + "cpufeatures", "password-hash", ] @@ -200,15 +222,15 @@ checksum = "6b4930d2cb77ce62f89ee5d5289b4ac049559b1c45539271f5ed4fdc7db34545" [[package]] name = "arrayvec" -version = "0.7.2" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8da52d66c7071e2e3fa2a1e5c6d088fec47b593032b254f5e980de8ea54454d6" +checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711" [[package]] name = "async-channel" -version = "1.8.0" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf46fee83e5ccffc220104713af3292ff9bc7c64c7de289f66dae8e38d826833" +checksum = "81953c529336010edd6d8e358f886d9581267795c61b19475b71314bffa46d35" dependencies = [ "concurrent-queue", "event-listener", @@ -244,18 +266,18 @@ checksum = "16e62a023e7c117e27523144c5d2459f4397fcc3cab0085af8e2224f643a0193" dependencies = [ "proc-macro2", "quote", - "syn 2.0.16", + "syn 2.0.28", ] [[package]] name = "async-trait" -version = "0.1.68" +version = "0.1.73" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9ccdd8f2a161be9bd5c023df56f1b2a0bd1d83872ae53b71a84a12c9bf6e842" +checksum = "bc00ceb34980c03614e35a3a4e218276a0a824e911d07651cd0d858a51e8c0f0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.16", + "syn 2.0.28", ] [[package]] @@ -310,13 +332,13 @@ checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" [[package]] name = "axum" -version = "0.6.18" +version = "0.6.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8175979259124331c1d7bf6586ee7e0da434155e4b2d48ec2c8386281d8df39" +checksum = "3b829e4e32b91e643de6eafe82b1d90675f5874230191a4ffbc1b336dec4d6bf" dependencies = [ "async-trait", "axum-core", - "bitflags", + "bitflags 1.3.2", "bytes", "futures-util", "http", @@ -353,6 +375,21 @@ dependencies = [ "tower-service", ] +[[package]] +name = "backtrace" +version = "0.3.68" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4319208da049c43661739c5fade2ba182f09d1dc2299b32298d3a31692b17e12" +dependencies = [ + "addr2line", + "cc", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", +] + [[package]] name = "base16ct" version = "0.1.1" @@ -371,12 +408,6 @@ version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" -[[package]] -name = "base64" -version = "0.20.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ea22880d78093b0cbe17c89f64a7d457941e65759157ec6cb31a31d652b05e5" - [[package]] name = "base64" version = "0.21.2" @@ -417,7 +448,7 @@ dependencies = [ "pbkdf2", "rand_core 0.6.4", "ripemd", - "sha2 0.10.6", + "sha2 0.10.7", "subtle 2.4.1", "zeroize", ] @@ -448,6 +479,12 @@ version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" +[[package]] +name = "bitflags" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4682ae6287fcf752ecaabbfcc7b6f9b72aa33933dc23a554d853aea8eea8635" + [[package]] name = "bitvec" version = "1.0.1" @@ -483,9 +520,9 @@ dependencies = [ [[package]] name = "blake3" -version = "1.3.3" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42ae2468a89544a466886840aa467a25b766499f4f04bf7d9fcd10ecee9fccef" +checksum = "199c42ab6972d92c9f8995f086273d25c42fc0f7b2a1fcefba465c1352d25ba5" dependencies = [ "arrayref", "arrayvec", @@ -559,14 +596,14 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f5353f36341f7451062466f0b755b96ac3a9547e4d7f6b70d603fc721a7d7896" dependencies = [ - "sha2 0.10.6", + "sha2 0.10.7", ] [[package]] name = "bumpalo" -version = "3.12.2" +version = "3.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c6ed94e98ecff0c12dd1b04c15ec0d7d9458ca8fe806cea6f12954efe74c63b" +checksum = "a3e2c3daef883ecc1b5d58c15adae93470a91d425f3532ba1695849656af3fc1" [[package]] name = "byte-tools" @@ -603,11 +640,12 @@ checksum = "a2698f953def977c68f935bb0dfa959375ad4638570e969e2f1e9f433cbf1af6" [[package]] name = "cc" -version = "1.0.79" +version = "1.0.82" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f" +checksum = "305fe645edc1442a0fa8b6726ba61d422798d37a52e12eaecf4b022ebbb88f01" dependencies = [ "jobserver", + "libc", ] [[package]] @@ -658,13 +696,13 @@ dependencies = [ [[package]] name = "chrono" -version = "0.4.24" +version = "0.4.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e3c5919066adf22df73762e50cffcde3a758f2a848b113b586d1f86728b673b" +checksum = "ec837a71355b28f6556dbd569b37b3f363091c0bd4b2e735674521b4c5fd9bc5" dependencies = [ + "android-tzdata", "iana-time-zone", "js-sys", - "num-integer", "num-traits", "serde", "time 0.1.45", @@ -725,17 +763,17 @@ version = "3.2.25" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ea181bf566f71cb9a5d17a59e1871af638180a18fb0035c92ae62b705207123" dependencies = [ - "bitflags", + "bitflags 1.3.2", "clap_lex 0.2.4", - "indexmap", + "indexmap 1.9.3", "textwrap", ] [[package]] name = "clap" -version = "4.2.7" +version = "4.3.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34d21f9bf1b425d2968943631ec91202fe5e837264063503708b83013f8fc938" +checksum = "c27cdf28c0f604ba3f512b0c9a409f8de8513e4816705deb0498b627e7c3a3fd" dependencies = [ "clap_builder", "clap_derive", @@ -744,47 +782,46 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.2.7" +version = "4.3.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "914c8c79fb560f238ef6429439a30023c862f7a28e688c58f7203f12b29970bd" +checksum = "08a9f1ab5e9f01a9b81f202e8562eb9a10de70abf9eaeac1be465c28b75aa4aa" dependencies = [ "anstream", "anstyle", - "bitflags", - "clap_lex 0.4.1", + "clap_lex 0.5.0", "once_cell", "strsim", ] [[package]] name = "clap_complete" -version = "4.2.3" +version = "4.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1594fe2312ec4abf402076e407628f5c313e54c32ade058521df4ee34ecac8a8" +checksum = "5fc443334c81a804575546c5a8a79b4913b50e28d69232903604cada1de817ce" dependencies = [ - "clap 4.2.7", + "clap 4.3.21", ] [[package]] name = "clap_complete_fig" -version = "4.2.0" +version = "4.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3af28956330989baa428ed4d3471b853715d445c62de21b67292e22cf8a41fa" +checksum = "99fee1d30a51305a6c2ed3fc5709be3c8af626c9c958e04dd9ae94e27bcbce9f" dependencies = [ - "clap 4.2.7", + "clap 4.3.21", "clap_complete", ] [[package]] name = "clap_derive" -version = "4.2.0" +version = "4.3.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f9644cd56d6b87dbe899ef8b053e331c0637664e9e21a33dfcdc36093f5c5c4" +checksum = "54a9bb5758fc5dfe728d1019941681eccaf0cf8a4189b692a0ee2f2ecf90a050" dependencies = [ "heck 0.4.1", "proc-macro2", "quote", - "syn 2.0.16", + "syn 2.0.28", ] [[package]] @@ -798,9 +835,9 @@ dependencies = [ [[package]] name = "clap_lex" -version = "0.4.1" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a2dd5a6fe8c6e3502f568a6353e5273bbb15193ad9a89e457b9970798efbea1" +checksum = "2da6da31387c7e4ef160ffab6d5e7f00c42626fe39aea70a7b0f1773f7dd6c1b" [[package]] name = "cloudabi" @@ -808,7 +845,7 @@ version = "0.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f" dependencies = [ - "bitflags", + "bitflags 1.3.2", ] [[package]] @@ -819,13 +856,13 @@ checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" [[package]] name = "colored" -version = "2.0.0" +version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3616f750b84d8f0de8a58bda93e08e2a81ad3f523089b05f1dffecab48c6cbd" +checksum = "2674ec482fbc38012cf31e6c42ba0177b431a0cb6f15fe40efa5aab1bda516f6" dependencies = [ - "atty", + "is-terminal", "lazy_static", - "winapi", + "windows-sys 0.48.0", ] [[package]] @@ -840,9 +877,9 @@ dependencies = [ [[package]] name = "comfy-table" -version = "6.1.4" +version = "6.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e7b787b0dc42e8111badfdbe4c3059158ccb2db8780352fa1b01e8ccf45cc4d" +checksum = "7e959d788268e3bf9d35ace83e81b124190378e4c91c9067524675e33394b8ba" dependencies = [ "crossterm", "strum 0.24.1", @@ -873,9 +910,9 @@ dependencies = [ [[package]] name = "console-subscriber" -version = "0.1.9" +version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57ab2224a0311582eb03adba4caaf18644f7b1f10a760803a803b9b605187fc7" +checksum = "d4cf42660ac07fcebed809cfe561dd8730bcd35b075215e6479c516bcd0d11cb" dependencies = [ "console-api", "crossbeam-channel", @@ -897,31 +934,24 @@ dependencies = [ [[package]] name = "const-oid" -version = "0.9.2" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "520fbf3c07483f94e3e3ca9d0cfd913d7718ef2483d2cfd91c0d9e91474ab913" +checksum = "28c122c3980598d243d63d9a704629a2d748d101f278052ff068be5a4423ab6f" [[package]] name = "constant_time_eq" -version = "0.2.5" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13418e745008f7349ec7e449155f419a61b92b58a99cc3616942b926825ec76b" +checksum = "f7144d30dcf0fafbce74250a3963025d8d52177934239851c917d29f1df280c2" [[package]] name = "cookie" -version = "0.16.2" +version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e859cd57d0710d9e06c381b550c06e76992472a8c6d527aecd2fc673dcc231fb" +checksum = "7efb37c3e1ccb1ff97164ad95ac1606e8ccd35b3fa0a7d99a304c7f4a428cc24" dependencies = [ - "aes-gcm", - "base64 0.20.0", - "hkdf 0.12.3", - "hmac 0.12.1", "percent-encoding", - "rand 0.8.5", - "sha2 0.10.6", - "subtle 2.4.1", - "time 0.3.21", + "time 0.3.25", "version_check", ] @@ -975,9 +1005,9 @@ dependencies = [ [[package]] name = "cosmwasm-crypto" -version = "1.3.0" +version = "1.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d076a08ec01ed23c4396aca98ec73a38fa1fee5f310465add52b4108181c7a8" +checksum = "7e272708a9745dad8b591ef8a718571512130f2b39b33e3d7a27c558e3069394" dependencies = [ "digest 0.10.7", "ed25519-zebra", @@ -988,9 +1018,9 @@ dependencies = [ [[package]] name = "cosmwasm-derive" -version = "1.3.0" +version = "1.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dec361f3c09d7b41221948fc17be9b3c96cb58e55a02f82da36f888a651f2584" +checksum = "296db6a3caca5283425ae0cf347f4e46999ba3f6620dbea8939a0e00347831ce" dependencies = [ "syn 1.0.109", ] @@ -1035,7 +1065,7 @@ dependencies = [ "schemars", "serde", "serde-json-wasm", - "sha2 0.10.6", + "sha2 0.10.7", "thiserror", ] @@ -1049,9 +1079,9 @@ dependencies = [ [[package]] name = "cpufeatures" -version = "0.2.7" +version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e4c1eaa2012c47becbbad2ab175484c2a84d1185b566fb2cc5b8707343dfe58" +checksum = "a17b76ff3a4162b0b27f354a0c87015ddad39d35f9c0c36607a3bdd175dde1f1" dependencies = [ "libc", ] @@ -1154,9 +1184,9 @@ dependencies = [ [[package]] name = "crossbeam-epoch" -version = "0.9.14" +version = "0.9.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46bd5f3f85273295a9d14aedfb86f6aadbff6d8f5295c4a9edb08e819dcf5695" +checksum = "ae211234986c545741a7dc064309f67ee1e5ad243d0e48335adc0484d960bcc7" dependencies = [ "autocfg 1.1.0", "cfg-if", @@ -1177,20 +1207,20 @@ dependencies = [ [[package]] name = "crossbeam-utils" -version = "0.8.15" +version = "0.8.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c063cd8cc95f5c377ed0d4b49a4b21f632396ff690e8470c29b3359b346984b" +checksum = "5a22b2d63d4d1dc0b7f1b6b2747dd0088008a9be28b6ddf0b1e7d335e3037294" dependencies = [ "cfg-if", ] [[package]] name = "crossterm" -version = "0.25.0" +version = "0.26.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e64e6c0fbe2c17357405f7c758c1ef960fce08bdfb2c03d88d2a18d7e09c4b67" +checksum = "a84cda67535339806297f1b331d6dd6320470d2a0fe65381e79ee9e156dd3d13" dependencies = [ - "bitflags", + "bitflags 1.3.2", "crossterm_winapi", "libc", "mio", @@ -1202,9 +1232,9 @@ dependencies = [ [[package]] name = "crossterm_winapi" -version = "0.9.0" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ae1b35a484aa10e07fe0638d02301c5ad24de82d310ccbd2f3693da5f09bf1c" +checksum = "acdd7c62a3665c7f6830a51635d9ac9b23ed385797f70a83bb8bafe9c572ab2b" dependencies = [ "winapi", ] @@ -1322,15 +1352,15 @@ dependencies = [ "openssl-probe", "openssl-sys", "schannel", - "socket2", + "socket2 0.4.9", "winapi", ] [[package]] name = "curl-sys" -version = "0.4.63+curl-8.1.2" +version = "0.4.65+curl-8.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aeb0fef7046022a1e2ad67a004978f0e3cacb9e3123dc62ce768f92197b771dc" +checksum = "961ba061c9ef2fe34bbd12b807152d96f0badd2bebe7b90ce6c8c8b7572a0986" dependencies = [ "cc", "libc", @@ -1404,7 +1434,7 @@ dependencies = [ "cosmwasm-std", "cw2", "schemars", - "semver 1.0.17", + "semver 1.0.18", "serde", "thiserror", ] @@ -1511,15 +1541,15 @@ dependencies = [ [[package]] name = "dashmap" -version = "5.4.0" +version = "5.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "907076dfda823b0b36d2a1bb5f90c96660a5bbcd7729e10727f07858f22c4edc" +checksum = "6943ae99c34386c84a470c499d3414f66502a41340aa895406e0d2e4a207b91d" dependencies = [ "cfg-if", - "hashbrown 0.12.3", + "hashbrown 0.14.0", "lock_api", "once_cell", - "parking_lot_core 0.9.7", + "parking_lot_core 0.9.8", ] [[package]] @@ -1534,14 +1564,23 @@ dependencies = [ [[package]] name = "der" -version = "0.7.7" +version = "0.7.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c7ed52955ce76b1554f509074bb357d3fb8ac9b51288a65a3fd480d1dfba946" +checksum = "fffa369a668c8af7dbf8b5e56c9f744fbd399949ed171606040001947de40b1c" dependencies = [ "const-oid", "zeroize", ] +[[package]] +name = "deranged" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7684a49fb1af197853ef7b2ee694bc1f5b4179556f1e5710e1760c5db6f5e929" +dependencies = [ + "serde", +] + [[package]] name = "derivative" version = "2.2.0" @@ -1555,9 +1594,9 @@ dependencies = [ [[package]] name = "devise" -version = "0.3.1" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50c7580b072f1c8476148f16e0a0d5dedddab787da98d86c5082c5e9ed8ab595" +checksum = "d6eacefd3f541c66fc61433d65e54e0e46e0a029a819a7dbbc7a7b489e8a85f8" dependencies = [ "devise_codegen", "devise_core", @@ -1565,9 +1604,9 @@ dependencies = [ [[package]] name = "devise_codegen" -version = "0.3.1" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "123c73e7a6e51b05c75fe1a1b2f4e241399ea5740ed810b0e3e6cacd9db5e7b2" +checksum = "9c8cf4b8dd484ede80fd5c547592c46c3745a617c8af278e2b72bea86b2dfed6" dependencies = [ "devise_core", "quote", @@ -1575,15 +1614,15 @@ dependencies = [ [[package]] name = "devise_core" -version = "0.3.1" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "841ef46f4787d9097405cac4e70fb8644fc037b526e8c14054247c0263c400d0" +checksum = "35b50dba0afdca80b187392b24f2499a88c336d5a8493e4b4ccfb608708be56a" dependencies = [ - "bitflags", + "bitflags 2.4.0", "proc-macro2", - "proc-macro2-diagnostics 0.9.1", + "proc-macro2-diagnostics", "quote", - "syn 1.0.109", + "syn 2.0.28", ] [[package]] @@ -1677,9 +1716,9 @@ checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b" [[package]] name = "dyn-clone" -version = "1.0.11" +version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68b0cf012f1230e43cd00ebb729c6bb58707ecfa8ad08b52ef3a4ccd2697fc30" +checksum = "304e6508efa593091e97a9abbc10f90aa7ca635b6d2784feff3c89d41dd12272" [[package]] name = "ecdsa" @@ -1699,7 +1738,7 @@ version = "0.16.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a4b1e0c257a9e9f25f90ff76d7a68360ed497ee519c8e428d1825ef0000799d4" dependencies = [ - "der 0.7.7", + "der 0.7.8", "digest 0.10.7", "elliptic-curve 0.13.5", "rfc6979 0.4.0", @@ -1719,9 +1758,9 @@ dependencies = [ [[package]] name = "ed25519" -version = "2.2.1" +version = "2.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fb04eee5d9d907f29e80ee6b0e78f7e2c82342c63e3580d8c4f69d9d5aad963" +checksum = "60f6d271ca33075c88028be6f04d502853d63a5ece419d269c15315d4fc1cf1d" dependencies = [ "pkcs8 0.10.2", "signature 2.1.0", @@ -1772,9 +1811,9 @@ dependencies = [ [[package]] name = "either" -version = "1.8.1" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91" +checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" [[package]] name = "elliptic-curve" @@ -1841,7 +1880,7 @@ checksum = "eecf8589574ce9b895052fa12d69af7a233f99e6107f5cb8dd1044f2a17bfdcb" dependencies = [ "proc-macro2", "quote", - "syn 2.0.16", + "syn 2.0.28", ] [[package]] @@ -1868,10 +1907,16 @@ dependencies = [ ] [[package]] -name = "errno" -version = "0.3.1" +name = "equivalent" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4bcfec3a70f97c962c307b2d2c56e358cf1d00b558d74262b5f929ee8cc7e73a" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + +[[package]] +name = "errno" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b30f669a7961ef1631673d2766cc92f52d64f7ef354d4fe0ddfd30ed52f0f4f" dependencies = [ "errno-dragonfly", "libc", @@ -1899,7 +1944,7 @@ name = "explorer-api" version = "1.1.24" dependencies = [ "chrono", - "clap 4.2.7", + "clap 4.3.21", "dotenvy", "humantime-serde", "isocountry", @@ -1978,6 +2023,12 @@ dependencies = [ "instant", ] +[[package]] +name = "fastrand" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6999dc1837253364c2ebb0704ba97994bd874e8f195d665c50b7548f6ea92764" + [[package]] name = "ff" version = "0.10.1" @@ -2020,27 +2071,27 @@ dependencies = [ [[package]] name = "figment" -version = "0.10.8" +version = "0.10.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e56602b469b2201400dec66a66aec5a9b8761ee97cd1b8c96ab2483fcc16cc9" +checksum = "4547e226f4c9ab860571e070a9034192b3175580ecea38da34fcdb53a018c9a5" dependencies = [ "atomic", "pear", "serde", - "toml 0.5.11", + "toml 0.7.6", "uncased", "version_check", ] [[package]] name = "filetime" -version = "0.2.21" +version = "0.2.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5cbc844cecaee9d4443931972e1289c8ff485cb4cc2767cb03ca139ed6885153" +checksum = "d4029edd3e734da6fe05b6cd7bd2960760a616bd2ddd0d59a0124746d6272af0" dependencies = [ "cfg-if", "libc", - "redox_syscall 0.2.16", + "redox_syscall 0.3.5", "windows-sys 0.48.0", ] @@ -2099,9 +2150,9 @@ checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" [[package]] name = "form_urlencoded" -version = "1.1.0" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9c384f161156f5260c24a097c56119f9be8c798586aecc13afbcbe7b7e26bf8" +checksum = "a62bc1cf6f830c2ec14a513a9fb124d0a213a629668a4186f329db21fe045652" dependencies = [ "percent-encoding", ] @@ -2198,7 +2249,7 @@ version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49a9d51ce47660b1e808d3c990b4709f2f415d928835a17dfd16991515c46bce" dependencies = [ - "fastrand", + "fastrand 1.9.0", "futures-core", "futures-io", "memchr", @@ -2215,7 +2266,7 @@ checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72" dependencies = [ "proc-macro2", "quote", - "syn 2.0.16", + "syn 2.0.28", ] [[package]] @@ -2256,9 +2307,9 @@ checksum = "8f5f3913fa0bfe7ee1fd8248b6b9f42a5af4b9d65ec2dd2c3c26132b950ecfc2" [[package]] name = "generator" -version = "0.7.4" +version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3e123d9ae7c02966b4d892e550bdc32164f05853cd40ab570650ad600596a8a" +checksum = "5cc16584ff22b460a382b7feec54b23d2908d858152e5739a120b949293bd74e" dependencies = [ "cc", "libc", @@ -2338,22 +2389,28 @@ dependencies = [ [[package]] name = "ghost" -version = "0.1.9" +version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e77ac7b51b8e6313251737fcef4b1c01a2ea102bde68415b62c0ee9268fec357" +checksum = "ba330b70a5341d3bc730b8e205aaee97ddab5d9c448c4f51a7c2d924266fa8f9" dependencies = [ "proc-macro2", "quote", - "syn 2.0.16", + "syn 2.0.28", ] +[[package]] +name = "gimli" +version = "0.27.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c80984affa11d98d1b88b66ac8853f143217b399d3c74116778ff8fdb4ed2e" + [[package]] name = "git2" version = "0.14.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d0155506aab710a86160ddb504a480d2964d7ab5b9e62419be69e0032bc5931c" dependencies = [ - "bitflags", + "bitflags 1.3.2", "libc", "libgit2-sys", "log", @@ -2426,9 +2483,9 @@ dependencies = [ [[package]] name = "h2" -version = "0.3.19" +version = "0.3.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d357c7ae988e7d2182f7d7871d0b963962420b0678b0997ce7de72001aeab782" +checksum = "97ec8491ebaf99c8eaa73058b045fe58073cd6be7f596ac993ced0b0a0c01049" dependencies = [ "bytes", "fnv", @@ -2436,7 +2493,7 @@ dependencies = [ "futures-sink", "futures-util", "http", - "indexmap", + "indexmap 1.9.3", "slab", "tokio", "tokio-util", @@ -2483,11 +2540,12 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.13.2" +version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e" +checksum = "2c6201b9ff9fd90a5a3bac2e56a830d0caa509576f0e503818ee82c181b3437a" dependencies = [ "ahash 0.8.3", + "allocator-api2", ] [[package]] @@ -2501,11 +2559,11 @@ dependencies = [ [[package]] name = "hashlink" -version = "0.8.2" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0761a1b9491c4f2e3d66aa0f62d0fba0af9a0e2852e4d48ea506632a4b56e6aa" +checksum = "312f66718a2d7789ffef4f4b7b213138ed9f1eb3aa1d0d82fc99f88fb3ffd26f" dependencies = [ - "hashbrown 0.13.2", + "hashbrown 0.14.0", ] [[package]] @@ -2528,7 +2586,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f3e372db8e5c0d213e0cd0b9be18be2aca3d44cf2fe30a9d46a65581cd454584" dependencies = [ "base64 0.13.1", - "bitflags", + "bitflags 1.3.2", "bytes", "headers-core", "http", @@ -2575,18 +2633,9 @@ dependencies = [ [[package]] name = "hermit-abi" -version = "0.2.6" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee512640fe35acbfb4bb779db6f0d80704c2cacfa2e39b601ef3e3f47d1ae4c7" -dependencies = [ - "libc", -] - -[[package]] -name = "hermit-abi" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fed44880c466736ef9a5c5b5facefb5ed0785676d0c02d612db14e54f0d84286" +checksum = "443144c8cdadd93ebf52ddb4056d257f5b52c04d3c804e657d19eb73fc33668b" [[package]] name = "hex" @@ -2680,9 +2729,9 @@ checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" [[package]] name = "httpdate" -version = "1.0.2" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" [[package]] name = "humantime" @@ -2711,9 +2760,9 @@ dependencies = [ [[package]] name = "hyper" -version = "0.14.26" +version = "0.14.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab302d72a6f11a3b910431ff93aae7e773078c769f0a3ef15fb9ec692ed147d4" +checksum = "ffb1cfd654a8219eaef89881fdb3bb3b1cdc5fa75ded05d6933b2b382e395468" dependencies = [ "bytes", "futures-channel", @@ -2726,7 +2775,7 @@ dependencies = [ "httpdate", "itoa", "pin-project-lite", - "socket2", + "socket2 0.4.9", "tokio", "tower-service", "tracing", @@ -2797,9 +2846,9 @@ dependencies = [ [[package]] name = "iana-time-zone" -version = "0.1.56" +version = "0.1.57" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0722cd7114b7de04316e7ea5456a0bbb20e4adb46fd27a3697adb812cff0f37c" +checksum = "2fad5b825842d2b38bd206f3e81d6957625fd7f0a361e345c30e01a0ae2dd613" dependencies = [ "android_system_properties", "core-foundation-sys", @@ -2837,9 +2886,9 @@ dependencies = [ [[package]] name = "idna" -version = "0.3.0" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e14ddfc70884202db2244c223200c204c2bda1bc6e0998d11b5e024d657209e6" +checksum = "7d20d6b07bfbc108882d88ed8e37d39636dcc260e15e30c45e6ba089610b917c" dependencies = [ "unicode-bidi", "unicode-normalization", @@ -2859,7 +2908,7 @@ checksum = "bfbcff6ae46750b15cc594bfd277b188cbddcfdc1817848f97f03f26f8625b9e" dependencies = [ "cfg-if", "js-sys", - "uuid 1.3.3", + "uuid 1.4.1", "wasm-bindgen", "wasm-bindgen-futures", "web-sys", @@ -2876,6 +2925,16 @@ dependencies = [ "serde", ] +[[package]] +name = "indexmap" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5477fe2230a79769d8dc68e0eabf5437907c0457a5614a9e8dddb67f65eb65d" +dependencies = [ + "equivalent", + "hashbrown 0.14.0", +] + [[package]] name = "inlinable_string" version = "0.1.15" @@ -2888,7 +2947,7 @@ version = "0.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8069d3ec154eb856955c1c0fbffefbf5f3c40a104ec912d4797314c1801abff" dependencies = [ - "bitflags", + "bitflags 1.3.2", "inotify-sys", "libc", ] @@ -2957,22 +3016,11 @@ dependencies = [ "syn 1.0.109", ] -[[package]] -name = "io-lifetimes" -version = "1.0.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c66c74d2ae7e79a5a8f7ac924adbe38ee42a859c6539ad869eb51f0b52dc220" -dependencies = [ - "hermit-abi 0.3.1", - "libc", - "windows-sys 0.48.0", -] - [[package]] name = "ipnet" -version = "2.7.2" +version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12b6ee2129af8d4fb011108c73d99a1b83a85977f23b82460c0ae2e25bb4b57f" +checksum = "28b29a3cd74f0f4598934efe3aeba42bae0eb4680554128851ebbecb02af14e6" [[package]] name = "ipnetwork" @@ -2994,12 +3042,11 @@ dependencies = [ [[package]] name = "is-terminal" -version = "0.4.7" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adcf93614601c8129ddf72e2d5633df827ba6551541c6d8c59520a371475be1f" +checksum = "cb0889898416213fab133e1d33a0e5858a48177452750691bde3666d0fdbaf8b" dependencies = [ - "hermit-abi 0.3.1", - "io-lifetimes", + "hermit-abi 0.3.2", "rustix", "windows-sys 0.48.0", ] @@ -3050,9 +3097,9 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.6" +version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "453ad9f582a441959e5f0d088b02ce04cfe8d51a8eaf077f12ac6d3e94164ca6" +checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" [[package]] name = "jni" @@ -3103,7 +3150,7 @@ dependencies = [ "cfg-if", "ecdsa 0.14.8", "elliptic-curve 0.12.3", - "sha2 0.10.6", + "sha2 0.10.7", ] [[package]] @@ -3116,7 +3163,7 @@ dependencies = [ "ecdsa 0.16.8", "elliptic-curve 0.13.5", "once_cell", - "sha2 0.10.6", + "sha2 0.10.7", "signature 2.1.0", ] @@ -3128,9 +3175,9 @@ checksum = "c33070833c9ee02266356de0c43f723152bd38bd96ddf52c82b3af10c9138b28" [[package]] name = "kqueue" -version = "1.0.7" +version = "1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c8fc60ba15bf51257aa9807a48a61013db043fcf3a78cb0d916e8e396dcad98" +checksum = "7447f1ca1b7b563588a205fe93dea8df60fd981423a768bc1c0ded35ed147d0c" dependencies = [ "kqueue-sys", "libc", @@ -3138,11 +3185,11 @@ dependencies = [ [[package]] name = "kqueue-sys" -version = "1.0.3" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8367585489f01bc55dd27404dcf56b95e6da061a256a666ab23be9ba96a2e587" +checksum = "ed9625ffda8729b85e45cf04090035ac368927b8cebc34898e7c120f52e4838b" dependencies = [ - "bitflags", + "bitflags 1.3.2", "libc", ] @@ -3202,9 +3249,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.144" +version = "0.2.147" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b00cc1c228a6782d0f076e7b232802e0c5689d41bb5df366f2a6b6621cfdfe1" +checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3" [[package]] name = "libgit2-sys" @@ -3237,9 +3284,9 @@ dependencies = [ [[package]] name = "libz-sys" -version = "1.1.9" +version = "1.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56ee889ecc9568871456d42f603d6a0ce59ff328d291063a45cbdf0036baf6db" +checksum = "d97137b25e321a73eef1418d1d5d2eda4d77e12813f8e6dead84bc52c5870a7b" dependencies = [ "cc", "libc", @@ -3249,9 +3296,9 @@ dependencies = [ [[package]] name = "linux-raw-sys" -version = "0.3.7" +version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ece97ea872ece730aed82664c424eb4c8291e1ff2480247ccf7409044bc6479f" +checksum = "57bcfdad1b858c2db7c38303a6d2ad4dfaf5eb53dfeb0910128b2c26d6158503" [[package]] name = "lioness" @@ -3267,9 +3314,9 @@ dependencies = [ [[package]] name = "lock_api" -version = "0.4.9" +version = "0.4.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "435011366fe56583b16cf956f9df0095b405b82d76425bc8981c0e22e60ec4df" +checksum = "c1cc9717a20b1bb222f333e6a92fd32f7d8a18ddc5a3191a11af45dcbf4dcd16" dependencies = [ "autocfg 1.1.0", "scopeguard", @@ -3277,12 +3324,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.17" +version = "0.4.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" -dependencies = [ - "cfg-if", -] +checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" [[package]] name = "loom" @@ -3321,7 +3365,7 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558" dependencies = [ - "regex-automata", + "regex-automata 0.1.10", ] [[package]] @@ -3332,9 +3376,9 @@ checksum = "2532096657941c2fea9c289d370a250971c689d4f143798ff67113ec042024a5" [[package]] name = "matchit" -version = "0.7.0" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b87248edafb776e59e6ee64a79086f65890d3510f2c656c000bf2a7e8a0aea40" +checksum = "ed1202b2a6f884ae56f04cff409ab315c5ce26b5e58d7412e484f01fd52f52ef" [[package]] name = "maxminddb" @@ -3356,9 +3400,9 @@ checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" [[package]] name = "memoffset" -version = "0.8.0" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d61c719bcfbcf5d62b3a09efa6088de8c54bc0bfcd3ea7ae39fcc186108b8de1" +checksum = "5a634b1c61a95585bd15607c6ab0c4e5b226e695ff2800ba0cdccddf208c406c" dependencies = [ "autocfg 1.1.0", ] @@ -3386,14 +3430,14 @@ dependencies = [ [[package]] name = "mio" -version = "0.8.6" +version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b9d9a46eff5b4ff64b45a9e316a6d1e0bc719ef429cbec4dc630684212bfdf9" +checksum = "927a765cd3fc26206e66b296465fa9d3e5ab003e651c1b3c060e7956d96b19d2" dependencies = [ "libc", "log", "wasi 0.11.0+wasi-snapshot-preview1", - "windows-sys 0.45.0", + "windows-sys 0.48.0", ] [[package]] @@ -3456,7 +3500,7 @@ version = "5.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "729f63e1ca555a43fe3efa4f3efdf4801c479da85b432242a7b726f353c88486" dependencies = [ - "bitflags", + "bitflags 1.3.2", "crossbeam-channel", "filetime", "fsevent-sys", @@ -3498,21 +3542,11 @@ dependencies = [ "syn 1.0.109", ] -[[package]] -name = "num-integer" -version = "0.1.45" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" -dependencies = [ - "autocfg 1.1.0", - "num-traits", -] - [[package]] name = "num-traits" -version = "0.2.15" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" +checksum = "f30b0abd723be7e2ffca1272140fac1a2f084c77ec3e123c192b66af1ee9e6c2" dependencies = [ "autocfg 1.1.0", "libm", @@ -3520,11 +3554,11 @@ dependencies = [ [[package]] name = "num_cpus" -version = "1.15.0" +version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fac9e2da13b5eb447a6ce3d392f23a29d8694bff781bf03a16cd9ac8697593b" +checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" dependencies = [ - "hermit-abi 0.2.6", + "hermit-abi 0.3.2", "libc", ] @@ -3537,7 +3571,7 @@ dependencies = [ "bip39", "bs58 0.4.0", "cfg-if", - "clap 4.2.7", + "clap 4.3.21", "console-subscriber", "cosmwasm-std", "cw-utils", @@ -3590,7 +3624,7 @@ dependencies = [ "sqlx 0.6.3", "tap", "thiserror", - "time 0.3.21", + "time 0.3.25", "tokio", "tokio-stream", "ts-rs", @@ -3634,7 +3668,7 @@ name = "nym-bin-common" version = "0.6.0" dependencies = [ "atty", - "clap 4.2.7", + "clap 4.3.21", "clap_complete", "clap_complete_fig", "log", @@ -3673,7 +3707,7 @@ dependencies = [ "base64 0.13.1", "bip39", "bs58 0.4.0", - "clap 4.2.7", + "clap 4.3.21", "clap_complete", "clap_complete_fig", "dotenvy", @@ -3697,7 +3731,7 @@ dependencies = [ "bip39", "bs58 0.4.0", "cfg-if", - "clap 4.2.7", + "clap 4.3.21", "comfy-table", "cosmrs", "cosmwasm-std", @@ -3726,7 +3760,7 @@ dependencies = [ "serde_json", "tap", "thiserror", - "time 0.3.21", + "time 0.3.25", "toml 0.5.11", "url", ] @@ -3735,7 +3769,7 @@ dependencies = [ name = "nym-client" version = "1.1.24" dependencies = [ - "clap 4.2.7", + "clap 4.3.21", "dirs 4.0.0", "futures", "lazy_static", @@ -3773,7 +3807,7 @@ version = "1.1.15" dependencies = [ "async-trait", "base64 0.21.2", - "dashmap 5.4.0", + "dashmap 5.5.0", "dirs 4.0.0", "futures", "gloo-timers", @@ -3797,12 +3831,12 @@ dependencies = [ "reqwest", "serde", "serde_json", - "sha2 0.10.6", + "sha2 0.10.7", "sqlx 0.6.3", "tap", "tempfile", "thiserror", - "time 0.3.21", + "time 0.3.25", "tokio", "tokio-stream", "tokio-tungstenite 0.14.0", @@ -3888,7 +3922,7 @@ dependencies = [ "log", "nym-network-defaults", "serde", - "toml 0.7.4", + "toml 0.7.6", "url", ] @@ -3909,7 +3943,7 @@ dependencies = [ name = "nym-credential-client" version = "0.1.0" dependencies = [ - "clap 4.2.7", + "clap 4.3.21", "log", "nym-bandwidth-controller", "nym-bin-common", @@ -3954,7 +3988,7 @@ dependencies = [ name = "nym-crypto" version = "0.4.0" dependencies = [ - "aes 0.8.2", + "aes 0.8.3", "blake3", "bs58 0.4.0", "cipher 0.4.4", @@ -4028,7 +4062,7 @@ dependencies = [ "atty", "bip39", "bs58 0.4.0", - "clap 4.2.7", + "clap 4.3.21", "colored", "dashmap 4.0.2", "dirs 4.0.0", @@ -4170,7 +4204,7 @@ dependencies = [ "serde-json-wasm", "serde_repr", "thiserror", - "time 0.3.21", + "time 0.3.25", "ts-rs", ] @@ -4181,7 +4215,7 @@ dependencies = [ "anyhow", "bs58 0.4.0", "cfg-if", - "clap 4.2.7", + "clap 4.3.21", "colored", "cpu-cycles", "cupid", @@ -4299,7 +4333,7 @@ dependencies = [ "async-file-watcher", "async-trait", "bs58 0.4.0", - "clap 4.2.7", + "clap 4.3.21", "dirs 4.0.0", "futures", "humantime-serde", @@ -4389,7 +4423,7 @@ name = "nym-nr-query" version = "0.1.0" dependencies = [ "anyhow", - "clap 4.2.7", + "clap 4.3.21", "log", "nym-bin-common", "nym-network-defaults", @@ -4417,7 +4451,7 @@ dependencies = [ "chacha20poly1305", "criterion", "curve25519-dalek", - "fastrand", + "fastrand 1.9.0", "getrandom 0.2.10", "log", "rand 0.7.3", @@ -4499,7 +4533,7 @@ dependencies = [ name = "nym-socks5-client" version = "1.1.24" dependencies = [ - "clap 4.2.7", + "clap 4.3.21", "lazy_static", "log", "nym-bin-common", @@ -4918,6 +4952,15 @@ dependencies = [ "ts-rs", ] +[[package]] +name = "object" +version = "0.31.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8bda667d9f2b5051b8833f59f3bf748b28ef54f850f4fcb389a252aa383866d1" +dependencies = [ + "memchr", +] + [[package]] name = "okapi" version = "0.7.0-rc.1" @@ -4932,9 +4975,9 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.17.1" +version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3" +checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" [[package]] name = "oorandom" @@ -4956,11 +4999,11 @@ checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" [[package]] name = "openssl" -version = "0.10.52" +version = "0.10.56" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01b8574602df80f7b85fdfc5392fa884a4e3b3f4f35402c070ab34c3d3f78d56" +checksum = "729b745ad4a5575dd06a3e1af1414bd330ee561c01b3899eb584baeaa8def17e" dependencies = [ - "bitflags", + "bitflags 1.3.2", "cfg-if", "foreign-types", "libc", @@ -4977,7 +5020,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.16", + "syn 2.0.28", ] [[package]] @@ -4988,18 +5031,18 @@ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" [[package]] name = "openssl-src" -version = "111.25.3+1.1.1t" +version = "111.27.0+1.1.1v" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "924757a6a226bf60da5f7dd0311a34d2b52283dd82ddeb103208ddc66362f80c" +checksum = "06e8f197c82d7511c5b014030c9b1efeda40d7d5f99d23b4ceed3524a5e63f02" dependencies = [ "cc", ] [[package]] name = "openssl-sys" -version = "0.9.87" +version = "0.9.91" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e17f59264b2809d77ae94f0e1ebabc434773f370d6ca667bd223ea10e06cc7e" +checksum = "866b5f16f90776b9bb8dc1e1802ac6f0513de3a7a7465867bfbc563dc737faac" dependencies = [ "cc", "libc", @@ -5069,7 +5112,7 @@ dependencies = [ "fnv", "futures-channel", "futures-util", - "indexmap", + "indexmap 1.9.3", "once_cell", "pin-project-lite", "thiserror", @@ -5084,7 +5127,7 @@ checksum = "8b3a2a91fdbfdd4d212c0dcc2ab540de2c2bcbbd90be17de7a7daf8822d010c1" dependencies = [ "async-trait", "crossbeam-channel", - "dashmap 5.4.0", + "dashmap 5.5.0", "fnv", "futures-channel", "futures-executor", @@ -5115,9 +5158,9 @@ dependencies = [ [[package]] name = "os_str_bytes" -version = "6.5.0" +version = "6.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ceedf44fb00f2d1984b0bc98102627ce622e083e49a5bacdb3e514fa4238e267" +checksum = "4d5d9eb14b174ee9aa2ef96dc2b94637a2d4b6e7cb873c7e171f0c20c6cf3eac" [[package]] name = "overload" @@ -5167,7 +5210,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" dependencies = [ "lock_api", - "parking_lot_core 0.9.7", + "parking_lot_core 0.9.8", ] [[package]] @@ -5186,15 +5229,15 @@ dependencies = [ [[package]] name = "parking_lot_core" -version = "0.9.7" +version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9069cbb9f99e3a5083476ccb29ceb1de18b9118cafa53e90c9551235de2b9521" +checksum = "93f00c865fe7cabf650081affecd3871070f26767e7b2070a3ffae14c654b447" dependencies = [ "cfg-if", "libc", - "redox_syscall 0.2.16", + "redox_syscall 0.3.5", "smallvec", - "windows-sys 0.45.0", + "windows-targets 0.48.1", ] [[package]] @@ -5210,9 +5253,9 @@ dependencies = [ [[package]] name = "paste" -version = "1.0.12" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f746c4065a8fa3fe23974dd82f15431cc8d40779821001404d10d2e79ca7d79" +checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c" [[package]] name = "pbkdf2" @@ -5226,25 +5269,25 @@ dependencies = [ [[package]] name = "pear" -version = "0.2.4" +version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ec95680a7087503575284e5063e14b694b7a9c0b065e5dceec661e0497127e8" +checksum = "61a386cd715229d399604b50d1361683fe687066f42d56f54be995bc6868f71c" dependencies = [ "inlinable_string", "pear_codegen", - "yansi", + "yansi 1.0.0-rc.1", ] [[package]] name = "pear_codegen" -version = "0.2.4" +version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9661a3a53f93f09f2ea882018e4d7c88f6ff2956d809a276060476fd8c879d3c" +checksum = "da9f0f13dac8069c139e8300a6510e3f4143ecf5259c60b116a9b271b4ca0d54" dependencies = [ "proc-macro2", - "proc-macro2-diagnostics 0.10.0", + "proc-macro2-diagnostics", "quote", - "syn 2.0.16", + "syn 2.0.28", ] [[package]] @@ -5287,15 +5330,15 @@ dependencies = [ [[package]] name = "percent-encoding" -version = "2.2.0" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e" +checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94" [[package]] name = "pest" -version = "2.6.0" +version = "2.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e68e84bfb01f0507134eac1e9b410a12ba379d064eab48c50ba4ce329a527b70" +checksum = "1acb4a4365a13f749a93f1a094a7805e5cfa0955373a9de860d962eaa3a5fe5a" dependencies = [ "thiserror", "ucd-trie", @@ -5303,9 +5346,9 @@ dependencies = [ [[package]] name = "pest_derive" -version = "2.6.0" +version = "2.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b79d4c71c865a25a4322296122e3924d30bc8ee0834c8bfc8b95f7f054afbfb" +checksum = "666d00490d4ac815001da55838c500eafb0320019bbaa44444137c48b443a853" dependencies = [ "pest", "pest_generator", @@ -5313,53 +5356,53 @@ dependencies = [ [[package]] name = "pest_generator" -version = "2.6.0" +version = "2.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c435bf1076437b851ebc8edc3a18442796b30f1728ffea6262d59bbe28b077e" +checksum = "68ca01446f50dbda87c1786af8770d535423fa8a53aec03b8f4e3d7eb10e0929" dependencies = [ "pest", "pest_meta", "proc-macro2", "quote", - "syn 2.0.16", + "syn 2.0.28", ] [[package]] name = "pest_meta" -version = "2.6.0" +version = "2.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "745a452f8eb71e39ffd8ee32b3c5f51d03845f99786fa9b68db6ff509c505411" +checksum = "56af0a30af74d0445c0bf6d9d051c979b516a1a5af790d251daee76005420a48" dependencies = [ "once_cell", "pest", - "sha2 0.10.6", + "sha2 0.10.7", ] [[package]] name = "pin-project" -version = "1.1.0" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c95a7476719eab1e366eaf73d0260af3021184f18177925b07f54b30089ceead" +checksum = "fda4ed1c6c173e3fc7a83629421152e01d7b1f9b7f65fb301e490e8cfc656422" dependencies = [ "pin-project-internal", ] [[package]] name = "pin-project-internal" -version = "1.1.0" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39407670928234ebc5e6e580247dd567ad73a3578460c5990f9503df207e8f07" +checksum = "4359fd9c9171ec6e8c62926d6faaf553a8dc3f64e1507e76da7911b4f6a04405" dependencies = [ "proc-macro2", "quote", - "syn 2.0.16", + "syn 2.0.28", ] [[package]] name = "pin-project-lite" -version = "0.2.9" +version = "0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" +checksum = "12cc1b0bf1727a77a54b6654e7b5f1af8604923edc8b81885f8ec92f9e3f0a05" [[package]] name = "pin-utils" @@ -5383,7 +5426,7 @@ version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" dependencies = [ - "der 0.7.7", + "der 0.7.8", "spki 0.7.2", ] @@ -5395,9 +5438,9 @@ checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964" [[package]] name = "plotters" -version = "0.3.4" +version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2538b639e642295546c50fcd545198c9d64ee2a38620a628724a3b266d5fbf97" +checksum = "d2c224ba00d7cadd4d5c660deaf2098e5e80e07846537c51f9cfa4be50c1fd45" dependencies = [ "num-traits", "plotters-backend", @@ -5408,15 +5451,15 @@ dependencies = [ [[package]] name = "plotters-backend" -version = "0.3.4" +version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "193228616381fecdc1224c62e96946dfbc73ff4384fba576e052ff8c1bea8142" +checksum = "9e76628b4d3a7581389a35d5b6e2139607ad7c75b17aed325f210aa91f4a9609" [[package]] name = "plotters-svg" -version = "0.3.3" +version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9a81d2759aae1dae668f783c308bc5c8ebd191ff4184aaa1b37f65a6ae5a56f" +checksum = "38f6d39893cca0701371e3c27294f09797214b86f1fb951b89ade8ec04e2abab" dependencies = [ "plotters-backend", ] @@ -5428,7 +5471,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4b2d323e8ca7996b3e23126511a523f7e62924d93ecd5ae73b333815b0eb3dce" dependencies = [ "autocfg 1.1.0", - "bitflags", + "bitflags 1.3.2", "cfg-if", "concurrent-queue", "libc", @@ -5450,9 +5493,9 @@ dependencies = [ [[package]] name = "polyval" -version = "0.6.0" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ef234e08c11dfcb2e56f79fd70f6f2eb7f025c0ce2333e82f4f0518ecad30c6" +checksum = "d52cff9d1d4dee5fe6d03729099f4a310a41179e0a10dbf542039873f2e826fb" dependencies = [ "cfg-if", "cpufeatures", @@ -5512,37 +5555,24 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.58" +version = "1.0.66" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa1fb82fc0c281dd9671101b66b771ebbe1eaf967b96ac8740dcba4b70005ca8" +checksum = "18fb31db3f9bddb2ea821cde30a9f70117e3f119938b5ee630b7403aa6e2ead9" dependencies = [ "unicode-ident", ] [[package]] name = "proc-macro2-diagnostics" -version = "0.9.1" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4bf29726d67464d49fa6224a1d07936a8c08bb3fba727c7493f6cf1616fdaada" +checksum = "af066a9c399a26e020ada66a034357a868728e72cd426f3adcd35f80d88d88c8" dependencies = [ "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.28", "version_check", - "yansi", -] - -[[package]] -name = "proc-macro2-diagnostics" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "606c4ba35817e2922a308af55ad51bab3645b59eae5c570d4a6cf07e36bd493b" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.16", - "version_check", - "yansi", + "yansi 1.0.0-rc.1", ] [[package]] @@ -5602,9 +5632,9 @@ checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3" [[package]] name = "quote" -version = "1.0.27" +version = "1.0.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f4f29d145265ec1c483c7c654450edde0bfe043d3938d6972630663356d9500" +checksum = "50f3b39ccfb720540debaa0164757101c08ecb8d326b15358ce76a62c7e85965" dependencies = [ "proc-macro2", ] @@ -5857,7 +5887,7 @@ version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" dependencies = [ - "bitflags", + "bitflags 1.3.2", ] [[package]] @@ -5866,7 +5896,7 @@ version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29" dependencies = [ - "bitflags", + "bitflags 1.3.2", ] [[package]] @@ -5882,33 +5912,34 @@ dependencies = [ [[package]] name = "ref-cast" -version = "1.0.16" +version = "1.0.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f43faa91b1c8b36841ee70e97188a869d37ae21759da6846d4be66de5bf7b12c" +checksum = "61ef7e18e8841942ddb1cf845054f8008410030a3997875d9e49b7a363063df1" dependencies = [ "ref-cast-impl", ] [[package]] name = "ref-cast-impl" -version = "1.0.16" +version = "1.0.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d2275aab483050ab2a7364c1a46604865ee7d6906684e08db0f090acf74f9e7" +checksum = "2dfaf0c85b766276c797f3791f5bc6d5bd116b41d53049af2789666b0c0bc9fa" dependencies = [ "proc-macro2", "quote", - "syn 2.0.16", + "syn 2.0.28", ] [[package]] name = "regex" -version = "1.8.4" +version = "1.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0ab3ca65655bb1e41f2a8c8cd662eb4fb035e67c3f78da1d61dffe89d07300f" +checksum = "81bc1d4caf89fac26a70747fe603c130093b53c773888797a6329091246d651a" dependencies = [ "aho-corasick", "memchr", - "regex-syntax 0.7.2", + "regex-automata 0.3.6", + "regex-syntax 0.7.4", ] [[package]] @@ -5920,6 +5951,17 @@ dependencies = [ "regex-syntax 0.6.29", ] +[[package]] +name = "regex-automata" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fed1ceff11a1dddaee50c9dc8e4938bd106e9d89ae372f192311e7da498e3b69" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax 0.7.4", +] + [[package]] name = "regex-syntax" version = "0.6.29" @@ -5928,9 +5970,9 @@ checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" [[package]] name = "regex-syntax" -version = "0.7.2" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "436b050e76ed2903236f032a59761c1eb99e1b0aead2c257922771dab1fc8c78" +checksum = "e5ea92a5b6195c6ef2a0295ea818b312502c6fc94dde986c5553242e18fd4ce2" [[package]] name = "reqwest" @@ -6017,20 +6059,20 @@ dependencies = [ [[package]] name = "rocket" -version = "0.5.0-rc.2" +version = "0.5.0-rc.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "98ead083fce4a405feb349cf09abdf64471c6077f14e0ce59364aa90d4b99317" +checksum = "58734f7401ae5cfd129685b48f61182331745b357b96f2367f01aebaf1cc9cc9" dependencies = [ "async-stream", "async-trait", "atomic", - "atty", "binascii", "bytes", "either", "figment", "futures", - "indexmap", + "indexmap 1.9.3", + "is-terminal", "log", "memchr", "multer", @@ -6045,28 +6087,28 @@ dependencies = [ "serde_json", "state", "tempfile", - "time 0.3.21", + "time 0.3.25", "tokio", "tokio-stream", "tokio-util", "ubyte", "version_check", - "yansi", + "yansi 0.5.1", ] [[package]] name = "rocket_codegen" -version = "0.5.0-rc.2" +version = "0.5.0-rc.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6aeb6bb9c61e9cd2c00d70ea267bf36f76a4cc615e5908b349c2f9d93999b47" +checksum = "7093353f14228c744982e409259fb54878ba9563d08214f2d880d59ff2fc508b" dependencies = [ "devise", "glob", - "indexmap", + "indexmap 1.9.3", "proc-macro2", "quote", "rocket_http", - "syn 1.0.109", + "syn 2.0.28", "unicode-xid", ] @@ -6087,16 +6129,16 @@ dependencies = [ [[package]] name = "rocket_http" -version = "0.5.0-rc.2" +version = "0.5.0-rc.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ded65d127954de3c12471630bf4b81a2792f065984461e65b91d0fdaafc17a2" +checksum = "936012c99162a03a67f37f9836d5f938f662e26f2717809761a9ac46432090f4" dependencies = [ "cookie", "either", "futures", "http", "hyper", - "indexmap", + "indexmap 1.9.3", "log", "memchr", "pear", @@ -6107,16 +6149,16 @@ dependencies = [ "smallvec", "stable-pattern", "state", - "time 0.3.21", + "time 0.3.25", "tokio", "uncased", ] [[package]] name = "rocket_okapi" -version = "0.8.0-rc.2" +version = "0.8.0-rc.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "489f4f5b120762f7974e65b919fc462d0660fd8b839026d8985b850fe5acccb0" +checksum = "742098674565c8f0c35c77444f90344aafedebb71cfee9cdbf0185acc6b9cdb7" dependencies = [ "either", "log", @@ -6130,9 +6172,9 @@ dependencies = [ [[package]] name = "rocket_okapi_codegen" -version = "0.8.0-rc.2" +version = "0.8.0-rc.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54f94d1ffe41472e08463d7a2674f1db04dc4df745285e8369b33d3cfd6b0308" +checksum = "8c43f8edc57d88750a220b0ec1870a36c1106204ec99cc35131b49de3b954a4a" dependencies = [ "darling", "proc-macro2", @@ -6141,6 +6183,12 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "rustc-demangle" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" + [[package]] name = "rustc_version" version = "0.2.3" @@ -6156,18 +6204,17 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" dependencies = [ - "semver 1.0.17", + "semver 1.0.18", ] [[package]] name = "rustix" -version = "0.37.19" +version = "0.38.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "acf8729d8542766f1b2cf77eb034d52f40d375bb8b615d0b147089946e16613d" +checksum = "19ed4fa021d81c8392ce04db050a3da9a60299050b7ae1cf482d862b54a7218f" dependencies = [ - "bitflags", + "bitflags 2.4.0", "errno", - "io-lifetimes", "libc", "linux-raw-sys", "windows-sys 0.48.0", @@ -6212,30 +6259,30 @@ dependencies = [ [[package]] name = "rustls-pemfile" -version = "1.0.2" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d194b56d58803a43635bdc398cd17e383d6f71f9182b9a192c127ca42494a59b" +checksum = "2d3987094b1d07b653b7dfdc3f70ce9a1da9c51ac18c1b06b662e4f9a0e9f4b2" dependencies = [ "base64 0.21.2", ] [[package]] name = "rustversion" -version = "1.0.12" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f3208ce4d8448b3f3e7d168a73f5e0c43a61e32930de3bceeccedb388b6bf06" +checksum = "7ffc183a10b4478d04cbbbfc96d0873219d962dd5accaff2ffbd4ceb7df837f4" [[package]] name = "ryu" -version = "1.0.13" +version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f91339c0467de62360649f8d3e185ca8de4224ff281f66000de5eb2a77a79041" +checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741" [[package]] name = "safer-ffi" -version = "0.1.0-rc1" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7270b08a2af50909969cfc28a239cfdda9ab1b3df74749b265c46956183e5bf7" +checksum = "4f47f1d2f33598dab2baa9517fffa1cf722f2e3a30633f2a230f20f9da67c564" dependencies = [ "inventory", "libc", @@ -6250,9 +6297,9 @@ dependencies = [ [[package]] name = "safer_ffi-proc_macros" -version = "0.1.0-rc1" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1438fb328c52f5148ea02a317603efd802f2cd6eb9e721adfd51b94bb55d9ddb" +checksum = "b08f58cf71a58bda5734758eb20051cdb66c06c9243badbc45092ced1be834df" dependencies = [ "macro_rules_attribute", "prettyplease", @@ -6272,11 +6319,11 @@ dependencies = [ [[package]] name = "schannel" -version = "0.1.21" +version = "0.1.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "713cfb06c7059f3588fb8044c0fad1d09e3c01d225e25b9220dbfdcf16dbb1b3" +checksum = "0c3733bf4cf7ea0880754e19cb5a462007c4a8c1914bff372ccc95b464f1df88" dependencies = [ - "windows-sys 0.42.0", + "windows-sys 0.48.0", ] [[package]] @@ -6286,7 +6333,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "02c613288622e5f0c3fdc5dbd4db1c5fbe752746b1d1a56a0630b78fd00de44f" dependencies = [ "dyn-clone", - "indexmap", + "indexmap 1.9.3", "schemars_derive", "serde", "serde_json", @@ -6312,9 +6359,9 @@ checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" [[package]] name = "scopeguard" -version = "1.1.0" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "sct" @@ -6357,7 +6404,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc" dependencies = [ "base16ct 0.2.0", - "der 0.7.7", + "der 0.7.8", "generic-array 0.14.7", "pkcs8 0.10.2", "subtle 2.4.1", @@ -6366,11 +6413,11 @@ dependencies = [ [[package]] name = "security-framework" -version = "2.9.0" +version = "2.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca2855b3715770894e67cbfa3df957790aa0c9edc3bf06efa1a84d77fa0839d1" +checksum = "05b64fb303737d99b81884b2c63433e9ae28abebe5eb5045dcdd175dc2ecf4de" dependencies = [ - "bitflags", + "bitflags 1.3.2", "core-foundation", "core-foundation-sys", "libc", @@ -6379,9 +6426,9 @@ dependencies = [ [[package]] name = "security-framework-sys" -version = "2.9.0" +version = "2.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f51d0c0d83bec45f16480d0ce0058397a69e48fcdc52d1dc8855fb68acbd31a7" +checksum = "e932934257d3b408ed8f30db49d85ea163bfe74961f017f405b025af298f0c7a" dependencies = [ "core-foundation-sys", "libc", @@ -6407,9 +6454,9 @@ dependencies = [ [[package]] name = "semver" -version = "1.0.17" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bebd363326d05ec3e2f532ab7660680f3b02130d780c299bca73469d521bc0ed" +checksum = "b0293b4b29daaf487284529cc2f5675b8e57c61f70167ba415a463651fd6a918" [[package]] name = "semver-parser" @@ -6428,9 +6475,9 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.163" +version = "1.0.183" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2113ab51b87a539ae008b5c6c02dc020ffa39afd2d83cffcb3f4eb2722cebec2" +checksum = "32ac8da02677876d532745a130fc9d8e6edfa81a269b107c5b00829b91d8eb3c" dependencies = [ "serde_derive", ] @@ -6457,22 +6504,22 @@ dependencies = [ [[package]] name = "serde_bytes" -version = "0.11.9" +version = "0.11.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "416bda436f9aab92e02c8e10d49a15ddd339cea90b6e340fe51ed97abb548294" +checksum = "ab33ec92f677585af6d88c65593ae2375adde54efdbf16d597f2cbc7a6d368ff" dependencies = [ "serde", ] [[package]] name = "serde_derive" -version = "1.0.163" +version = "1.0.183" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c805777e3930c8883389c602315a24224bcc738b63905ef87cd1420353ea93e" +checksum = "aafe972d60b0b9bee71a91b92fee2d4fb3c9d7e8f6b179aa99f27203d99a4816" dependencies = [ "proc-macro2", "quote", - "syn 2.0.16", + "syn 2.0.28", ] [[package]] @@ -6488,9 +6535,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.96" +version = "1.0.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "057d394a50403bcac12672b2b18fb387ab6d289d957dab67dd201875391e52f1" +checksum = "076066c5f1078eac5b722a31827a8832fe108bed65dfa75e233c89f8206e976c" dependencies = [ "itoa", "ryu", @@ -6499,20 +6546,20 @@ dependencies = [ [[package]] name = "serde_repr" -version = "0.1.12" +version = "0.1.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bcec881020c684085e55a25f7fd888954d56609ef363479dc5a1305eb0d40cab" +checksum = "8725e1dfadb3a50f7e5ce0b1a540466f6ed3fe7a0fca2ac2b8b831d31316bd00" dependencies = [ "proc-macro2", "quote", - "syn 2.0.16", + "syn 2.0.28", ] [[package]] name = "serde_spanned" -version = "0.6.2" +version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93107647184f6027e3b7dcb2e11034cf95ffa1e3a682c67951963ac69c1c007d" +checksum = "96426c9936fd7a0124915f9185ea1d20aa9445cc9821142f0a73bc9207a2e186" dependencies = [ "serde", ] @@ -6579,9 +6626,9 @@ dependencies = [ [[package]] name = "sha2" -version = "0.10.6" +version = "0.10.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82e6b795fe2e3b1e845bafcb27aa35405c4d47cdfc92af5fc8d3002f76cebdc0" +checksum = "479fb9d862239e610720565ca91403019f2f00410f1864c5aa7479b950a76ed8" dependencies = [ "cfg-if", "cpufeatures", @@ -6599,9 +6646,9 @@ dependencies = [ [[package]] name = "signal-hook" -version = "0.3.15" +version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "732768f1176d21d09e076c23a93123d40bba92d50c4058da34d45c8de8e682b9" +checksum = "8621587d4798caf8eb44879d42e56b9a93ea5dcd315a6487c357130095b62801" dependencies = [ "libc", "signal-hook-registry", @@ -6669,15 +6716,15 @@ dependencies = [ [[package]] name = "smallvec" -version = "1.10.0" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0" +checksum = "62bb4feee49fdd9f707ef802e22365a35de4b7b299de4763d44bfea899442ff9" [[package]] name = "snafu" -version = "0.7.4" +version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb0656e7e3ffb70f6c39b3c2a86332bb74aa3c679da781642590f3c1118c5045" +checksum = "e4de37ad025c587a29e8f3f5605c00f70b98715ef90b9061a815b9e59e9042d6" dependencies = [ "doc-comment", "snafu-derive", @@ -6685,9 +6732,9 @@ dependencies = [ [[package]] name = "snafu-derive" -version = "0.7.4" +version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "475b3bbe5245c26f2d8a6f62d67c1f30eb9fffeccee721c45d162c3ebbdf81b2" +checksum = "990079665f075b699031e9c08fd3ab99be5029b96f3b78dc0709e8f77e4efebf" dependencies = [ "heck 0.4.1", "proc-macro2", @@ -6705,6 +6752,16 @@ dependencies = [ "winapi", ] +[[package]] +name = "socket2" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2538b18701741680e0322a2302176d3253a35388e2e62f172f64f4f16605f877" +dependencies = [ + "libc", + "windows-sys 0.48.0", +] + [[package]] name = "sphinx-packet" version = "0.1.0" @@ -6761,7 +6818,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9d1e996ef02c474957d681f1b05213dfb0abab947b446a62d37770b23500184a" dependencies = [ "base64ct", - "der 0.7.7", + "der 0.7.8", ] [[package]] @@ -6814,7 +6871,7 @@ checksum = "e48c61941ccf5ddcada342cd59e3e5173b007c509e1e8e990dafc830294d9dc5" dependencies = [ "ahash 0.7.6", "atoi 0.4.0", - "bitflags", + "bitflags 1.3.2", "byteorder", "bytes", "chrono", @@ -6830,7 +6887,7 @@ dependencies = [ "futures-util", "hashlink 0.7.0", "hex", - "indexmap", + "indexmap 1.9.3", "itoa", "libc", "libsqlite3-sys", @@ -6840,7 +6897,7 @@ dependencies = [ "paste", "percent-encoding", "rustls 0.19.1", - "sha2 0.10.6", + "sha2 0.10.7", "smallvec", "sqlformat 0.1.8", "sqlx-rt 0.5.13", @@ -6860,7 +6917,7 @@ checksum = "fa8241483a83a3f33aa5fff7e7d9def398ff9990b2752b6c6112b83c6d246029" dependencies = [ "ahash 0.7.6", "atoi 1.0.0", - "bitflags", + "bitflags 1.3.2", "byteorder", "bytes", "chrono", @@ -6875,9 +6932,9 @@ dependencies = [ "futures-executor", "futures-intrusive", "futures-util", - "hashlink 0.8.2", + "hashlink 0.8.3", "hex", - "indexmap", + "indexmap 1.9.3", "itoa", "libc", "libsqlite3-sys", @@ -6888,7 +6945,7 @@ dependencies = [ "percent-encoding", "rustls 0.20.8", "rustls-pemfile", - "sha2 0.10.6", + "sha2 0.10.7", "smallvec", "sqlformat 0.2.1", "sqlx-rt 0.6.3", @@ -6911,7 +6968,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "sha2 0.10.6", + "sha2 0.10.7", "sqlx-core 0.5.13", "sqlx-rt 0.5.13", "syn 1.0.109", @@ -6930,7 +6987,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "sha2 0.10.6", + "sha2 0.10.7", "sqlx-core 0.6.3", "sqlx-rt 0.6.3", "syn 1.0.109", @@ -6979,9 +7036,9 @@ dependencies = [ [[package]] name = "stringprep" -version = "0.1.2" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ee348cb74b87454fff4b551cbf727025810a004f88aeacae7f85b87f4e9a1c1" +checksum = "db3737bde7edce97102e0e2b15365bf7a20bfdb5f60f4f9e8d7004258a51a8da" dependencies = [ "unicode-bidi", "unicode-normalization", @@ -7074,9 +7131,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.16" +version = "2.0.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6f671d4b5ffdb8eadec19c0ae67fe2639df8684bd7bc4b83d986b8db549cf01" +checksum = "04361975b3f5e348b2189d8dc55bc942f278b2d482a6a0365de5bdd62d351567" dependencies = [ "proc-macro2", "quote", @@ -7112,15 +7169,15 @@ checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" [[package]] name = "tempfile" -version = "3.5.0" +version = "3.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9fbec84f381d5795b08656e4912bec604d162bff9291d6189a78f4c8ab87998" +checksum = "dc02fddf48964c42031a0b3fe0428320ecf3a73c401040fc0096f97794310651" dependencies = [ "cfg-if", - "fastrand", + "fastrand 2.0.0", "redox_syscall 0.3.5", "rustix", - "windows-sys 0.45.0", + "windows-sys 0.48.0", ] [[package]] @@ -7131,7 +7188,7 @@ checksum = "3f0a7d05cf78524782337f8edd55cbc578d159a16ad4affe2135c92f7dbac7f0" dependencies = [ "bytes", "digest 0.10.7", - "ed25519 2.2.1", + "ed25519 2.2.2", "ed25519-consensus", "flex-error", "futures", @@ -7145,12 +7202,12 @@ dependencies = [ "serde_bytes", "serde_json", "serde_repr", - "sha2 0.10.6", + "sha2 0.10.7", "signature 2.1.0", "subtle 2.4.1", "subtle-encoding", "tendermint-proto", - "time 0.3.21", + "time 0.3.25", "zeroize", ] @@ -7183,7 +7240,7 @@ dependencies = [ "serde", "serde_bytes", "subtle-encoding", - "time 0.3.21", + "time 0.3.25", ] [[package]] @@ -7203,7 +7260,7 @@ dependencies = [ "hyper-rustls", "peg", "pin-project", - "semver 1.0.17", + "semver 1.0.18", "serde", "serde_bytes", "serde_json", @@ -7213,7 +7270,7 @@ dependencies = [ "tendermint-config", "tendermint-proto", "thiserror", - "time 0.3.21", + "time 0.3.25", "tokio", "tracing", "url", @@ -7238,22 +7295,22 @@ checksum = "222a222a5bfe1bba4a77b45ec488a741b3cb8872e5e499451fd7d0129c9c7c3d" [[package]] name = "thiserror" -version = "1.0.40" +version = "1.0.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "978c9a314bd8dc99be594bc3c175faaa9794be04a5a5e153caba6915336cebac" +checksum = "611040a08a0439f8248d1990b111c95baa9c704c805fa1f62104b39655fd7f90" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.40" +version = "1.0.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9456a42c5b0d803c8cd86e73dd7cc9edd429499f37a3550d286d5e86720569f" +checksum = "090198534930841fab3a5d1bb637cde49e339654e606195f8d9c76eeb081dc96" dependencies = [ "proc-macro2", "quote", - "syn 2.0.16", + "syn 2.0.28", ] [[package]] @@ -7301,10 +7358,11 @@ dependencies = [ [[package]] name = "time" -version = "0.3.21" +version = "0.3.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f3403384eaacbca9923fa06940178ac13e4edb725486d70e8e15881d0c836cc" +checksum = "b0fdd63d58b18d663fbdf70e049f00a22c8e42be082203be7f26589213cd75ea" dependencies = [ + "deranged", "itoa", "js-sys", "serde", @@ -7320,9 +7378,9 @@ checksum = "7300fbefb4dadc1af235a9cef3737cea692a9d97e1b9cbcd4ebdae6f8868e6fb" [[package]] name = "time-macros" -version = "0.2.9" +version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "372950940a5f07bf38dbe211d7283c9e6d7327df53794992d293e534c733d09b" +checksum = "eb71511c991639bb078fd5bf97757e03914361c48100d52878b8e52b46fb92cd" dependencies = [ "time-core", ] @@ -7354,11 +7412,11 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.28.1" +version = "1.31.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0aa32867d44e6f2ce3385e89dceb990188b8bb0fb25b0cf576647a6f98ac5105" +checksum = "40de3a2ba249dcb097e01be5e67a5ff53cf250397715a071a81543e8a832a920" dependencies = [ - "autocfg 1.1.0", + "backtrace", "bytes", "libc", "mio", @@ -7366,7 +7424,7 @@ dependencies = [ "parking_lot 0.12.1", "pin-project-lite", "signal-hook-registry", - "socket2", + "socket2 0.5.3", "tokio-macros", "tracing", "windows-sys 0.48.0", @@ -7390,7 +7448,7 @@ checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.16", + "syn 2.0.28", ] [[package]] @@ -7513,9 +7571,9 @@ dependencies = [ [[package]] name = "toml" -version = "0.7.4" +version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6135d499e69981f9ff0ef2167955a5333c35e36f6937d382974566b3d5b94ec" +checksum = "c17e963a819c331dcacd7ab957d80bc2b9a9c1e71c804826d2f283dd65306542" dependencies = [ "serde", "serde_spanned", @@ -7525,20 +7583,20 @@ dependencies = [ [[package]] name = "toml_datetime" -version = "0.6.2" +version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a76a9312f5ba4c2dec6b9161fdf25d87ad8a09256ccea5a556fef03c706a10f" +checksum = "7cda73e2f1397b1262d6dfdcef8aafae14d1de7748d66822d3bfeeb6d03e5e4b" dependencies = [ "serde", ] [[package]] name = "toml_edit" -version = "0.19.10" +version = "0.19.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2380d56e8670370eee6566b0bfd4265f65b3f432e8c6d85623f728d4fa31f739" +checksum = "f8123f27e969974a3dfba720fdb560be359f57b44302d280ba72e76a74480e8a" dependencies = [ - "indexmap", + "indexmap 2.0.0", "serde", "serde_spanned", "toml_datetime", @@ -7581,7 +7639,7 @@ checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c" dependencies = [ "futures-core", "futures-util", - "indexmap", + "indexmap 1.9.3", "pin-project", "pin-project-lite", "rand 0.8.5", @@ -7620,13 +7678,13 @@ dependencies = [ [[package]] name = "tracing-attributes" -version = "0.1.24" +version = "0.1.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f57e3ca2a01450b1a921183a9c9cbfda207fd822cef4ccb00a65402cbba7a74" +checksum = "5f4f31f56159e98206da9efd823404b79b6ef3143b4a7ab76e67b1751b25a4ab" dependencies = [ "proc-macro2", "quote", - "syn 2.0.16", + "syn 2.0.28", ] [[package]] @@ -7694,11 +7752,11 @@ dependencies = [ [[package]] name = "tracing-tree" -version = "0.2.3" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f9742d8df709837409dbb22aa25dd7769c260406f20ff48a2320b80a4a6aed0" +checksum = "92d6b63348fad3ae0439b8bebf8d38fb5bda0b115d7a8a7e6f165f12790c58c3" dependencies = [ - "atty", + "is-terminal", "nu-ansi-term", "tracing-core", "tracing-log", @@ -7805,9 +7863,9 @@ dependencies = [ [[package]] name = "ucd-trie" -version = "0.1.5" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e79c4d996edb816c91e4308506774452e55e95c3c9de07b6729e17e15a5ef81" +checksum = "ed646292ffc8188ef8ea4d1e0e0150fb15a5c2e12ad9b8fc191ae7a8a7f3c4b9" [[package]] name = "uncased" @@ -7846,9 +7904,9 @@ checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460" [[package]] name = "unicode-ident" -version = "1.0.8" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5464a87b239f13a63a501f2701565754bae92d243d4bb7eb12f6d57d2269bf4" +checksum = "301abaae475aa91687eb82514b328ab47a211a533026cb25fc3e519b86adfc3c" [[package]] name = "unicode-normalization" @@ -7894,9 +7952,9 @@ dependencies = [ [[package]] name = "universal-hash" -version = "0.5.0" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d3160b73c9a19f7e2939a2fdad446c57c1bbbbf4d919d3213ff1267a580d8b5" +checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea" dependencies = [ "crypto-common", "subtle 2.4.1", @@ -7916,21 +7974,21 @@ checksum = "0976c77def3f1f75c4ef892a292c31c0bbe9e3d0702c63044d7c76db298171a3" [[package]] name = "url" -version = "2.3.1" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d68c799ae75762b8c3fe375feb6600ef5602c883c5d21eb51c09f22b83c4643" +checksum = "50bff7831e19200a85b17131d085c25d7811bc4e186efdaf54bbd132994a88cb" dependencies = [ "form_urlencoded", - "idna 0.3.0", + "idna 0.4.0", "percent-encoding", "serde", ] [[package]] name = "urlencoding" -version = "2.1.2" +version = "2.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8db7427f936968176eaa7cdf81b7f98b980b18495ec28f1b5791ac3bfe3eea9" +checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da" [[package]] name = "utf-8" @@ -7952,9 +8010,9 @@ checksum = "bc5cf98d8186244414c848017f0e2676b3fcb46807f6668a97dfe67359a3c4b7" [[package]] name = "uuid" -version = "1.3.3" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "345444e32442451b267fc254ae85a209c64be56d2890e601a0c37ff0c3c5ecd2" +checksum = "79daa5ed5740825c40b389c5e50312b9c86df53fccd33f281df655642b43869d" dependencies = [ "getrandom 0.2.10", "wasm-bindgen", @@ -7986,7 +8044,7 @@ dependencies = [ "rustc_version 0.4.0", "rustversion", "thiserror", - "time 0.3.21", + "time 0.3.25", ] [[package]] @@ -8013,11 +8071,10 @@ dependencies = [ [[package]] name = "want" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ce8a968cb1cd110d136ff8b819a556d6fb6d919363c61534f6860c7eb172ba0" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" dependencies = [ - "log", "try-lock", ] @@ -8222,22 +8279,7 @@ version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e686886bc078bc1b0b600cac0147aadb815089b6e4da64016cbd754b6342700f" dependencies = [ - "windows-targets 0.48.0", -] - -[[package]] -name = "windows-sys" -version = "0.42.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" -dependencies = [ - "windows_aarch64_gnullvm 0.42.2", - "windows_aarch64_msvc 0.42.2", - "windows_i686_gnu 0.42.2", - "windows_i686_msvc 0.42.2", - "windows_x86_64_gnu 0.42.2", - "windows_x86_64_gnullvm 0.42.2", - "windows_x86_64_msvc 0.42.2", + "windows-targets 0.48.1", ] [[package]] @@ -8255,7 +8297,7 @@ version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" dependencies = [ - "windows-targets 0.48.0", + "windows-targets 0.48.1", ] [[package]] @@ -8275,9 +8317,9 @@ dependencies = [ [[package]] name = "windows-targets" -version = "0.48.0" +version = "0.48.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b1eb6f0cd7c80c79759c929114ef071b87354ce476d9d94271031c0497adfd5" +checksum = "05d4b17490f70499f20b9e791dcf6a299785ce8af4d709018206dc5b4953e95f" dependencies = [ "windows_aarch64_gnullvm 0.48.0", "windows_aarch64_msvc 0.48.0", @@ -8374,9 +8416,9 @@ checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" [[package]] name = "winnow" -version = "0.4.6" +version = "0.5.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61de7bac303dc551fe038e2b3cef0f571087a47571ea6e79a87692ac99b99699" +checksum = "5504cc7644f4b593cbc05c4a55bf9bd4e94b867c3c0bd440934174d50482427d" dependencies = [ "memchr", ] @@ -8437,6 +8479,12 @@ version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09041cd90cf85f7f8b2df60c646f853b7f535ce68f85244eb6731cf89fa498ec" +[[package]] +name = "yansi" +version = "1.0.0-rc.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1367295b8f788d371ce2dbc842c7b709c73ee1364d30351dd300ec2203b12377" + [[package]] name = "zeroize" version = "1.6.0" @@ -8454,5 +8502,5 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn 2.0.16", + "syn 2.0.28", ] diff --git a/clients/webassembly/Cargo.lock b/clients/webassembly/Cargo.lock index 0ea047444e..3f21d6f932 100644 --- a/clients/webassembly/Cargo.lock +++ b/clients/webassembly/Cargo.lock @@ -2,6 +2,15 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "addr2line" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4fa78e18c64fce05e902adecd7a5eed15a5e0a3439f7b0e169f0252214865e3" +dependencies = [ + "gimli", +] + [[package]] name = "adler" version = "1.0.2" @@ -33,9 +42,9 @@ dependencies = [ [[package]] name = "aes" -version = "0.8.2" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "433cfd6710c9986c576a25ca913c39d66a6474107b406f34f91d4a8923395241" +checksum = "ac1f845298e95f983ff1944b728ae08b8cebab80d684f0a832ed0fc74dfa27e2" dependencies = [ "cfg-if 1.0.0", "cipher 0.4.4", @@ -44,12 +53,12 @@ dependencies = [ [[package]] name = "aes-gcm" -version = "0.10.1" +version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82e1366e0c69c9f927b1fa5ce2c7bf9eafc8f9268c0b9800729e8b267612447c" +checksum = "209b47e8954a928e1d72e86eca7000ebb6655fe1436d33eefc2201cad027e237" dependencies = [ "aead", - "aes 0.8.2", + "aes 0.8.3", "cipher 0.4.4", "ctr 0.9.2", "ghash", @@ -80,13 +89,19 @@ dependencies = [ [[package]] name = "aho-corasick" -version = "1.0.1" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67fc08ce920c31afb70f013dcce1bfc3a3195de6a228474e45e1f145b36f8d04" +checksum = "86b8f9420f797f2d9e935edf629310eb938a0d839f984e25327f3c7eed22300c" dependencies = [ "memchr", ] +[[package]] +name = "allocator-api2" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0942ffc6dcaadf03badf6e6a2d0228460359d5e34b57ccdc720b7382dfbd5ec5" + [[package]] name = "anstream" version = "0.3.2" @@ -104,15 +119,15 @@ dependencies = [ [[package]] name = "anstyle" -version = "1.0.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41ed9a86bf92ae6580e0a31281f65a1b1d867c0cc68d5346e2ae128dddfa6a7d" +checksum = "3a30da5c5f2d5e72842e00bcb57657162cdabef0931f40e2deb9b4140440cecd" [[package]] name = "anstyle-parse" -version = "0.2.0" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e765fd216e48e067936442276d1d57399e37bce53c264d6fefbe298080cb57ee" +checksum = "938874ff5980b03a87c5524b3ae5b59cf99b1d6bc836848df7bc5ada9643c333" dependencies = [ "utf8parse", ] @@ -123,33 +138,34 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5ca11d4be1bab0c8bc8734a9aa7bf4ee8316d462a08c6ac5052f888fef5b494b" dependencies = [ - "windows-sys 0.48.0", + "windows-sys", ] [[package]] name = "anstyle-wincon" -version = "1.0.1" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "180abfa45703aebe0093f79badacc01b8fd4ea2e35118747e5811127f926e188" +checksum = "c677ab05e09154296dd37acecd46420c17b9713e8366facafa8fc0885167cf4c" dependencies = [ "anstyle", - "windows-sys 0.48.0", + "windows-sys", ] [[package]] name = "anyhow" -version = "1.0.71" +version = "1.0.72" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c7d0618f0e0b7e8ff11427422b64564d5fb0be1940354bfe2e0529b18a9d9b8" +checksum = "3b13c32d80ecc7ab747b80c3784bce54ee8a7a0cc4fbda9bf4cda2cf6fe90854" [[package]] name = "argon2" -version = "0.5.0" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95c2fcf79ad1932ac6269a738109997a83c227c09b75842ae564dc8ede6a861c" +checksum = "b2e554a8638bdc1e4eae9984845306cc95f8a9208ba8d49c3859fd958b46774d" dependencies = [ "base64ct", "blake2 0.10.6", + "cpufeatures", "password-hash", ] @@ -161,19 +177,19 @@ checksum = "6b4930d2cb77ce62f89ee5d5289b4ac049559b1c45539271f5ed4fdc7db34545" [[package]] name = "arrayvec" -version = "0.7.2" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8da52d66c7071e2e3fa2a1e5c6d088fec47b593032b254f5e980de8ea54454d6" +checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711" [[package]] name = "async-trait" -version = "0.1.68" +version = "0.1.73" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9ccdd8f2a161be9bd5c023df56f1b2a0bd1d83872ae53b71a84a12c9bf6e842" +checksum = "bc00ceb34980c03614e35a3a4e218276a0a824e911d07651cd0d858a51e8c0f0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.16", + "syn 2.0.28", ] [[package]] @@ -211,6 +227,21 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" +[[package]] +name = "backtrace" +version = "0.3.68" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4319208da049c43661739c5fade2ba182f09d1dc2299b32298d3a31692b17e12" +dependencies = [ + "addr2line", + "cc", + "cfg-if 1.0.0", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", +] + [[package]] name = "base16ct" version = "0.1.1" @@ -254,7 +285,7 @@ dependencies = [ "pbkdf2", "rand_core 0.6.4", "ripemd", - "sha2 0.10.6", + "sha2 0.10.7", "subtle 2.4.1", "zeroize", ] @@ -285,6 +316,12 @@ version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" +[[package]] +name = "bitflags" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4682ae6287fcf752ecaabbfcc7b6f9b72aa33933dc23a554d853aea8eea8635" + [[package]] name = "bitvec" version = "1.0.1" @@ -320,9 +357,9 @@ dependencies = [ [[package]] name = "blake3" -version = "1.3.3" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42ae2468a89544a466886840aa467a25b766499f4f04bf7d9fcd10ecee9fccef" +checksum = "199c42ab6972d92c9f8995f086273d25c42fc0f7b2a1fcefba465c1352d25ba5" dependencies = [ "arrayref", "arrayvec", @@ -396,14 +433,14 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f5353f36341f7451062466f0b755b96ac3a9547e4d7f6b70d603fc721a7d7896" dependencies = [ - "sha2 0.10.6", + "sha2 0.10.7", ] [[package]] name = "bumpalo" -version = "3.12.2" +version = "3.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c6ed94e98ecff0c12dd1b04c15ec0d7d9458ca8fe806cea6f12954efe74c63b" +checksum = "a3e2c3daef883ecc1b5d58c15adae93470a91d425f3532ba1695849656af3fc1" [[package]] name = "byte-tools" @@ -428,11 +465,12 @@ dependencies = [ [[package]] name = "cc" -version = "1.0.79" +version = "1.0.82" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f" +checksum = "305fe645edc1442a0fa8b6726ba61d422798d37a52e12eaecf4b022ebbb88f01" dependencies = [ "jobserver", + "libc", ] [[package]] @@ -503,9 +541,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.2.7" +version = "4.3.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34d21f9bf1b425d2968943631ec91202fe5e837264063503708b83013f8fc938" +checksum = "c27cdf28c0f604ba3f512b0c9a409f8de8513e4816705deb0498b627e7c3a3fd" dependencies = [ "clap_builder", "clap_derive", @@ -514,31 +552,30 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.2.7" +version = "4.3.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "914c8c79fb560f238ef6429439a30023c862f7a28e688c58f7203f12b29970bd" +checksum = "08a9f1ab5e9f01a9b81f202e8562eb9a10de70abf9eaeac1be465c28b75aa4aa" dependencies = [ "anstream", "anstyle", - "bitflags", "clap_lex", "strsim", ] [[package]] name = "clap_complete" -version = "4.2.3" +version = "4.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1594fe2312ec4abf402076e407628f5c313e54c32ade058521df4ee34ecac8a8" +checksum = "5fc443334c81a804575546c5a8a79b4913b50e28d69232903604cada1de817ce" dependencies = [ "clap", ] [[package]] name = "clap_complete_fig" -version = "4.2.0" +version = "4.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3af28956330989baa428ed4d3471b853715d445c62de21b67292e22cf8a41fa" +checksum = "99fee1d30a51305a6c2ed3fc5709be3c8af626c9c958e04dd9ae94e27bcbce9f" dependencies = [ "clap", "clap_complete", @@ -546,21 +583,21 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.2.0" +version = "4.3.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f9644cd56d6b87dbe899ef8b053e331c0637664e9e21a33dfcdc36093f5c5c4" +checksum = "54a9bb5758fc5dfe728d1019941681eccaf0cf8a4189b692a0ee2f2ecf90a050" dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.16", + "syn 2.0.28", ] [[package]] name = "clap_lex" -version = "0.4.1" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a2dd5a6fe8c6e3502f568a6353e5273bbb15193ad9a89e457b9970798efbea1" +checksum = "2da6da31387c7e4ef160ffab6d5e7f00c42626fe39aea70a7b0f1773f7dd6c1b" [[package]] name = "colorchoice" @@ -570,13 +607,13 @@ checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" [[package]] name = "colored" -version = "2.0.0" +version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3616f750b84d8f0de8a58bda93e08e2a81ad3f523089b05f1dffecab48c6cbd" +checksum = "2674ec482fbc38012cf31e6c42ba0177b431a0cb6f15fe40efa5aab1bda516f6" dependencies = [ - "atty", + "is-terminal", "lazy_static", - "winapi", + "windows-sys", ] [[package]] @@ -591,15 +628,15 @@ dependencies = [ [[package]] name = "const-oid" -version = "0.9.4" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "795bc6e66a8e340f075fcf6227e417a2dc976b92b91f3cdc778bb858778b6747" +checksum = "28c122c3980598d243d63d9a704629a2d748d101f278052ff068be5a4423ab6f" [[package]] name = "constant_time_eq" -version = "0.2.5" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13418e745008f7349ec7e449155f419a61b92b58a99cc3616942b926825ec76b" +checksum = "f7144d30dcf0fafbce74250a3963025d8d52177934239851c917d29f1df280c2" [[package]] name = "core-foundation" @@ -651,9 +688,9 @@ dependencies = [ [[package]] name = "cosmwasm-crypto" -version = "1.3.0" +version = "1.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d076a08ec01ed23c4396aca98ec73a38fa1fee5f310465add52b4108181c7a8" +checksum = "7e272708a9745dad8b591ef8a718571512130f2b39b33e3d7a27c558e3069394" dependencies = [ "digest 0.10.7", "ed25519-zebra", @@ -664,9 +701,9 @@ dependencies = [ [[package]] name = "cosmwasm-derive" -version = "1.3.0" +version = "1.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dec361f3c09d7b41221948fc17be9b3c96cb58e55a02f82da36f888a651f2584" +checksum = "296db6a3caca5283425ae0cf347f4e46999ba3f6620dbea8939a0e00347831ce" dependencies = [ "syn 1.0.109", ] @@ -711,15 +748,15 @@ dependencies = [ "schemars", "serde", "serde-json-wasm", - "sha2 0.10.6", + "sha2 0.10.7", "thiserror", ] [[package]] name = "cpufeatures" -version = "0.2.7" +version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e4c1eaa2012c47becbbad2ab175484c2a84d1185b566fb2cc5b8707343dfe58" +checksum = "a17b76ff3a4162b0b27f354a0c87015ddad39d35f9c0c36607a3bdd175dde1f1" dependencies = [ "libc", ] @@ -786,9 +823,9 @@ dependencies = [ [[package]] name = "crossbeam-epoch" -version = "0.9.14" +version = "0.9.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46bd5f3f85273295a9d14aedfb86f6aadbff6d8f5295c4a9edb08e819dcf5695" +checksum = "ae211234986c545741a7dc064309f67ee1e5ad243d0e48335adc0484d960bcc7" dependencies = [ "autocfg", "cfg-if 1.0.0", @@ -809,9 +846,9 @@ dependencies = [ [[package]] name = "crossbeam-utils" -version = "0.8.15" +version = "0.8.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c063cd8cc95f5c377ed0d4b49a4b21f632396ff690e8470c29b3359b346984b" +checksum = "5a22b2d63d4d1dc0b7f1b6b2747dd0088008a9be28b6ddf0b1e7d335e3037294" dependencies = [ "cfg-if 1.0.0", ] @@ -961,7 +998,7 @@ dependencies = [ "cosmwasm-std", "cw2", "schemars", - "semver 1.0.17", + "semver 1.0.18", "serde", "thiserror", ] @@ -1023,15 +1060,15 @@ dependencies = [ [[package]] name = "dashmap" -version = "5.4.0" +version = "5.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "907076dfda823b0b36d2a1bb5f90c96660a5bbcd7729e10727f07858f22c4edc" +checksum = "6943ae99c34386c84a470c499d3414f66502a41340aa895406e0d2e4a207b91d" dependencies = [ "cfg-if 1.0.0", - "hashbrown 0.12.3", + "hashbrown 0.14.0", "lock_api", "once_cell", - "parking_lot_core 0.9.7", + "parking_lot_core 0.9.8", ] [[package]] @@ -1046,14 +1083,20 @@ dependencies = [ [[package]] name = "der" -version = "0.7.7" +version = "0.7.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c7ed52955ce76b1554f509074bb357d3fb8ac9b51288a65a3fd480d1dfba946" +checksum = "fffa369a668c8af7dbf8b5e56c9f744fbd399949ed171606040001947de40b1c" dependencies = [ "const-oid", "zeroize", ] +[[package]] +name = "deranged" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7684a49fb1af197853ef7b2ee694bc1f5b4179556f1e5710e1760c5db6f5e929" + [[package]] name = "derivative" version = "2.2.0" @@ -1133,7 +1176,7 @@ dependencies = [ "libc", "option-ext", "redox_users", - "windows-sys 0.48.0", + "windows-sys", ] [[package]] @@ -1150,9 +1193,9 @@ checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b" [[package]] name = "dyn-clone" -version = "1.0.11" +version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68b0cf012f1230e43cd00ebb729c6bb58707ecfa8ad08b52ef3a4ccd2697fc30" +checksum = "304e6508efa593091e97a9abbc10f90aa7ca635b6d2784feff3c89d41dd12272" [[package]] name = "ecdsa" @@ -1172,7 +1215,7 @@ version = "0.16.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a4b1e0c257a9e9f25f90ff76d7a68360ed497ee519c8e428d1825ef0000799d4" dependencies = [ - "der 0.7.7", + "der 0.7.8", "digest 0.10.7", "elliptic-curve 0.13.5", "rfc6979 0.4.0", @@ -1192,9 +1235,9 @@ dependencies = [ [[package]] name = "ed25519" -version = "2.2.1" +version = "2.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fb04eee5d9d907f29e80ee6b0e78f7e2c82342c63e3580d8c4f69d9d5aad963" +checksum = "60f6d271ca33075c88028be6f04d502853d63a5ece419d269c15315d4fc1cf1d" dependencies = [ "pkcs8 0.10.2", "signature 2.1.0", @@ -1245,9 +1288,9 @@ dependencies = [ [[package]] name = "either" -version = "1.8.1" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91" +checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" [[package]] name = "elliptic-curve" @@ -1314,7 +1357,7 @@ checksum = "eecf8589574ce9b895052fa12d69af7a233f99e6107f5cb8dd1044f2a17bfdcb" dependencies = [ "proc-macro2", "quote", - "syn 2.0.16", + "syn 2.0.28", ] [[package]] @@ -1331,14 +1374,20 @@ dependencies = [ ] [[package]] -name = "errno" -version = "0.3.1" +name = "equivalent" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4bcfec3a70f97c962c307b2d2c56e358cf1d00b558d74262b5f929ee8cc7e73a" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + +[[package]] +name = "errno" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b30f669a7961ef1631673d2766cc92f52d64f7ef354d4fe0ddfd30ed52f0f4f" dependencies = [ "errno-dragonfly", "libc", - "windows-sys 0.48.0", + "windows-sys", ] [[package]] @@ -1369,12 +1418,9 @@ dependencies = [ [[package]] name = "fastrand" -version = "1.9.0" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e51093e27b0797c359783294ca4f0a911c270184cb10f85783b118614a1501be" -dependencies = [ - "instant", -] +checksum = "6999dc1837253364c2ebb0704ba97994bd874e8f195d665c50b7548f6ea92764" [[package]] name = "ff" @@ -1471,9 +1517,9 @@ checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" [[package]] name = "form_urlencoded" -version = "1.1.0" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9c384f161156f5260c24a097c56119f9be8c798586aecc13afbcbe7b7e26bf8" +checksum = "a62bc1cf6f830c2ec14a513a9fb124d0a213a629668a4186f329db21fe045652" dependencies = [ "percent-encoding", ] @@ -1557,7 +1603,7 @@ checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72" dependencies = [ "proc-macro2", "quote", - "syn 2.0.16", + "syn 2.0.28", ] [[package]] @@ -1659,13 +1705,19 @@ dependencies = [ "polyval", ] +[[package]] +name = "gimli" +version = "0.27.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c80984affa11d98d1b88b66ac8853f143217b399d3c74116778ff8fdb4ed2e" + [[package]] name = "git2" version = "0.14.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d0155506aab710a86160ddb504a480d2964d7ab5b9e62419be69e0032bc5931c" dependencies = [ - "bitflags", + "bitflags 1.3.2", "libc", "libgit2-sys", "log", @@ -1732,9 +1784,9 @@ dependencies = [ [[package]] name = "h2" -version = "0.3.19" +version = "0.3.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d357c7ae988e7d2182f7d7871d0b963962420b0678b0997ce7de72001aeab782" +checksum = "97ec8491ebaf99c8eaa73058b045fe58073cd6be7f596ac993ced0b0a0c01049" dependencies = [ "bytes", "fnv", @@ -1742,7 +1794,7 @@ dependencies = [ "futures-sink", "futures-util", "http", - "indexmap", + "indexmap 1.9.3", "slab", "tokio", "tokio-util", @@ -1783,11 +1835,12 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.13.2" +version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e" +checksum = "2c6201b9ff9fd90a5a3bac2e56a830d0caa509576f0e503818ee82c181b3437a" dependencies = [ "ahash 0.8.3", + "allocator-api2", ] [[package]] @@ -1801,11 +1854,11 @@ dependencies = [ [[package]] name = "hashlink" -version = "0.8.2" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0761a1b9491c4f2e3d66aa0f62d0fba0af9a0e2852e4d48ea506632a4b56e6aa" +checksum = "312f66718a2d7789ffef4f4b7b213138ed9f1eb3aa1d0d82fc99f88fb3ffd26f" dependencies = [ - "hashbrown 0.13.2", + "hashbrown 0.14.0", ] [[package]] @@ -1815,7 +1868,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f3e372db8e5c0d213e0cd0b9be18be2aca3d44cf2fe30a9d46a65581cd454584" dependencies = [ "base64 0.13.1", - "bitflags", + "bitflags 1.3.2", "bytes", "headers-core", "http", @@ -1853,18 +1906,9 @@ dependencies = [ [[package]] name = "hermit-abi" -version = "0.2.6" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee512640fe35acbfb4bb779db6f0d80704c2cacfa2e39b601ef3e3f47d1ae4c7" -dependencies = [ - "libc", -] - -[[package]] -name = "hermit-abi" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fed44880c466736ef9a5c5b5facefb5ed0785676d0c02d612db14e54f0d84286" +checksum = "443144c8cdadd93ebf52ddb4056d257f5b52c04d3c804e657d19eb73fc33668b" [[package]] name = "hex" @@ -1946,9 +1990,9 @@ checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" [[package]] name = "httpdate" -version = "1.0.2" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" [[package]] name = "humantime" @@ -1977,9 +2021,9 @@ dependencies = [ [[package]] name = "hyper" -version = "0.14.26" +version = "0.14.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab302d72a6f11a3b910431ff93aae7e773078c769f0a3ef15fb9ec692ed147d4" +checksum = "ffb1cfd654a8219eaef89881fdb3bb3b1cdc5fa75ded05d6933b2b382e395468" dependencies = [ "bytes", "futures-channel", @@ -1992,7 +2036,7 @@ dependencies = [ "httpdate", "itoa", "pin-project-lite", - "socket2", + "socket2 0.4.9", "tokio", "tower-service", "tracing", @@ -2051,9 +2095,9 @@ dependencies = [ [[package]] name = "idna" -version = "0.3.0" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e14ddfc70884202db2244c223200c204c2bda1bc6e0998d11b5e024d657209e6" +checksum = "7d20d6b07bfbc108882d88ed8e37d39636dcc260e15e30c45e6ba089610b917c" dependencies = [ "unicode-bidi", "unicode-normalization", @@ -2073,7 +2117,7 @@ checksum = "bfbcff6ae46750b15cc594bfd277b188cbddcfdc1817848f97f03f26f8625b9e" dependencies = [ "cfg-if 1.0.0", "js-sys", - "uuid 1.3.3", + "uuid 1.4.1", "wasm-bindgen", "wasm-bindgen-futures", "web-sys", @@ -2090,6 +2134,16 @@ dependencies = [ "serde", ] +[[package]] +name = "indexmap" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5477fe2230a79769d8dc68e0eabf5437907c0457a5614a9e8dddb67f65eb65d" +dependencies = [ + "equivalent", + "hashbrown 0.14.0", +] + [[package]] name = "inout" version = "0.1.3" @@ -2117,33 +2171,21 @@ dependencies = [ "cfg-if 1.0.0", ] -[[package]] -name = "io-lifetimes" -version = "1.0.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c66c74d2ae7e79a5a8f7ac924adbe38ee42a859c6539ad869eb51f0b52dc220" -dependencies = [ - "hermit-abi 0.3.1", - "libc", - "windows-sys 0.48.0", -] - [[package]] name = "ipnet" -version = "2.7.2" +version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12b6ee2129af8d4fb011108c73d99a1b83a85977f23b82460c0ae2e25bb4b57f" +checksum = "28b29a3cd74f0f4598934efe3aeba42bae0eb4680554128851ebbecb02af14e6" [[package]] name = "is-terminal" -version = "0.4.7" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adcf93614601c8129ddf72e2d5633df827ba6551541c6d8c59520a371475be1f" +checksum = "cb0889898416213fab133e1d33a0e5858a48177452750691bde3666d0fdbaf8b" dependencies = [ - "hermit-abi 0.3.1", - "io-lifetimes", + "hermit-abi 0.3.2", "rustix", - "windows-sys 0.48.0", + "windows-sys", ] [[package]] @@ -2157,9 +2199,9 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.6" +version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "453ad9f582a441959e5f0d088b02ce04cfe8d51a8eaf077f12ac6d3e94164ca6" +checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" [[package]] name = "jobserver" @@ -2188,7 +2230,7 @@ dependencies = [ "cfg-if 1.0.0", "ecdsa 0.14.8", "elliptic-curve 0.12.3", - "sha2 0.10.6", + "sha2 0.10.7", ] [[package]] @@ -2201,7 +2243,7 @@ dependencies = [ "ecdsa 0.16.8", "elliptic-curve 0.13.5", "once_cell", - "sha2 0.10.6", + "sha2 0.10.7", "signature 2.1.0", ] @@ -2219,9 +2261,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.144" +version = "0.2.147" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b00cc1c228a6782d0f076e7b232802e0c5689d41bb5df366f2a6b6621cfdfe1" +checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3" [[package]] name = "libgit2-sys" @@ -2254,9 +2296,9 @@ dependencies = [ [[package]] name = "libz-sys" -version = "1.1.9" +version = "1.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56ee889ecc9568871456d42f603d6a0ce59ff328d291063a45cbdf0036baf6db" +checksum = "d97137b25e321a73eef1418d1d5d2eda4d77e12813f8e6dead84bc52c5870a7b" dependencies = [ "cc", "libc", @@ -2266,9 +2308,9 @@ dependencies = [ [[package]] name = "linux-raw-sys" -version = "0.3.7" +version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ece97ea872ece730aed82664c424eb4c8291e1ff2480247ccf7409044bc6479f" +checksum = "57bcfdad1b858c2db7c38303a6d2ad4dfaf5eb53dfeb0910128b2c26d6158503" [[package]] name = "lioness" @@ -2284,9 +2326,9 @@ dependencies = [ [[package]] name = "lock_api" -version = "0.4.9" +version = "0.4.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "435011366fe56583b16cf956f9df0095b405b82d76425bc8981c0e22e60ec4df" +checksum = "c1cc9717a20b1bb222f333e6a92fd32f7d8a18ddc5a3191a11af45dcbf4dcd16" dependencies = [ "autocfg", "scopeguard", @@ -2294,12 +2336,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.17" +version = "0.4.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" -dependencies = [ - "cfg-if 1.0.0", -] +checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" [[package]] name = "memchr" @@ -2309,9 +2348,9 @@ checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" [[package]] name = "memoffset" -version = "0.8.0" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d61c719bcfbcf5d62b3a09efa6088de8c54bc0bfcd3ea7ae39fcc186108b8de1" +checksum = "5a634b1c61a95585bd15607c6ab0c4e5b226e695ff2800ba0cdccddf208c406c" dependencies = [ "autocfg", ] @@ -2345,14 +2384,13 @@ dependencies = [ [[package]] name = "mio" -version = "0.8.6" +version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b9d9a46eff5b4ff64b45a9e316a6d1e0bc719ef429cbec4dc630684212bfdf9" +checksum = "927a765cd3fc26206e66b296465fa9d3e5ab003e651c1b3c060e7956d96b19d2" dependencies = [ "libc", - "log", "wasi 0.11.0+wasi-snapshot-preview1", - "windows-sys 0.45.0", + "windows-sys", ] [[package]] @@ -2396,9 +2434,9 @@ dependencies = [ [[package]] name = "num-traits" -version = "0.2.15" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" +checksum = "f30b0abd723be7e2ffca1272140fac1a2f084c77ec3e123c192b66af1ee9e6c2" dependencies = [ "autocfg", "libm", @@ -2406,11 +2444,11 @@ dependencies = [ [[package]] name = "num_cpus" -version = "1.15.0" +version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fac9e2da13b5eb447a6ce3d392f23a29d8694bff781bf03a16cd9ac8697593b" +checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" dependencies = [ - "hermit-abi 0.2.6", + "hermit-abi 0.3.2", "libc", ] @@ -2489,7 +2527,7 @@ dependencies = [ "reqwest", "serde", "serde_json", - "sha2 0.10.6", + "sha2 0.10.7", "sqlx 0.6.3", "tap", "thiserror", @@ -2605,7 +2643,7 @@ dependencies = [ "log", "nym-network-defaults", "serde", - "toml 0.7.4", + "toml 0.7.6", "url", ] @@ -2649,7 +2687,7 @@ dependencies = [ name = "nym-crypto" version = "0.4.0" dependencies = [ - "aes 0.8.2", + "aes 0.8.3", "blake3", "bs58 0.4.0", "cipher 0.4.4", @@ -3136,10 +3174,19 @@ dependencies = [ ] [[package]] -name = "once_cell" -version = "1.17.1" +name = "object" +version = "0.31.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3" +checksum = "8bda667d9f2b5051b8833f59f3bf748b28ef54f850f4fcb389a252aa383866d1" +dependencies = [ + "memchr", +] + +[[package]] +name = "once_cell" +version = "1.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" [[package]] name = "opaque-debug" @@ -3155,11 +3202,11 @@ checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" [[package]] name = "openssl" -version = "0.10.52" +version = "0.10.56" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01b8574602df80f7b85fdfc5392fa884a4e3b3f4f35402c070ab34c3d3f78d56" +checksum = "729b745ad4a5575dd06a3e1af1414bd330ee561c01b3899eb584baeaa8def17e" dependencies = [ - "bitflags", + "bitflags 1.3.2", "cfg-if 1.0.0", "foreign-types", "libc", @@ -3176,7 +3223,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.16", + "syn 2.0.28", ] [[package]] @@ -3187,18 +3234,18 @@ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" [[package]] name = "openssl-src" -version = "111.26.0+1.1.1u" +version = "111.27.0+1.1.1v" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "efc62c9f12b22b8f5208c23a7200a442b2e5999f8bdf80233852122b5a4f6f37" +checksum = "06e8f197c82d7511c5b014030c9b1efeda40d7d5f99d23b4ceed3524a5e63f02" dependencies = [ "cc", ] [[package]] name = "openssl-sys" -version = "0.9.87" +version = "0.9.91" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e17f59264b2809d77ae94f0e1ebabc434773f370d6ca667bd223ea10e06cc7e" +checksum = "866b5f16f90776b9bb8dc1e1802ac6f0513de3a7a7465867bfbc563dc737faac" dependencies = [ "cc", "libc", @@ -3249,7 +3296,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" dependencies = [ "lock_api", - "parking_lot_core 0.9.7", + "parking_lot_core 0.9.8", ] [[package]] @@ -3268,15 +3315,15 @@ dependencies = [ [[package]] name = "parking_lot_core" -version = "0.9.7" +version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9069cbb9f99e3a5083476ccb29ceb1de18b9118cafa53e90c9551235de2b9521" +checksum = "93f00c865fe7cabf650081affecd3871070f26767e7b2070a3ffae14c654b447" dependencies = [ "cfg-if 1.0.0", "libc", - "redox_syscall 0.2.16", + "redox_syscall 0.3.5", "smallvec", - "windows-sys 0.45.0", + "windows-targets", ] [[package]] @@ -3292,9 +3339,9 @@ dependencies = [ [[package]] name = "paste" -version = "1.0.12" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f746c4065a8fa3fe23974dd82f15431cc8d40779821001404d10d2e79ca7d79" +checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c" [[package]] name = "pbkdf2" @@ -3346,15 +3393,15 @@ dependencies = [ [[package]] name = "percent-encoding" -version = "2.2.0" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e" +checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94" [[package]] name = "pest" -version = "2.6.0" +version = "2.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e68e84bfb01f0507134eac1e9b410a12ba379d064eab48c50ba4ce329a527b70" +checksum = "1acb4a4365a13f749a93f1a094a7805e5cfa0955373a9de860d962eaa3a5fe5a" dependencies = [ "thiserror", "ucd-trie", @@ -3362,9 +3409,9 @@ dependencies = [ [[package]] name = "pest_derive" -version = "2.6.0" +version = "2.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b79d4c71c865a25a4322296122e3924d30bc8ee0834c8bfc8b95f7f054afbfb" +checksum = "666d00490d4ac815001da55838c500eafb0320019bbaa44444137c48b443a853" dependencies = [ "pest", "pest_generator", @@ -3372,53 +3419,53 @@ dependencies = [ [[package]] name = "pest_generator" -version = "2.6.0" +version = "2.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c435bf1076437b851ebc8edc3a18442796b30f1728ffea6262d59bbe28b077e" +checksum = "68ca01446f50dbda87c1786af8770d535423fa8a53aec03b8f4e3d7eb10e0929" dependencies = [ "pest", "pest_meta", "proc-macro2", "quote", - "syn 2.0.16", + "syn 2.0.28", ] [[package]] name = "pest_meta" -version = "2.6.0" +version = "2.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "745a452f8eb71e39ffd8ee32b3c5f51d03845f99786fa9b68db6ff509c505411" +checksum = "56af0a30af74d0445c0bf6d9d051c979b516a1a5af790d251daee76005420a48" dependencies = [ "once_cell", "pest", - "sha2 0.10.6", + "sha2 0.10.7", ] [[package]] name = "pin-project" -version = "1.1.0" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c95a7476719eab1e366eaf73d0260af3021184f18177925b07f54b30089ceead" +checksum = "fda4ed1c6c173e3fc7a83629421152e01d7b1f9b7f65fb301e490e8cfc656422" dependencies = [ "pin-project-internal", ] [[package]] name = "pin-project-internal" -version = "1.1.0" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39407670928234ebc5e6e580247dd567ad73a3578460c5990f9503df207e8f07" +checksum = "4359fd9c9171ec6e8c62926d6faaf553a8dc3f64e1507e76da7911b4f6a04405" dependencies = [ "proc-macro2", "quote", - "syn 2.0.16", + "syn 2.0.28", ] [[package]] name = "pin-project-lite" -version = "0.2.9" +version = "0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" +checksum = "12cc1b0bf1727a77a54b6654e7b5f1af8604923edc8b81885f8ec92f9e3f0a05" [[package]] name = "pin-utils" @@ -3442,7 +3489,7 @@ version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" dependencies = [ - "der 0.7.7", + "der 0.7.8", "spki 0.7.2", ] @@ -3465,9 +3512,9 @@ dependencies = [ [[package]] name = "polyval" -version = "0.6.0" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ef234e08c11dfcb2e56f79fd70f6f2eb7f025c0ce2333e82f4f0518ecad30c6" +checksum = "d52cff9d1d4dee5fe6d03729099f4a310a41179e0a10dbf542039873f2e826fb" dependencies = [ "cfg-if 1.0.0", "cpufeatures", @@ -3517,9 +3564,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.58" +version = "1.0.66" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa1fb82fc0c281dd9671101b66b771ebbe1eaf967b96ac8740dcba4b70005ca8" +checksum = "18fb31db3f9bddb2ea821cde30a9f70117e3f119938b5ee630b7403aa6e2ead9" dependencies = [ "unicode-ident", ] @@ -3570,9 +3617,9 @@ checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3" [[package]] name = "quote" -version = "1.0.27" +version = "1.0.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f4f29d145265ec1c483c7c654450edde0bfe043d3938d6972630663356d9500" +checksum = "50f3b39ccfb720540debaa0164757101c08ecb8d326b15358ce76a62c7e85965" dependencies = [ "proc-macro2", ] @@ -3692,7 +3739,7 @@ version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" dependencies = [ - "bitflags", + "bitflags 1.3.2", ] [[package]] @@ -3701,7 +3748,7 @@ version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29" dependencies = [ - "bitflags", + "bitflags 1.3.2", ] [[package]] @@ -3717,9 +3764,21 @@ dependencies = [ [[package]] name = "regex" -version = "1.8.1" +version = "1.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af83e617f331cc6ae2da5443c602dfa5af81e517212d9d611a5b3ba1777b5370" +checksum = "81bc1d4caf89fac26a70747fe603c130093b53c773888797a6329091246d651a" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fed1ceff11a1dddaee50c9dc8e4938bd106e9d89ae372f192311e7da498e3b69" dependencies = [ "aho-corasick", "memchr", @@ -3728,9 +3787,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.7.1" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5996294f19bd3aae0453a862ad728f60e6600695733dd5df01da90c54363a3c" +checksum = "e5ea92a5b6195c6ef2a0295ea818b312502c6fc94dde986c5553242e18fd4ce2" [[package]] name = "reqwest" @@ -3814,27 +3873,32 @@ dependencies = [ "digest 0.10.7", ] +[[package]] +name = "rustc-demangle" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" + [[package]] name = "rustc_version" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" dependencies = [ - "semver 1.0.17", + "semver 1.0.18", ] [[package]] name = "rustix" -version = "0.37.19" +version = "0.38.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "acf8729d8542766f1b2cf77eb034d52f40d375bb8b615d0b147089946e16613d" +checksum = "19ed4fa021d81c8392ce04db050a3da9a60299050b7ae1cf482d862b54a7218f" dependencies = [ - "bitflags", + "bitflags 2.4.0", "errno", - "io-lifetimes", "libc", "linux-raw-sys", - "windows-sys 0.48.0", + "windows-sys", ] [[package]] @@ -3876,24 +3940,24 @@ dependencies = [ [[package]] name = "rustls-pemfile" -version = "1.0.2" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d194b56d58803a43635bdc398cd17e383d6f71f9182b9a192c127ca42494a59b" +checksum = "2d3987094b1d07b653b7dfdc3f70ce9a1da9c51ac18c1b06b662e4f9a0e9f4b2" dependencies = [ "base64 0.21.2", ] [[package]] name = "rustversion" -version = "1.0.12" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f3208ce4d8448b3f3e7d168a73f5e0c43a61e32930de3bceeccedb388b6bf06" +checksum = "7ffc183a10b4478d04cbbbfc96d0873219d962dd5accaff2ffbd4ceb7df837f4" [[package]] name = "ryu" -version = "1.0.13" +version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f91339c0467de62360649f8d3e185ca8de4224ff281f66000de5eb2a77a79041" +checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741" [[package]] name = "same-file" @@ -3906,11 +3970,11 @@ dependencies = [ [[package]] name = "schannel" -version = "0.1.21" +version = "0.1.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "713cfb06c7059f3588fb8044c0fad1d09e3c01d225e25b9220dbfdcf16dbb1b3" +checksum = "0c3733bf4cf7ea0880754e19cb5a462007c4a8c1914bff372ccc95b464f1df88" dependencies = [ - "windows-sys 0.42.0", + "windows-sys", ] [[package]] @@ -3920,7 +3984,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "02c613288622e5f0c3fdc5dbd4db1c5fbe752746b1d1a56a0630b78fd00de44f" dependencies = [ "dyn-clone", - "indexmap", + "indexmap 1.9.3", "schemars_derive", "serde", "serde_json", @@ -3946,9 +4010,9 @@ checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" [[package]] name = "scopeguard" -version = "1.1.0" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "sct" @@ -3991,7 +4055,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc" dependencies = [ "base16ct 0.2.0", - "der 0.7.7", + "der 0.7.8", "generic-array 0.14.7", "pkcs8 0.10.2", "subtle 2.4.1", @@ -4000,11 +4064,11 @@ dependencies = [ [[package]] name = "security-framework" -version = "2.9.0" +version = "2.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca2855b3715770894e67cbfa3df957790aa0c9edc3bf06efa1a84d77fa0839d1" +checksum = "05b64fb303737d99b81884b2c63433e9ae28abebe5eb5045dcdd175dc2ecf4de" dependencies = [ - "bitflags", + "bitflags 1.3.2", "core-foundation", "core-foundation-sys", "libc", @@ -4013,9 +4077,9 @@ dependencies = [ [[package]] name = "security-framework-sys" -version = "2.9.0" +version = "2.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f51d0c0d83bec45f16480d0ce0058397a69e48fcdc52d1dc8855fb68acbd31a7" +checksum = "e932934257d3b408ed8f30db49d85ea163bfe74961f017f405b025af298f0c7a" dependencies = [ "core-foundation-sys", "libc", @@ -4032,9 +4096,9 @@ dependencies = [ [[package]] name = "semver" -version = "1.0.17" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bebd363326d05ec3e2f532ab7660680f3b02130d780c299bca73469d521bc0ed" +checksum = "b0293b4b29daaf487284529cc2f5675b8e57c61f70167ba415a463651fd6a918" [[package]] name = "semver-parser" @@ -4047,9 +4111,9 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.163" +version = "1.0.183" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2113ab51b87a539ae008b5c6c02dc020ffa39afd2d83cffcb3f4eb2722cebec2" +checksum = "32ac8da02677876d532745a130fc9d8e6edfa81a269b107c5b00829b91d8eb3c" dependencies = [ "serde_derive", ] @@ -4076,22 +4140,22 @@ dependencies = [ [[package]] name = "serde_bytes" -version = "0.11.9" +version = "0.11.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "416bda436f9aab92e02c8e10d49a15ddd339cea90b6e340fe51ed97abb548294" +checksum = "ab33ec92f677585af6d88c65593ae2375adde54efdbf16d597f2cbc7a6d368ff" dependencies = [ "serde", ] [[package]] name = "serde_derive" -version = "1.0.163" +version = "1.0.183" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c805777e3930c8883389c602315a24224bcc738b63905ef87cd1420353ea93e" +checksum = "aafe972d60b0b9bee71a91b92fee2d4fb3c9d7e8f6b179aa99f27203d99a4816" dependencies = [ "proc-macro2", "quote", - "syn 2.0.16", + "syn 2.0.28", ] [[package]] @@ -4107,9 +4171,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.96" +version = "1.0.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "057d394a50403bcac12672b2b18fb387ab6d289d957dab67dd201875391e52f1" +checksum = "076066c5f1078eac5b722a31827a8832fe108bed65dfa75e233c89f8206e976c" dependencies = [ "itoa", "ryu", @@ -4118,20 +4182,20 @@ dependencies = [ [[package]] name = "serde_repr" -version = "0.1.12" +version = "0.1.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bcec881020c684085e55a25f7fd888954d56609ef363479dc5a1305eb0d40cab" +checksum = "8725e1dfadb3a50f7e5ce0b1a540466f6ed3fe7a0fca2ac2b8b831d31316bd00" dependencies = [ "proc-macro2", "quote", - "syn 2.0.16", + "syn 2.0.28", ] [[package]] name = "serde_spanned" -version = "0.6.2" +version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93107647184f6027e3b7dcb2e11034cf95ffa1e3a682c67951963ac69c1c007d" +checksum = "96426c9936fd7a0124915f9185ea1d20aa9445cc9821142f0a73bc9207a2e186" dependencies = [ "serde", ] @@ -4187,9 +4251,9 @@ dependencies = [ [[package]] name = "sha2" -version = "0.10.6" +version = "0.10.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82e6b795fe2e3b1e845bafcb27aa35405c4d47cdfc92af5fc8d3002f76cebdc0" +checksum = "479fb9d862239e610720565ca91403019f2f00410f1864c5aa7479b950a76ed8" dependencies = [ "cfg-if 1.0.0", "cpufeatures", @@ -4236,9 +4300,9 @@ dependencies = [ [[package]] name = "smallvec" -version = "1.10.0" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0" +checksum = "62bb4feee49fdd9f707ef802e22365a35de4b7b299de4763d44bfea899442ff9" [[package]] name = "socket2" @@ -4250,6 +4314,16 @@ dependencies = [ "winapi", ] +[[package]] +name = "socket2" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2538b18701741680e0322a2302176d3253a35388e2e62f172f64f4f16605f877" +dependencies = [ + "libc", + "windows-sys", +] + [[package]] name = "sphinx-packet" version = "0.1.0" @@ -4306,7 +4380,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9d1e996ef02c474957d681f1b05213dfb0abab947b446a62d37770b23500184a" dependencies = [ "base64ct", - "der 0.7.7", + "der 0.7.8", ] [[package]] @@ -4359,7 +4433,7 @@ checksum = "e48c61941ccf5ddcada342cd59e3e5173b007c509e1e8e990dafc830294d9dc5" dependencies = [ "ahash 0.7.6", "atoi 0.4.0", - "bitflags", + "bitflags 1.3.2", "byteorder", "bytes", "crc 2.1.0", @@ -4374,7 +4448,7 @@ dependencies = [ "futures-util", "hashlink 0.7.0", "hex", - "indexmap", + "indexmap 1.9.3", "itoa", "libc", "libsqlite3-sys", @@ -4384,7 +4458,7 @@ dependencies = [ "paste", "percent-encoding", "rustls 0.19.1", - "sha2 0.10.6", + "sha2 0.10.7", "smallvec", "sqlformat 0.1.8", "sqlx-rt 0.5.13", @@ -4404,7 +4478,7 @@ checksum = "fa8241483a83a3f33aa5fff7e7d9def398ff9990b2752b6c6112b83c6d246029" dependencies = [ "ahash 0.7.6", "atoi 1.0.0", - "bitflags", + "bitflags 1.3.2", "byteorder", "bytes", "crc 3.0.1", @@ -4418,9 +4492,9 @@ dependencies = [ "futures-executor", "futures-intrusive", "futures-util", - "hashlink 0.8.2", + "hashlink 0.8.3", "hex", - "indexmap", + "indexmap 1.9.3", "itoa", "libc", "libsqlite3-sys", @@ -4431,7 +4505,7 @@ dependencies = [ "percent-encoding", "rustls 0.20.8", "rustls-pemfile", - "sha2 0.10.6", + "sha2 0.10.7", "smallvec", "sqlformat 0.2.1", "sqlx-rt 0.6.3", @@ -4454,7 +4528,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "sha2 0.10.6", + "sha2 0.10.7", "sqlx-core 0.5.13", "sqlx-rt 0.5.13", "syn 1.0.109", @@ -4473,7 +4547,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "sha2 0.10.6", + "sha2 0.10.7", "sqlx-core 0.6.3", "sqlx-rt 0.6.3", "syn 1.0.109", @@ -4504,9 +4578,9 @@ dependencies = [ [[package]] name = "stringprep" -version = "0.1.2" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ee348cb74b87454fff4b551cbf727025810a004f88aeacae7f85b87f4e9a1c1" +checksum = "db3737bde7edce97102e0e2b15365bf7a20bfdb5f60f4f9e8d7004258a51a8da" dependencies = [ "unicode-bidi", "unicode-normalization", @@ -4558,9 +4632,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.16" +version = "2.0.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6f671d4b5ffdb8eadec19c0ae67fe2639df8684bd7bc4b83d986b8db549cf01" +checksum = "04361975b3f5e348b2189d8dc55bc942f278b2d482a6a0365de5bdd62d351567" dependencies = [ "proc-macro2", "quote", @@ -4575,15 +4649,15 @@ checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" [[package]] name = "tempfile" -version = "3.5.0" +version = "3.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9fbec84f381d5795b08656e4912bec604d162bff9291d6189a78f4c8ab87998" +checksum = "dc02fddf48964c42031a0b3fe0428320ecf3a73c401040fc0096f97794310651" dependencies = [ "cfg-if 1.0.0", "fastrand", "redox_syscall 0.3.5", "rustix", - "windows-sys 0.45.0", + "windows-sys", ] [[package]] @@ -4594,7 +4668,7 @@ checksum = "3f0a7d05cf78524782337f8edd55cbc578d159a16ad4affe2135c92f7dbac7f0" dependencies = [ "bytes", "digest 0.10.7", - "ed25519 2.2.1", + "ed25519 2.2.2", "ed25519-consensus", "flex-error", "futures", @@ -4608,7 +4682,7 @@ dependencies = [ "serde_bytes", "serde_json", "serde_repr", - "sha2 0.10.6", + "sha2 0.10.7", "signature 2.1.0", "subtle 2.4.1", "subtle-encoding", @@ -4666,7 +4740,7 @@ dependencies = [ "hyper-rustls", "peg", "pin-project", - "semver 1.0.17", + "semver 1.0.18", "serde", "serde_bytes", "serde_json", @@ -4695,30 +4769,31 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.40" +version = "1.0.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "978c9a314bd8dc99be594bc3c175faaa9794be04a5a5e153caba6915336cebac" +checksum = "611040a08a0439f8248d1990b111c95baa9c704c805fa1f62104b39655fd7f90" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.40" +version = "1.0.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9456a42c5b0d803c8cd86e73dd7cc9edd429499f37a3550d286d5e86720569f" +checksum = "090198534930841fab3a5d1bb637cde49e339654e606195f8d9c76eeb081dc96" dependencies = [ "proc-macro2", "quote", - "syn 2.0.16", + "syn 2.0.28", ] [[package]] name = "time" -version = "0.3.21" +version = "0.3.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f3403384eaacbca9923fa06940178ac13e4edb725486d70e8e15881d0c836cc" +checksum = "b0fdd63d58b18d663fbdf70e049f00a22c8e42be082203be7f26589213cd75ea" dependencies = [ + "deranged", "itoa", "js-sys", "serde", @@ -4734,9 +4809,9 @@ checksum = "7300fbefb4dadc1af235a9cef3737cea692a9d97e1b9cbcd4ebdae6f8868e6fb" [[package]] name = "time-macros" -version = "0.2.9" +version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "372950940a5f07bf38dbe211d7283c9e6d7327df53794992d293e534c733d09b" +checksum = "eb71511c991639bb078fd5bf97757e03914361c48100d52878b8e52b46fb92cd" dependencies = [ "time-core", ] @@ -4758,20 +4833,20 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.28.1" +version = "1.31.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0aa32867d44e6f2ce3385e89dceb990188b8bb0fb25b0cf576647a6f98ac5105" +checksum = "40de3a2ba249dcb097e01be5e67a5ff53cf250397715a071a81543e8a832a920" dependencies = [ - "autocfg", + "backtrace", "bytes", "libc", "mio", "num_cpus", "pin-project-lite", "signal-hook-registry", - "socket2", + "socket2 0.5.3", "tokio-macros", - "windows-sys 0.48.0", + "windows-sys", ] [[package]] @@ -4782,7 +4857,7 @@ checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.16", + "syn 2.0.28", ] [[package]] @@ -4868,9 +4943,9 @@ dependencies = [ [[package]] name = "toml" -version = "0.7.4" +version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6135d499e69981f9ff0ef2167955a5333c35e36f6937d382974566b3d5b94ec" +checksum = "c17e963a819c331dcacd7ab957d80bc2b9a9c1e71c804826d2f283dd65306542" dependencies = [ "serde", "serde_spanned", @@ -4880,20 +4955,20 @@ dependencies = [ [[package]] name = "toml_datetime" -version = "0.6.2" +version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a76a9312f5ba4c2dec6b9161fdf25d87ad8a09256ccea5a556fef03c706a10f" +checksum = "7cda73e2f1397b1262d6dfdcef8aafae14d1de7748d66822d3bfeeb6d03e5e4b" dependencies = [ "serde", ] [[package]] name = "toml_edit" -version = "0.19.10" +version = "0.19.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2380d56e8670370eee6566b0bfd4265f65b3f432e8c6d85623f728d4fa31f739" +checksum = "f8123f27e969974a3dfba720fdb560be359f57b44302d280ba72e76a74480e8a" dependencies = [ - "indexmap", + "indexmap 2.0.0", "serde", "serde_spanned", "toml_datetime", @@ -4960,9 +5035,9 @@ checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba" [[package]] name = "ucd-trie" -version = "0.1.5" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e79c4d996edb816c91e4308506774452e55e95c3c9de07b6729e17e15a5ef81" +checksum = "ed646292ffc8188ef8ea4d1e0e0150fb15a5c2e12ad9b8fc191ae7a8a7f3c4b9" [[package]] name = "unicode-bidi" @@ -4972,9 +5047,9 @@ checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460" [[package]] name = "unicode-ident" -version = "1.0.8" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5464a87b239f13a63a501f2701565754bae92d243d4bb7eb12f6d57d2269bf4" +checksum = "301abaae475aa91687eb82514b328ab47a211a533026cb25fc3e519b86adfc3c" [[package]] name = "unicode-normalization" @@ -4999,9 +5074,9 @@ checksum = "39ec24b3121d976906ece63c9daad25b85969647682eee313cb5779fdd69e14e" [[package]] name = "universal-hash" -version = "0.5.0" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d3160b73c9a19f7e2939a2fdad446c57c1bbbbf4d919d3213ff1267a580d8b5" +checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea" dependencies = [ "crypto-common", "subtle 2.4.1", @@ -5015,9 +5090,9 @@ checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" [[package]] name = "url" -version = "2.3.1" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d68c799ae75762b8c3fe375feb6600ef5602c883c5d21eb51c09f22b83c4643" +checksum = "50bff7831e19200a85b17131d085c25d7811bc4e186efdaf54bbd132994a88cb" dependencies = [ "form_urlencoded", "idna", @@ -5045,9 +5120,9 @@ checksum = "bc5cf98d8186244414c848017f0e2676b3fcb46807f6668a97dfe67359a3c4b7" [[package]] name = "uuid" -version = "1.3.3" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "345444e32442451b267fc254ae85a209c64be56d2890e601a0c37ff0c3c5ecd2" +checksum = "79daa5ed5740825c40b389c5e50312b9c86df53fccd33f281df655642b43869d" dependencies = [ "getrandom 0.2.10", "wasm-bindgen", @@ -5094,11 +5169,10 @@ dependencies = [ [[package]] name = "want" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ce8a968cb1cd110d136ff8b819a556d6fb6d919363c61534f6860c7eb172ba0" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" dependencies = [ - "log", "try-lock", ] @@ -5329,147 +5403,66 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" -[[package]] -name = "windows-sys" -version = "0.42.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" -dependencies = [ - "windows_aarch64_gnullvm 0.42.2", - "windows_aarch64_msvc 0.42.2", - "windows_i686_gnu 0.42.2", - "windows_i686_msvc 0.42.2", - "windows_x86_64_gnu 0.42.2", - "windows_x86_64_gnullvm 0.42.2", - "windows_x86_64_msvc 0.42.2", -] - -[[package]] -name = "windows-sys" -version = "0.45.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" -dependencies = [ - "windows-targets 0.42.2", -] - [[package]] name = "windows-sys" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" dependencies = [ - "windows-targets 0.48.0", + "windows-targets", ] [[package]] name = "windows-targets" -version = "0.42.2" +version = "0.48.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" +checksum = "05d4b17490f70499f20b9e791dcf6a299785ce8af4d709018206dc5b4953e95f" dependencies = [ - "windows_aarch64_gnullvm 0.42.2", - "windows_aarch64_msvc 0.42.2", - "windows_i686_gnu 0.42.2", - "windows_i686_msvc 0.42.2", - "windows_x86_64_gnu 0.42.2", - "windows_x86_64_gnullvm 0.42.2", - "windows_x86_64_msvc 0.42.2", + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", ] -[[package]] -name = "windows-targets" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b1eb6f0cd7c80c79759c929114ef071b87354ce476d9d94271031c0497adfd5" -dependencies = [ - "windows_aarch64_gnullvm 0.48.0", - "windows_aarch64_msvc 0.48.0", - "windows_i686_gnu 0.48.0", - "windows_i686_msvc 0.48.0", - "windows_x86_64_gnu 0.48.0", - "windows_x86_64_gnullvm 0.48.0", - "windows_x86_64_msvc 0.48.0", -] - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" - [[package]] name = "windows_aarch64_gnullvm" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc" -[[package]] -name = "windows_aarch64_msvc" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" - [[package]] name = "windows_aarch64_msvc" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3" -[[package]] -name = "windows_i686_gnu" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" - [[package]] name = "windows_i686_gnu" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241" -[[package]] -name = "windows_i686_msvc" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" - [[package]] name = "windows_i686_msvc" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00" -[[package]] -name = "windows_x86_64_gnu" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" - [[package]] name = "windows_x86_64_gnu" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1" -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" - [[package]] name = "windows_x86_64_gnullvm" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953" -[[package]] -name = "windows_x86_64_msvc" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" - [[package]] name = "windows_x86_64_msvc" version = "0.48.0" @@ -5478,9 +5471,9 @@ checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" [[package]] name = "winnow" -version = "0.4.6" +version = "0.5.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61de7bac303dc551fe038e2b3cef0f571087a47571ea6e79a87692ac99b99699" +checksum = "5504cc7644f4b593cbc05c4a55bf9bd4e94b867c3c0bd440934174d50482427d" dependencies = [ "memchr", ] @@ -5532,5 +5525,5 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn 2.0.16", + "syn 2.0.28", ] diff --git a/common/client-libs/validator-client/Cargo.toml b/common/client-libs/validator-client/Cargo.toml index 5ac785a9c7..793adaf3cb 100644 --- a/common/client-libs/validator-client/Cargo.toml +++ b/common/client-libs/validator-client/Cargo.toml @@ -22,13 +22,13 @@ nym-group-contract-common = { path = "../../cosmwasm-smart-contracts/group-contr nym-service-provider-directory-common = { path = "../../cosmwasm-smart-contracts/service-provider-directory" } serde = { workspace = true, features = ["derive"] } serde_json = { workspace = true } -reqwest = { version = "0.11", features = ["json"] } +reqwest = { workspace = true, features = ["json"] } thiserror = "1" log = { workspace = true } url = { version = "2.2", features = ["serde"] } tokio = { version = "1.24.1", features = ["sync", "time"] } futures = "0.3" -openssl = { version = "0.10", features = ["vendored"], optional = true } +openssl = { version = "^0.10.55", features = ["vendored"], optional = true } nym-coconut-interface = { path = "../../coconut-interface" } nym-network-defaults = { path = "../../network-defaults" } diff --git a/common/socks5-client-core/Cargo.toml b/common/socks5-client-core/Cargo.toml index 2e1aa52b32..4c7df38c8e 100644 --- a/common/socks5-client-core/Cargo.toml +++ b/common/socks5-client-core/Cargo.toml @@ -11,7 +11,7 @@ futures = "0.3" log = { workspace = true } pin-project = "1.0" rand = { version = "0.7.3", features = ["wasm-bindgen"] } -reqwest = "0.11.4" +reqwest = { workspace = true } schemars = { version = "0.8", features = ["preserve_order"] } serde = { workspace = true, features = ["derive"] } # for config serialization/deserialization tap = "1.0.1" diff --git a/common/statistics/Cargo.toml b/common/statistics/Cargo.toml index 29573f1e06..1e762b88f1 100644 --- a/common/statistics/Cargo.toml +++ b/common/statistics/Cargo.toml @@ -11,7 +11,7 @@ edition = "2021" [dependencies] async-trait = { workspace = true } log = { workspace = true } -reqwest = { version = "0.11", features = ["json"] } +reqwest = { workspace = true, features = ["json"] } serde = { version = "1.0", features = ["derive"] } serde_json = "1" sqlx = { version = "0.5", features = ["runtime-tokio-rustls", "chrono"]} diff --git a/common/types/Cargo.toml b/common/types/Cargo.toml index cbf12b4364..35c7cd228a 100644 --- a/common/types/Cargo.toml +++ b/common/types/Cargo.toml @@ -10,7 +10,7 @@ rust-version = "1.58" eyre = "0.6.5" log = { workspace = true } itertools = "0.10" -reqwest = "0.11.9" +reqwest = { workspace = true } schemars = "0.8" serde = { version = "1.0", features = ["derive"] } serde_json = { workspace = true } diff --git a/explorer-api/Cargo.toml b/explorer-api/Cargo.toml index 04e7255d01..148a8b7d03 100644 --- a/explorer-api/Cargo.toml +++ b/explorer-api/Cargo.toml @@ -20,7 +20,7 @@ pretty_env_logger = "0.4.0" rand = "0.8.5" rand_pcg = "0.3.1" rand_seeder = "0.2.3" -reqwest = "0.11.4" +reqwest = { workspace = true } rocket = { version = "0.5.0-rc.2", features = ["json"] } rocket_cors = { git="https://github.com/lawliet89/rocket_cors", rev="dfd3662c49e2f6fc37df35091cb94d82f7fb5915" } rocket_okapi = { version = "0.8.0-rc.2", features = ["swagger"] } diff --git a/nym-api/Cargo.toml b/nym-api/Cargo.toml index 50568b440a..7de7da3f4e 100644 --- a/nym-api/Cargo.toml +++ b/nym-api/Cargo.toml @@ -30,7 +30,7 @@ pin-project = "1.0" pretty_env_logger = "0.4.0" rand = "0.8.5" rand-07 = { package = "rand", version = "0.7.3" } # required for compatibility -reqwest = { version = "0.11.11", features = ["json"] } +reqwest = { workspace = true, features = ["json"] } rocket = { version = "0.5.0-rc.2", features = ["json"] } rocket_cors = { git = "https://github.com/lawliet89/rocket_cors", rev = "dfd3662c49e2f6fc37df35091cb94d82f7fb5915" } serde = "1.0" diff --git a/nym-connect/desktop/Cargo.lock b/nym-connect/desktop/Cargo.lock index 6ef76b497a..8e2379646a 100644 --- a/nym-connect/desktop/Cargo.lock +++ b/nym-connect/desktop/Cargo.lock @@ -25,9 +25,9 @@ checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" [[package]] name = "aead" -version = "0.5.1" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c192eb8f11fc081b0fe4259ba5af04217d4e0faddd02417310a927911abd7c8" +checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0" dependencies = [ "crypto-common", "generic-array 0.14.7", @@ -48,9 +48,9 @@ dependencies = [ [[package]] name = "aes" -version = "0.8.2" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "433cfd6710c9986c576a25ca913c39d66a6474107b406f34f91d4a8923395241" +checksum = "ac1f845298e95f983ff1944b728ae08b8cebab80d684f0a832ed0fc74dfa27e2" dependencies = [ "cfg-if", "cipher 0.4.4", @@ -69,10 +69,21 @@ dependencies = [ ] [[package]] -name = "aho-corasick" -version = "0.7.20" +name = "ahash" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc936419f96fa211c1b9166887b38e5e40b19958e5b895be7c1f93adec7071ac" +checksum = "2c99f64d1e06488f620f932677e24bc6e2897582980441ae90a671415bd7ec2f" +dependencies = [ + "cfg-if", + "once_cell", + "version_check", +] + +[[package]] +name = "aho-corasick" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86b8f9420f797f2d9e935edf629310eb938a0d839f984e25327f3c7eed22300c" dependencies = [ "memchr", ] @@ -93,10 +104,80 @@ dependencies = [ ] [[package]] -name = "anyhow" -version = "1.0.70" +name = "allocator-api2" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7de8ce5e0f9f8d88245311066a578d72b7af3e7088f32783804676302df237e4" +checksum = "0942ffc6dcaadf03badf6e6a2d0228460359d5e34b57ccdc720b7382dfbd5ec5" + +[[package]] +name = "android-tzdata" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + +[[package]] +name = "anstream" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ca84f3628370c59db74ee214b3263d58f9aadd9b4fe7e711fd87dc452b7f163" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is-terminal", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a30da5c5f2d5e72842e00bcb57657162cdabef0931f40e2deb9b4140440cecd" + +[[package]] +name = "anstyle-parse" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "938874ff5980b03a87c5524b3ae5b59cf99b1d6bc836848df7bc5ada9643c333" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ca11d4be1bab0c8bc8734a9aa7bf4ee8316d462a08c6ac5052f888fef5b494b" +dependencies = [ + "windows-sys 0.48.0", +] + +[[package]] +name = "anstyle-wincon" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c677ab05e09154296dd37acecd46420c17b9713e8366facafa8fc0885167cf4c" +dependencies = [ + "anstyle", + "windows-sys 0.48.0", +] + +[[package]] +name = "anyhow" +version = "1.0.72" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b13c32d80ecc7ab747b80c3784bce54ee8a7a0cc4fbda9bf4cda2cf6fe90854" dependencies = [ "backtrace", ] @@ -115,19 +196,130 @@ checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b" [[package]] name = "arrayvec" -version = "0.7.2" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8da52d66c7071e2e3fa2a1e5c6d088fec47b593032b254f5e980de8ea54454d6" +checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711" [[package]] -name = "async-trait" -version = "0.1.67" +name = "async-broadcast" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86ea188f25f0255d8f92797797c97ebf5631fa88178beb1a46fdf5622c9a00e4" +checksum = "7c48ccdbf6ca6b121e0f586cbc0e73ae440e56c67c30fa0873b4e110d9c26d2b" +dependencies = [ + "event-listener", + "futures-core", +] + +[[package]] +name = "async-channel" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81953c529336010edd6d8e358f886d9581267795c61b19475b71314bffa46d35" +dependencies = [ + "concurrent-queue", + "event-listener", + "futures-core", +] + +[[package]] +name = "async-executor" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fa3dc5f2a8564f07759c008b9109dc0d39de92a88d5588b8a5036d286383afb" +dependencies = [ + "async-lock", + "async-task", + "concurrent-queue", + "fastrand 1.9.0", + "futures-lite", + "slab", +] + +[[package]] +name = "async-fs" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "279cf904654eeebfa37ac9bb1598880884924aab82e290aa65c9e77a0e142e06" +dependencies = [ + "async-lock", + "autocfg", + "blocking", + "futures-lite", +] + +[[package]] +name = "async-io" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fc5b45d93ef0529756f812ca52e44c221b35341892d3dcc34132ac02f3dd2af" +dependencies = [ + "async-lock", + "autocfg", + "cfg-if", + "concurrent-queue", + "futures-lite", + "log", + "parking", + "polling", + "rustix 0.37.23", + "slab", + "socket2 0.4.9", + "waker-fn", +] + +[[package]] +name = "async-lock" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "287272293e9d8c41773cec55e365490fe034813a2f172f502d6ddcf75b2f582b" +dependencies = [ + "event-listener", +] + +[[package]] +name = "async-process" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a9d28b1d97e08915212e2e45310d47854eafa69600756fc735fb788f75199c9" +dependencies = [ + "async-io", + "async-lock", + "autocfg", + "blocking", + "cfg-if", + "event-listener", + "futures-lite", + "rustix 0.37.23", + "signal-hook", + "windows-sys 0.48.0", +] + +[[package]] +name = "async-recursion" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e97ce7de6cf12de5d7226c73f5ba9811622f4db3a5b91b55c53e987e5f91cba" dependencies = [ "proc-macro2", "quote", - "syn 2.0.5", + "syn 2.0.28", +] + +[[package]] +name = "async-task" +version = "4.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecc7ab41815b3c653ccd2978ec3255c81349336702dfdf62ee6f7069b12a3aae" + +[[package]] +name = "async-trait" +version = "0.1.73" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc00ceb34980c03614e35a3a4e218276a0a824e911d07651cd0d858a51e8c0f0" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.28", ] [[package]] @@ -151,7 +343,7 @@ dependencies = [ "glib-sys", "gobject-sys", "libc", - "system-deps 6.0.3", + "system-deps 6.1.1", ] [[package]] @@ -173,20 +365,10 @@ dependencies = [ ] [[package]] -name = "attohttpc" -version = "0.22.0" +name = "atomic-waker" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fcf00bc6d5abb29b5f97e3c61a90b6d3caa12f3faf897d4a3e3607c050a35a7" -dependencies = [ - "flate2", - "http", - "log", - "native-tls", - "serde", - "serde_json", - "serde_urlencoded", - "url", -] +checksum = "1181e1e0d1fce796a03db1ae795d67167da795f9cf4a39c37589e85ef57f26d3" [[package]] name = "atty" @@ -215,7 +397,7 @@ dependencies = [ "cc", "cfg-if", "libc", - "miniz_oxide 0.7.1", + "miniz_oxide", "object", "rustc-demangle", ] @@ -272,7 +454,7 @@ dependencies = [ "pbkdf2", "rand_core 0.6.4", "ripemd", - "sha2 0.10.6", + "sha2 0.10.7", "subtle 2.4.1", "zeroize", ] @@ -305,9 +487,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.0.2" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "487f1e0fcbe47deb8b0574e646def1c903389d95241dd1bbcc6ce4a715dfc0c1" +checksum = "b4682ae6287fcf752ecaabbfcc7b6f9b72aa33933dc23a554d853aea8eea8635" [[package]] name = "bitvec" @@ -335,12 +517,12 @@ dependencies = [ [[package]] name = "blake3" -version = "1.3.3" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42ae2468a89544a466886840aa467a25b766499f4f04bf7d9fcd10ecee9fccef" +checksum = "199c42ab6972d92c9f8995f086273d25c42fc0f7b2a1fcefba465c1352d25ba5" dependencies = [ "arrayref", - "arrayvec 0.7.2", + "arrayvec 0.7.4", "cc", "cfg-if", "constant_time_eq", @@ -371,6 +553,21 @@ dependencies = [ "generic-array 0.14.7", ] +[[package]] +name = "blocking" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77231a1c8f801696fc0123ec6150ce92cffb8e164a02afb9c8ddee0e9b65ad65" +dependencies = [ + "async-channel", + "async-lock", + "async-task", + "atomic-waker", + "fastrand 1.9.0", + "futures-lite", + "log", +] + [[package]] name = "bls12_381" version = "0.5.0" @@ -438,14 +635,14 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f5353f36341f7451062466f0b755b96ac3a9547e4d7f6b70d603fc721a7d7896" dependencies = [ - "sha2 0.10.6", + "sha2 0.10.7", ] [[package]] name = "bstr" -version = "1.4.0" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3d4260bcc2e8fc9df1eac4919a720effeb63a3f0952f5bf4944adfa18897f09" +checksum = "6798148dccfbff0fae41c7574d2fa8f1ef3492fba0face179de5d8d447d67b05" dependencies = [ "memchr", "serde", @@ -453,9 +650,9 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.12.0" +version = "3.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d261e256854913907f67ed06efbc3338dfe6179796deefc1ff763fc1aee5535" +checksum = "a3e2c3daef883ecc1b5d58c15adae93470a91d425f3532ba1695849656af3fc1" [[package]] name = "byte-tools" @@ -505,26 +702,27 @@ checksum = "3c55d429bef56ac9172d25fecb85dc8068307d17acd74b377866b7a1ef25d3c8" dependencies = [ "glib-sys", "libc", - "system-deps 6.0.3", + "system-deps 6.1.1", ] [[package]] name = "cargo_toml" -version = "0.13.3" +version = "0.15.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "497049e9477329f8f6a559972ee42e117487d01d1e8c2cc9f836ea6fa23a9e1a" +checksum = "599aa35200ffff8f04c1925aa1acc92fa2e08874379ef42e210a80e527e60838" dependencies = [ "serde", - "toml 0.5.11", + "toml 0.7.6", ] [[package]] name = "cc" -version = "1.0.79" +version = "1.0.82" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f" +checksum = "305fe645edc1442a0fa8b6726ba61d422798d37a52e12eaecf4b022ebbb88f01" dependencies = [ "jobserver", + "libc", ] [[package]] @@ -535,12 +733,13 @@ checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" [[package]] name = "cfb" -version = "0.6.1" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74f89d248799e3f15f91b70917f65381062a01bb8e222700ea0e5a7ff9785f9c" +checksum = "d38f2da7a0a2c4ccf0065be06397cc26a81f4e528be095826eee9d4adbb8c60f" dependencies = [ "byteorder", - "uuid 0.8.2", + "fnv", + "uuid 1.4.1", ] [[package]] @@ -554,11 +753,12 @@ dependencies = [ [[package]] name = "cfg-expr" -version = "0.11.0" +version = "0.15.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0357a6402b295ca3a86bc148e84df46c02e41f41fef186bda662557ef6328aa" +checksum = "b40ccee03b5175c18cde8f37e7d2a33bcef6f8ec8f7cc0d81090d1bb380949c9" dependencies = [ "smallvec", + "target-lexicon", ] [[package]] @@ -579,9 +779,9 @@ dependencies = [ [[package]] name = "chacha20" -version = "0.9.0" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7fc89c7c5b9e7a02dfe45cd2367bae382f9ed31c61ca8debe5f827c420a2f08" +checksum = "c3613f74bd2eac03dad61bd53dbe620703d4371614fe0bc3b9f04dd36fe4e818" dependencies = [ "cfg-if", "cipher 0.4.4", @@ -601,6 +801,19 @@ dependencies = [ "zeroize", ] +[[package]] +name = "chrono" +version = "0.4.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec837a71355b28f6556dbd569b37b3f363091c0bd4b2e735674521b4c5fd9bc5" +dependencies = [ + "android-tzdata", + "iana-time-zone", + "num-traits", + "serde", + "winapi", +] + [[package]] name = "cipher" version = "0.3.0" @@ -623,33 +836,41 @@ dependencies = [ [[package]] name = "clap" -version = "4.1.11" +version = "4.3.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42dfd32784433290c51d92c438bb72ea5063797fc3cc9a21a8c4346bebbb2098" +checksum = "c27cdf28c0f604ba3f512b0c9a409f8de8513e4816705deb0498b627e7c3a3fd" dependencies = [ - "bitflags 2.0.2", + "clap_builder", "clap_derive", - "clap_lex", - "is-terminal", "once_cell", +] + +[[package]] +name = "clap_builder" +version = "4.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08a9f1ab5e9f01a9b81f202e8562eb9a10de70abf9eaeac1be465c28b75aa4aa" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", "strsim", - "termcolor", ] [[package]] name = "clap_complete" -version = "4.1.5" +version = "4.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37686beaba5ac9f3ab01ee3172f792fc6ffdd685bfb9e63cfef02c0571a4e8e1" +checksum = "5fc443334c81a804575546c5a8a79b4913b50e28d69232903604cada1de817ce" dependencies = [ "clap", ] [[package]] name = "clap_complete_fig" -version = "4.1.2" +version = "4.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2171bc6242ad7a1801422bff039574449b5bd832b715222e500714ce10f91a54" +checksum = "99fee1d30a51305a6c2ed3fc5709be3c8af626c9c958e04dd9ae94e27bcbce9f" dependencies = [ "clap", "clap_complete", @@ -657,25 +878,21 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.1.9" +version = "4.3.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fddf67631444a3a3e3e5ac51c36a5e01335302de677bd78759eaa90ab1f46644" +checksum = "54a9bb5758fc5dfe728d1019941681eccaf0cf8a4189b692a0ee2f2ecf90a050" dependencies = [ "heck 0.4.1", - "proc-macro-error", "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.28", ] [[package]] name = "clap_lex" -version = "0.3.3" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "033f6b7a4acb1f358c742aaca805c939ee73b4c6209ae4318ec7aca81c42e646" -dependencies = [ - "os_str_bytes", -] +checksum = "2da6da31387c7e4ef160ffab6d5e7f00c42626fe39aea70a7b0f1773f7dd6c1b" [[package]] name = "cocoa" @@ -715,25 +932,31 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" [[package]] -name = "colored" -version = "1.9.3" +name = "colorchoice" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4ffc801dacf156c5854b9df4f425a626539c3a6ef7893cc0c5084a23f0b6c59" +checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" + +[[package]] +name = "colored" +version = "1.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a5f741c91823341bebf717d4c71bda820630ce065443b58bd1b7451af008355" dependencies = [ - "atty", + "is-terminal", "lazy_static", "winapi", ] [[package]] name = "colored" -version = "2.0.0" +version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3616f750b84d8f0de8a58bda93e08e2a81ad3f523089b05f1dffecab48c6cbd" +checksum = "2674ec482fbc38012cf31e6c42ba0177b431a0cb6f15fe40efa5aab1bda516f6" dependencies = [ - "atty", + "is-terminal", "lazy_static", - "winapi", + "windows-sys 0.48.0", ] [[package]] @@ -747,16 +970,25 @@ dependencies = [ ] [[package]] -name = "const-oid" -version = "0.9.2" +name = "concurrent-queue" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "520fbf3c07483f94e3e3ca9d0cfd913d7718ef2483d2cfd91c0d9e91474ab913" +checksum = "62ec6771ecfa0762d24683ee5a32ad78487a3d3afdc0fb8cae19d2c5deb50b7c" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "const-oid" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28c122c3980598d243d63d9a704629a2d748d101f278052ff068be5a4423ab6f" [[package]] name = "constant_time_eq" -version = "0.2.5" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13418e745008f7349ec7e449155f419a61b92b58a99cc3616942b926825ec76b" +checksum = "f7144d30dcf0fafbce74250a3963025d8d52177934239851c917d29f1df280c2" [[package]] name = "convert_case" @@ -776,9 +1008,9 @@ dependencies = [ [[package]] name = "core-foundation-sys" -version = "0.8.3" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc" +checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa" [[package]] name = "core-graphics" @@ -795,13 +1027,12 @@ dependencies = [ [[package]] name = "core-graphics-types" -version = "0.1.1" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a68b68b3446082644c91ac778bf50cd4104bfb002b5a6a7c44cca5a2c70788b" +checksum = "2bb142d41022986c1d8ff29103a1411c8a3dfad3552f87a4f8dc50d61d4f4e33" dependencies = [ "bitflags 1.3.2", "core-foundation", - "foreign-types", "libc", ] @@ -839,9 +1070,9 @@ dependencies = [ [[package]] name = "cosmwasm-crypto" -version = "1.3.0" +version = "1.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d076a08ec01ed23c4396aca98ec73a38fa1fee5f310465add52b4108181c7a8" +checksum = "7e272708a9745dad8b591ef8a718571512130f2b39b33e3d7a27c558e3069394" dependencies = [ "digest 0.10.7", "ed25519-zebra", @@ -852,9 +1083,9 @@ dependencies = [ [[package]] name = "cosmwasm-derive" -version = "1.3.0" +version = "1.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dec361f3c09d7b41221948fc17be9b3c96cb58e55a02f82da36f888a651f2584" +checksum = "296db6a3caca5283425ae0cf347f4e46999ba3f6620dbea8939a0e00347831ce" dependencies = [ "syn 1.0.109", ] @@ -899,15 +1130,15 @@ dependencies = [ "schemars", "serde", "serde-json-wasm", - "sha2 0.10.6", + "sha2 0.10.7", "thiserror", ] [[package]] name = "cpufeatures" -version = "0.2.5" +version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28d997bd5e24a5928dd43e46dc529867e207907fe0b239c3477d924f7f2ca320" +checksum = "a17b76ff3a4162b0b27f354a0c87015ddad39d35f9c0c36607a3bdd175dde1f1" dependencies = [ "libc", ] @@ -953,9 +1184,9 @@ dependencies = [ [[package]] name = "crossbeam-channel" -version = "0.5.7" +version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf2b3e8478797446514c91ef04bafcb59faba183e621ad488df88983cc14128c" +checksum = "a33c2bf77f2df06183c3aa30d1e96c0695a313d4f9c453cc3762a6db39f99200" dependencies = [ "cfg-if", "crossbeam-utils", @@ -974,14 +1205,14 @@ dependencies = [ [[package]] name = "crossbeam-epoch" -version = "0.9.14" +version = "0.9.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46bd5f3f85273295a9d14aedfb86f6aadbff6d8f5295c4a9edb08e819dcf5695" +checksum = "ae211234986c545741a7dc064309f67ee1e5ad243d0e48335adc0484d960bcc7" dependencies = [ "autocfg", "cfg-if", "crossbeam-utils", - "memoffset", + "memoffset 0.9.0", "scopeguard", ] @@ -997,9 +1228,9 @@ dependencies = [ [[package]] name = "crossbeam-utils" -version = "0.8.15" +version = "0.8.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c063cd8cc95f5c377ed0d4b49a4b21f632396ff690e8470c29b3359b346984b" +checksum = "5a22b2d63d4d1dc0b7f1b6b2747dd0088008a9be28b6ddf0b1e7d335e3037294" dependencies = [ "cfg-if", ] @@ -1078,12 +1309,12 @@ dependencies = [ [[package]] name = "cssparser-macros" -version = "0.6.0" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfae75de57f2b2e85e8768c3ea840fd159c8f33e2b6522c7835b7abac81be16e" +checksum = "13b588ba4ac1a99f7f2964d24b3d896ddc6bf847ee3855dbd4366f058cfcd331" dependencies = [ "quote", - "syn 1.0.109", + "syn 2.0.28", ] [[package]] @@ -1185,7 +1416,7 @@ dependencies = [ "cosmwasm-std", "cw2", "schemars", - "semver 1.0.17", + "semver 1.0.18", "serde", "thiserror", ] @@ -1247,9 +1478,9 @@ dependencies = [ [[package]] name = "darling" -version = "0.13.4" +version = "0.20.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a01d95850c592940db9b8194bc39f4bc0e89dee5c4265e4b1807c34a9aba453c" +checksum = "0209d94da627ab5605dcccf08bb18afa5009cfbef48d8a8b7d7bdbc79be25c5e" dependencies = [ "darling_core", "darling_macro", @@ -1257,51 +1488,40 @@ dependencies = [ [[package]] name = "darling_core" -version = "0.13.4" +version = "0.20.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "859d65a907b6852c9361e3185c862aae7fafd2887876799fa55f5f99dc40d610" +checksum = "177e3443818124b357d8e76f53be906d60937f0d3a90773a664fa63fa253e621" dependencies = [ "fnv", "ident_case", "proc-macro2", "quote", "strsim", - "syn 1.0.109", + "syn 2.0.28", ] [[package]] name = "darling_macro" -version = "0.13.4" +version = "0.20.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c972679f83bdf9c42bd905396b6c3588a843a17f0f16dfcfa3e2c5d57441835" +checksum = "836a9bbc7ad63342d6d6e7b815ccab164bc77a2d95d84bc3117a8c0d5c98e2d5" dependencies = [ "darling_core", "quote", - "syn 1.0.109", + "syn 2.0.28", ] [[package]] name = "dashmap" -version = "5.4.0" +version = "5.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "907076dfda823b0b36d2a1bb5f90c96660a5bbcd7729e10727f07858f22c4edc" +checksum = "6943ae99c34386c84a470c499d3414f66502a41340aa895406e0d2e4a207b91d" dependencies = [ "cfg-if", - "hashbrown 0.12.3", + "hashbrown 0.14.0", "lock_api", "once_cell", - "parking_lot_core 0.9.7", -] - -[[package]] -name = "dbus" -version = "0.9.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bb21987b9fb1613058ba3843121dd18b163b254d8a6e797e144cbac14d96d1b" -dependencies = [ - "libc", - "libdbus-sys", - "winapi", + "parking_lot_core 0.9.8", ] [[package]] @@ -1311,7 +1531,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef552e6f588e446098f6ba40d89ac146c8c7b64aade83c051ee00bb5d2bc18d" dependencies = [ "serde", - "uuid 1.3.0", + "uuid 1.4.1", ] [[package]] @@ -1326,14 +1546,23 @@ dependencies = [ [[package]] name = "der" -version = "0.7.7" +version = "0.7.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c7ed52955ce76b1554f509074bb357d3fb8ac9b51288a65a3fd480d1dfba946" +checksum = "fffa369a668c8af7dbf8b5e56c9f744fbd399949ed171606040001947de40b1c" dependencies = [ "const-oid", "zeroize", ] +[[package]] +name = "deranged" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7684a49fb1af197853ef7b2ee694bc1f5b4179556f1e5710e1760c5db6f5e929" +dependencies = [ + "serde", +] + [[package]] name = "derivative" version = "2.2.0" @@ -1470,30 +1699,30 @@ checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b" [[package]] name = "dtoa" -version = "0.4.8" +version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56899898ce76aaf4a0f24d914c97ea6ed976d42fec6ad33fcbb0a1103e07b2b0" +checksum = "dcbb2bf8e87535c23f7a8a321e364ce21462d0ff10cb6407820e8e96dfff6653" [[package]] name = "dtoa-short" -version = "0.3.3" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bde03329ae10e79ede66c9ce4dc930aa8599043b0743008548680f25b91502d6" +checksum = "dbaceec3c6e4211c79e7b1800fb9680527106beb2f9c51904a3210c03a448c74" dependencies = [ "dtoa", ] [[package]] name = "dunce" -version = "1.0.3" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bd4b30a6560bbd9b4620f4de34c3f14f60848e58a9b7216801afcb4c7b31c3c" +checksum = "56ce8c6da7551ec6c462cbaf3bfbc75131ebbfa1c944aeaa9dab51ca1c5f0c3b" [[package]] name = "dyn-clone" -version = "1.0.11" +version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68b0cf012f1230e43cd00ebb729c6bb58707ecfa8ad08b52ef3a4ccd2697fc30" +checksum = "304e6508efa593091e97a9abbc10f90aa7ca635b6d2784feff3c89d41dd12272" [[package]] name = "ecdsa" @@ -1513,7 +1742,7 @@ version = "0.16.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a4b1e0c257a9e9f25f90ff76d7a68360ed497ee519c8e428d1825ef0000799d4" dependencies = [ - "der 0.7.7", + "der 0.7.8", "digest 0.10.7", "elliptic-curve 0.13.5", "rfc6979 0.4.0", @@ -1532,9 +1761,9 @@ dependencies = [ [[package]] name = "ed25519" -version = "2.2.1" +version = "2.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fb04eee5d9d907f29e80ee6b0e78f7e2c82342c63e3580d8c4f69d9d5aad963" +checksum = "60f6d271ca33075c88028be6f04d502853d63a5ece419d269c15315d4fc1cf1d" dependencies = [ "pkcs8 0.10.2", "signature 2.1.0", @@ -1584,9 +1813,9 @@ dependencies = [ [[package]] name = "either" -version = "1.8.1" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91" +checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" [[package]] name = "elliptic-curve" @@ -1627,6 +1856,19 @@ dependencies = [ "zeroize", ] +[[package]] +name = "embed-resource" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7f1e82a60222fc67bfd50d752a9c89da5cce4c39ed39decc84a443b07bbd69a" +dependencies = [ + "cc", + "rustc_version", + "toml 0.7.6", + "vswhom", + "winreg 0.11.0", +] + [[package]] name = "embed_plist" version = "1.2.2" @@ -1653,13 +1895,34 @@ dependencies = [ [[package]] name = "enum-iterator-derive" -version = "1.2.0" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "355f93763ef7b0ae1c43c4d8eccc9d5848d84ad1a1d8ce61c421d1ac85a19d05" +checksum = "eecf8589574ce9b895052fa12d69af7a233f99e6107f5cb8dd1044f2a17bfdcb" dependencies = [ "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.28", +] + +[[package]] +name = "enumflags2" +version = "0.7.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c041f5090df68b32bcd905365fd51769c8b9d553fe87fde0b683534f10c01bd2" +dependencies = [ + "enumflags2_derive", + "serde", +] + +[[package]] +name = "enumflags2_derive" +version = "0.7.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e9a1f9f7d83e59740248a6e14ecf93929ade55027844dfcea78beafccc15745" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.28", ] [[package]] @@ -1677,19 +1940,19 @@ dependencies = [ [[package]] name = "equivalent" -version = "1.0.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88bffebc5d80432c9b140ee17875ff173a8ab62faad5b257da912bd2f6c1c0a1" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" [[package]] name = "errno" -version = "0.2.8" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f639046355ee4f37944e44f60642c6f3a7efa3cf6b78c78a0d989a8ce6c396a1" +checksum = "6b30f669a7961ef1631673d2766cc92f52d64f7ef354d4fe0ddfd30ed52f0f4f" dependencies = [ "errno-dragonfly", "libc", - "winapi", + "windows-sys 0.48.0", ] [[package]] @@ -1728,12 +1991,27 @@ dependencies = [ ] [[package]] -name = "fern" -version = "0.6.1" +name = "fastrand" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3bdd7b0849075e79ee9a1836df22c717d1eba30451796fdc631b04565dd11e2a" +checksum = "6999dc1837253364c2ebb0704ba97994bd874e8f195d665c50b7548f6ea92764" + +[[package]] +name = "fdeflate" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d329bdeac514ee06249dabc27877490f17f5d371ec693360768b838e19f3ae10" dependencies = [ - "colored 1.9.3", + "simd-adler32", +] + +[[package]] +name = "fern" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9f0c14694cbd524c8720dd69b0e3179344f04ebb5f90f2e4a440c6ea3b2f1ee" +dependencies = [ + "colored 1.9.4", "log", ] @@ -1779,24 +2057,24 @@ dependencies = [ [[package]] name = "field-offset" -version = "0.3.5" +version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3cf3a800ff6e860c863ca6d4b16fd999db8b752819c1606884047b73e468535" +checksum = "38e2275cc4e4fc009b0669731a1e5ab7ebf11f469eaede2bab9309a5b4d6057f" dependencies = [ - "memoffset", + "memoffset 0.9.0", "rustc_version", ] [[package]] name = "filetime" -version = "0.2.20" +version = "0.2.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a3de6e8d11b22ff9edc6d916f890800597d60f8b2da1caf2955c274638d6412" +checksum = "d4029edd3e734da6fe05b6cd7bd2960760a616bd2ddd0d59a0124746d6272af0" dependencies = [ "cfg-if", "libc", - "redox_syscall", - "windows-sys 0.45.0", + "redox_syscall 0.3.5", + "windows-sys 0.48.0", ] [[package]] @@ -1822,12 +2100,12 @@ dependencies = [ [[package]] name = "flate2" -version = "1.0.25" +version = "1.0.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8a2db397cb1c8772f31494cb8917e48cd1e64f0fa7efac59fbd741a0a8ce841" +checksum = "3b9429470923de8e8cbd4d2dc513535400b4b3fef0319fb5c4e1f520a7bef743" dependencies = [ "crc32fast", - "miniz_oxide 0.6.2", + "miniz_oxide", ] [[package]] @@ -1849,7 +2127,7 @@ dependencies = [ "futures-core", "futures-sink", "pin-project", - "spin 0.9.6", + "spin 0.9.8", ] [[package]] @@ -1875,9 +2153,9 @@ checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" [[package]] name = "form_urlencoded" -version = "1.1.0" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9c384f161156f5260c24a097c56119f9be8c798586aecc13afbcbe7b7e26bf8" +checksum = "a62bc1cf6f830c2ec14a513a9fb124d0a213a629668a4186f329db21fe045652" dependencies = [ "percent-encoding", ] @@ -1906,9 +2184,9 @@ dependencies = [ [[package]] name = "futures" -version = "0.3.27" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "531ac96c6ff5fd7c62263c5e3c67a603af4fcaee2e1a0ae5565ba3a11e69e549" +checksum = "23342abe12aba583913b2e62f22225ff9c950774065e4bfb61a19cd9770fec40" dependencies = [ "futures-channel", "futures-core", @@ -1921,9 +2199,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.27" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "164713a5a0dcc3e7b4b1ed7d3b433cabc18025386f9339346e8daf15963cf7ac" +checksum = "955518d47e09b25bbebc7a18df10b81f0c766eaf4c4f1cccef2fca5f2a4fb5f2" dependencies = [ "futures-core", "futures-sink", @@ -1931,15 +2209,15 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.27" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86d7a0c1aa76363dac491de0ee99faf6941128376f1cf96f07db7603b7de69dd" +checksum = "4bca583b7e26f571124fe5b7561d49cb2868d79116cfa0eefce955557c6fee8c" [[package]] name = "futures-executor" -version = "0.3.27" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1997dd9df74cdac935c76252744c1ed5794fac083242ea4fe77ef3ed60ba0f83" +checksum = "ccecee823288125bd88b4d7f565c9e58e41858e47ab72e8ea2d64e93624386e0" dependencies = [ "futures-core", "futures-task", @@ -1959,38 +2237,53 @@ dependencies = [ [[package]] name = "futures-io" -version = "0.3.27" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89d422fa3cbe3b40dca574ab087abb5bc98258ea57eea3fd6f1fa7162c778b91" +checksum = "4fff74096e71ed47f8e023204cfd0aa1289cd54ae5430a9523be060cdb849964" + +[[package]] +name = "futures-lite" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49a9d51ce47660b1e808d3c990b4709f2f415d928835a17dfd16991515c46bce" +dependencies = [ + "fastrand 1.9.0", + "futures-core", + "futures-io", + "memchr", + "parking", + "pin-project-lite", + "waker-fn", +] [[package]] name = "futures-macro" -version = "0.3.27" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3eb14ed937631bd8b8b8977f2c198443447a8355b6e3ca599f38c975e5a963b6" +checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72" dependencies = [ "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.28", ] [[package]] name = "futures-sink" -version = "0.3.27" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec93083a4aecafb2a80a885c9de1f0ccae9dbd32c2bb54b0c3a65690e0b8d2f2" +checksum = "f43be4fe21a13b9781a69afa4985b0f6ee0e1afab2c6f454a8cf30e2b2237b6e" [[package]] name = "futures-task" -version = "0.3.27" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd65540d33b37b16542a0438c12e6aeead10d4ac5d05bd3f805b8f35ab592879" +checksum = "76d3d132be6c0e6aa1534069c705a74a5997a356c0dc2f86a47765e5617c5b65" [[package]] name = "futures-util" -version = "0.3.27" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ef6b17e481503ec85211fed8f39d1970f128935ca1f814cd32ac4a6842e84ab" +checksum = "26b01e40b772d54cf6c6d721c1d1abd0647a0106a12ecaa1c186273392a69533" dependencies = [ "futures-channel", "futures-core", @@ -2052,7 +2345,7 @@ dependencies = [ "glib-sys", "gobject-sys", "libc", - "system-deps 6.0.3", + "system-deps 6.1.1", ] [[package]] @@ -2069,7 +2362,21 @@ dependencies = [ "libc", "pango-sys", "pkg-config", - "system-deps 6.0.3", + "system-deps 6.1.1", +] + +[[package]] +name = "gdkwayland-sys" +version = "0.15.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cca49a59ad8cfdf36ef7330fe7bdfbe1d34323220cc16a0de2679ee773aee2c2" +dependencies = [ + "gdk-sys", + "glib-sys", + "gobject-sys", + "libc", + "pkg-config", + "system-deps 6.1.1", ] [[package]] @@ -2081,21 +2388,21 @@ dependencies = [ "gdk-sys", "glib-sys", "libc", - "system-deps 6.0.3", + "system-deps 6.1.1", "x11", ] [[package]] name = "generator" -version = "0.7.3" +version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33a20a288a94683f5f4da0adecdbe095c94a77c295e514cc6484e9394dd8376e" +checksum = "5cc16584ff22b460a382b7feec54b23d2908d858152e5739a120b949293bd74e" dependencies = [ "cc", "libc", "log", "rustversion", - "windows 0.44.0", + "windows 0.48.0", ] [[package]] @@ -2189,7 +2496,7 @@ dependencies = [ "glib-sys", "gobject-sys", "libc", - "system-deps 6.0.3", + "system-deps 6.1.1", "winapi", ] @@ -2228,9 +2535,9 @@ dependencies = [ [[package]] name = "glib-macros" -version = "0.15.11" +version = "0.15.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25a68131a662b04931e71891fb14aaf65ee4b44d08e8abc10f49e77418c86c64" +checksum = "10c6ae9f6fa26f4fb2ac16b528d138d971ead56141de489f8111e259b9df3c4a" dependencies = [ "anyhow", "heck 0.4.1", @@ -2248,7 +2555,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ef4b192f8e65e9cf76cbf4ea71fa8e3be4a0e18ffe3d68b8da6836974cc5bad4" dependencies = [ "libc", - "system-deps 6.0.3", + "system-deps 6.1.1", ] [[package]] @@ -2259,9 +2566,9 @@ checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" [[package]] name = "globset" -version = "0.4.10" +version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "029d74589adefde59de1a0c4f4732695c32805624aec7b68d91503d4dba79afc" +checksum = "759c97c1e17c55525b57192c06a267cda0ac5210b222d6b82189a2338fa1c13d" dependencies = [ "aho-corasick", "bstr", @@ -2290,7 +2597,7 @@ checksum = "0d57ce44246becd17153bd035ab4d32cfee096a657fc01f2231c9278378d1e0a" dependencies = [ "glib-sys", "libc", - "system-deps 6.0.3", + "system-deps 6.1.1", ] [[package]] @@ -2377,14 +2684,14 @@ dependencies = [ "gobject-sys", "libc", "pango-sys", - "system-deps 6.0.3", + "system-deps 6.1.1", ] [[package]] name = "gtk3-macros" -version = "0.15.4" +version = "0.15.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24f518afe90c23fba585b2d7697856f9e6a7bbc62f65588035e66f6afb01a2e9" +checksum = "684c0456c086e8e7e9af73ec5b84e35938df394712054550e81558d21c44ab0d" dependencies = [ "anyhow", "proc-macro-crate", @@ -2396,9 +2703,9 @@ dependencies = [ [[package]] name = "h2" -version = "0.3.16" +version = "0.3.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5be7b54589b581f624f566bf5d8eb2bab1db736c51528720b6bd36b96b55924d" +checksum = "97ec8491ebaf99c8eaa73058b045fe58073cd6be7f596ac993ced0b0a0c01049" dependencies = [ "bytes", "fnv", @@ -2406,7 +2713,7 @@ dependencies = [ "futures-sink", "futures-util", "http", - "indexmap 1.9.2", + "indexmap 1.9.3", "slab", "tokio", "tokio-util", @@ -2433,7 +2740,7 @@ version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e" dependencies = [ - "ahash", + "ahash 0.7.6", ] [[package]] @@ -2442,7 +2749,7 @@ version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" dependencies = [ - "ahash", + "ahash 0.7.6", ] [[package]] @@ -2450,6 +2757,10 @@ name = "hashbrown" version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2c6201b9ff9fd90a5a3bac2e56a830d0caa509576f0e503818ee82c181b3437a" +dependencies = [ + "ahash 0.8.3", + "allocator-api2", +] [[package]] name = "hashlink" @@ -2462,11 +2773,11 @@ dependencies = [ [[package]] name = "hashlink" -version = "0.8.1" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69fe1fcf8b4278d860ad0548329f892a3631fb63f82574df68275f34cdbe0ffa" +checksum = "312f66718a2d7789ffef4f4b7b213138ed9f1eb3aa1d0d82fc99f88fb3ffd26f" dependencies = [ - "hashbrown 0.12.3", + "hashbrown 0.14.0", ] [[package]] @@ -2523,18 +2834,9 @@ dependencies = [ [[package]] name = "hermit-abi" -version = "0.2.6" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee512640fe35acbfb4bb779db6f0d80704c2cacfa2e39b601ef3e3f47d1ae4c7" -dependencies = [ - "libc", -] - -[[package]] -name = "hermit-abi" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fed44880c466736ef9a5c5b5facefb5ed0785676d0c02d612db14e54f0d84286" +checksum = "443144c8cdadd93ebf52ddb4056d257f5b52c04d3c804e657d19eb73fc33668b" [[package]] name = "hex" @@ -2619,7 +2921,7 @@ checksum = "bd6effc99afb63425aff9b05836f029929e345a6148a14b7ecd5ab67af944482" dependencies = [ "bytes", "fnv", - "itoa 1.0.6", + "itoa 1.0.9", ] [[package]] @@ -2647,9 +2949,9 @@ checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" [[package]] name = "httpdate" -version = "1.0.2" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" [[package]] name = "humantime" @@ -2678,9 +2980,9 @@ dependencies = [ [[package]] name = "hyper" -version = "0.14.25" +version = "0.14.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc5e554ff619822309ffd57d8734d77cd5ce6238bc956f037ea06c58238c9899" +checksum = "ffb1cfd654a8219eaef89881fdb3bb3b1cdc5fa75ded05d6933b2b382e395468" dependencies = [ "bytes", "futures-channel", @@ -2691,9 +2993,9 @@ dependencies = [ "http-body", "httparse", "httpdate", - "itoa 1.0.6", + "itoa 1.0.9", "pin-project-lite", - "socket2", + "socket2 0.4.9", "tokio", "tower-service", "tracing", @@ -2751,10 +3053,33 @@ dependencies = [ ] [[package]] -name = "ico" -version = "0.2.0" +name = "iana-time-zone" +version = "0.1.57" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "031530fe562d8c8d71c0635013d6d155bbfe8ba0aa4b4d2d24ce8af6b71047bd" +checksum = "2fad5b825842d2b38bd206f3e81d6957625fd7f0a361e345c30e01a0ae2dd613" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "wasm-bindgen", + "windows 0.48.0", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + +[[package]] +name = "ico" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3804960be0bb5e4edb1e1ad67afd321a9ecfd875c3e65c099468fd2717d7cae" dependencies = [ "byteorder", "png", @@ -2768,9 +3093,9 @@ checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" [[package]] name = "idna" -version = "0.3.0" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e14ddfc70884202db2244c223200c204c2bda1bc6e0998d11b5e024d657209e6" +checksum = "7d20d6b07bfbc108882d88ed8e37d39636dcc260e15e30c45e6ba089610b917c" dependencies = [ "unicode-bidi", "unicode-normalization", @@ -2778,11 +3103,10 @@ dependencies = [ [[package]] name = "ignore" -version = "0.4.18" +version = "0.4.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "713f1b139373f96a2e0ce3ac931cd01ee973c3c5dd7c40c0c2efe96ad2b6751d" +checksum = "dbe7873dab538a9a44ad79ede1faf5f30d49f9a5c883ddbab48bce81b64b7492" dependencies = [ - "crossbeam-utils", "globset", "lazy_static", "log", @@ -2796,9 +3120,9 @@ dependencies = [ [[package]] name = "image" -version = "0.24.5" +version = "0.24.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69b7ea949b537b0fd0af141fff8c77690f2ce96f4f41f042ccb6c69c6c965945" +checksum = "6f3dfdbdd72063086ff443e297b61695500514b1e41095b6fb9a5ab48a70a711" dependencies = [ "bytemuck", "byteorder", @@ -2815,9 +3139,9 @@ checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683" [[package]] name = "indexmap" -version = "1.9.2" +version = "1.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1885e79c1fc4b10f0e172c475f458b7f7b93061064d98c3293e98c5ba0c8b399" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" dependencies = [ "autocfg", "hashbrown 0.12.3", @@ -2832,13 +3156,14 @@ checksum = "d5477fe2230a79769d8dc68e0eabf5437907c0457a5614a9e8dddb67f65eb65d" dependencies = [ "equivalent", "hashbrown 0.14.0", + "serde", ] [[package]] name = "infer" -version = "0.7.0" +version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20b2b533137b9cad970793453d4f921c2e91312a6d88b1085c07bc15fc51bb3b" +checksum = "a898e4b7951673fce96614ce5751d13c40fc5674bc2d759288e46c3ab62598b3" dependencies = [ "cfb", ] @@ -2872,31 +3197,30 @@ dependencies = [ [[package]] name = "io-lifetimes" -version = "1.0.9" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09270fd4fa1111bc614ed2246c7ef56239a3063d5be0d1ec3b589c505d400aeb" +checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2" dependencies = [ - "hermit-abi 0.3.1", + "hermit-abi 0.3.2", "libc", - "windows-sys 0.45.0", + "windows-sys 0.48.0", ] [[package]] name = "ipnet" -version = "2.7.1" +version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30e22bd8629359895450b59ea7a776c850561b96a3b1d31321c1949d9e6c9146" +checksum = "28b29a3cd74f0f4598934efe3aeba42bae0eb4680554128851ebbecb02af14e6" [[package]] name = "is-terminal" -version = "0.4.5" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8687c819457e979cc940d09cb16e42a1bf70aa6b60a549de6d3a62a0ee90c69e" +checksum = "cb0889898416213fab133e1d33a0e5858a48177452750691bde3666d0fdbaf8b" dependencies = [ - "hermit-abi 0.3.1", - "io-lifetimes", - "rustix", - "windows-sys 0.45.0", + "hermit-abi 0.3.2", + "rustix 0.38.8", + "windows-sys 0.48.0", ] [[package]] @@ -2916,9 +3240,9 @@ checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4" [[package]] name = "itoa" -version = "1.0.6" +version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "453ad9f582a441959e5f0d088b02ce04cfe8d51a8eaf077f12ac6d3e94164ca6" +checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" [[package]] name = "javascriptcore-rs" @@ -2983,12 +3307,13 @@ dependencies = [ [[package]] name = "json-patch" -version = "0.2.7" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb3fa5a61630976fc4c353c70297f2e93f1930e3ccee574d59d618ccbd5154ce" +checksum = "1f54898088ccb91df1b492cc80029a6fdf1c48ca0db7c6822a8babad69c94658" dependencies = [ "serde", "serde_json", + "thiserror", "treediff", ] @@ -3001,7 +3326,7 @@ dependencies = [ "cfg-if", "ecdsa 0.14.8", "elliptic-curve 0.12.3", - "sha2 0.10.6", + "sha2 0.10.7", ] [[package]] @@ -3014,7 +3339,7 @@ dependencies = [ "ecdsa 0.16.8", "elliptic-curve 0.13.5", "once_cell", - "sha2 0.10.6", + "sha2 0.10.7", "signature 2.1.0", ] @@ -3068,18 +3393,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.146" +version = "0.2.147" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f92be4933c13fd498862a9e02a3055f8a8d9c039ce33db97306fd5a6caa7f29b" - -[[package]] -name = "libdbus-sys" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f8d7ae751e1cb825c840ae5e682f59b098cdfd213c350ac268b61449a5f58a0" -dependencies = [ - "pkg-config", -] +checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3" [[package]] name = "libgit2-sys" @@ -3105,9 +3421,9 @@ dependencies = [ [[package]] name = "libm" -version = "0.2.6" +version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "348108ab3fba42ec82ff6e9564fc4ca0247bdccdc68dd8af9764bbc79c3c8ffb" +checksum = "f7012b1bbb0719e1097c47611d3898568c546d597c2e74d66f6087edd5233ff4" [[package]] name = "libsqlite3-sys" @@ -3122,9 +3438,9 @@ dependencies = [ [[package]] name = "libz-sys" -version = "1.1.8" +version = "1.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9702761c3935f8cc2f101793272e202c72b99da8f4224a19ddcf1279a6450bbf" +checksum = "d97137b25e321a73eef1418d1d5d2eda4d77e12813f8e6dead84bc52c5870a7b" dependencies = [ "cc", "libc", @@ -3149,9 +3465,15 @@ checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" [[package]] name = "linux-raw-sys" -version = "0.1.4" +version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f051f77a7c8e6957c0696eac88f26b0117e54f52d3fc682ab19397a8812846a4" +checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519" + +[[package]] +name = "linux-raw-sys" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57bcfdad1b858c2db7c38303a6d2ad4dfaf5eb53dfeb0910128b2c26d6158503" [[package]] name = "lioness" @@ -3167,9 +3489,9 @@ dependencies = [ [[package]] name = "lock_api" -version = "0.4.9" +version = "0.4.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "435011366fe56583b16cf956f9df0095b405b82d76425bc8981c0e22e60ec4df" +checksum = "c1cc9717a20b1bb222f333e6a92fd32f7d8a18ddc5a3191a11af45dcbf4dcd16" dependencies = [ "autocfg", "scopeguard", @@ -3177,11 +3499,10 @@ dependencies = [ [[package]] name = "log" -version = "0.4.17" +version = "0.4.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" +checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" dependencies = [ - "cfg-if", "serde", ] @@ -3208,9 +3529,9 @@ checksum = "c41e0c4fef86961ac6d6f8a82609f55f31b05e4fce149ac5710e439df7619ba4" [[package]] name = "mac-notification-sys" -version = "0.5.6" +version = "0.5.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e72d50edb17756489e79d52eb146927bec8eba9dd48faadf9ef08bca3791ad5" +checksum = "9402858e87f85f88bf518bd2e68450640df1c99b1ddb3ea28c4d5d823bb54cdd" dependencies = [ "cc", "dirs-next", @@ -3254,7 +3575,7 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558" dependencies = [ - "regex-automata", + "regex-automata 0.1.10", ] [[package]] @@ -3271,9 +3592,18 @@ checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" [[package]] name = "memoffset" -version = "0.8.0" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d61c719bcfbcf5d62b3a09efa6088de8c54bc0bfcd3ea7ae39fcc186108b8de1" +checksum = "5de893c32cde5f383baa4c04c5d6dbdd735cfd4a794b0debdb2bb1b421da5ff4" +dependencies = [ + "autocfg", +] + +[[package]] +name = "memoffset" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a634b1c61a95585bd15607c6ab0c4e5b226e695ff2800ba0cdccddf208c406c" dependencies = [ "autocfg", ] @@ -3296,15 +3626,6 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "933dca44d65cdd53b355d0b73d380a2ff5da71f87f036053188bf1eab6a19881" -[[package]] -name = "miniz_oxide" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b275950c28b37e794e8c55d88aeb5e139d0ce23fdbbeda68f8d7174abdf9e8fa" -dependencies = [ - "adler", -] - [[package]] name = "miniz_oxide" version = "0.7.1" @@ -3312,18 +3633,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7" dependencies = [ "adler", + "simd-adler32", ] [[package]] name = "mio" -version = "0.8.6" +version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b9d9a46eff5b4ff64b45a9e316a6d1e0bc719ef429cbec4dc630684212bfdf9" +checksum = "927a765cd3fc26206e66b296465fa9d3e5ab003e651c1b3c060e7956d96b19d2" dependencies = [ "libc", - "log", "wasi 0.11.0+wasi-snapshot-preview1", - "windows-sys 0.45.0", + "windows-sys 0.48.0", ] [[package]] @@ -3378,6 +3699,19 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e4a24736216ec316047a1fc4252e27dabb04218aa4a3f37c6e7ddbf1f9782b54" +[[package]] +name = "nix" +version = "0.26.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfdda3d196821d6af13126e40375cdf7da646a96114af134d5f417a9a1dc8e1a" +dependencies = [ + "bitflags 1.3.2", + "cfg-if", + "libc", + "memoffset 0.7.1", + "static_assertions", +] + [[package]] name = "nodrop" version = "0.1.14" @@ -3400,10 +3734,11 @@ version = "4.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2bfa211d18e360f08e36c364308f394b5eb23a6629150690e109a916dc6f610e" dependencies = [ - "dbus", "log", "mac-notification-sys", + "serde", "tauri-winrt-notification", + "zbus", ] [[package]] @@ -3450,9 +3785,9 @@ dependencies = [ [[package]] name = "num-traits" -version = "0.2.15" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" +checksum = "f30b0abd723be7e2ffca1272140fac1a2f084c77ec3e123c192b66af1ee9e6c2" dependencies = [ "autocfg", "libm", @@ -3460,11 +3795,11 @@ dependencies = [ [[package]] name = "num_cpus" -version = "1.15.0" +version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fac9e2da13b5eb447a6ce3d392f23a29d8694bff781bf03a16cd9ac8697593b" +checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" dependencies = [ - "hermit-abi 0.2.6", + "hermit-abi 0.3.2", "libc", ] @@ -3573,7 +3908,7 @@ dependencies = [ "reqwest", "serde", "serde_json", - "sha2 0.10.6", + "sha2 0.10.7", "sqlx 0.6.3", "tap", "thiserror", @@ -3650,7 +3985,7 @@ dependencies = [ "log", "nym-network-defaults", "serde", - "toml 0.7.4", + "toml 0.7.6", "url", ] @@ -3696,7 +4031,7 @@ dependencies = [ "thiserror", "time", "tokio", - "toml 0.7.4", + "toml 0.7.6", "ts-rs", "url", "yaml-rust", @@ -3742,7 +4077,7 @@ dependencies = [ name = "nym-crypto" version = "0.4.0" dependencies = [ - "aes 0.8.2", + "aes 0.8.3", "blake3", "bs58 0.4.0", "cipher 0.4.4", @@ -4222,7 +4557,7 @@ dependencies = [ "async-trait", "base64 0.13.1", "bip39", - "colored 2.0.0", + "colored 2.0.4", "cosmrs", "cosmwasm-std", "cw3", @@ -4320,9 +4655,9 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.17.1" +version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3" +checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" [[package]] name = "opaque-debug" @@ -4348,9 +4683,9 @@ dependencies = [ [[package]] name = "openssl" -version = "0.10.47" +version = "0.10.56" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8b277f87dacc05a6b709965d1cbafac4649d6ce9f3ce9ceb88508b5666dfec9" +checksum = "729b745ad4a5575dd06a3e1af1414bd330ee561c01b3899eb584baeaa8def17e" dependencies = [ "bitflags 1.3.2", "cfg-if", @@ -4363,13 +4698,13 @@ dependencies = [ [[package]] name = "openssl-macros" -version = "0.1.0" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b501e44f11665960c7e7fcf062c7d96a14ade4aa98116c004b2e37b5be7d736c" +checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.28", ] [[package]] @@ -4380,20 +4715,19 @@ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" [[package]] name = "openssl-src" -version = "111.26.0+1.1.1u" +version = "111.27.0+1.1.1v" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "efc62c9f12b22b8f5208c23a7200a442b2e5999f8bdf80233852122b5a4f6f37" +checksum = "06e8f197c82d7511c5b014030c9b1efeda40d7d5f99d23b4ceed3524a5e63f02" dependencies = [ "cc", ] [[package]] name = "openssl-sys" -version = "0.9.82" +version = "0.9.91" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a95792af3c4e0153c3914df2261bedd30a98476f94dc892b67dfe1d89d433a04" +checksum = "866b5f16f90776b9bb8dc1e1802ac6f0513de3a7a7465867bfbc563dc737faac" dependencies = [ - "autocfg", "cc", "libc", "openssl-src", @@ -4407,6 +4741,16 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" +[[package]] +name = "ordered-stream" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aa2b01e1d916879f73a53d01d1d6cee68adbb31d6d9177a8cfce093cced1d50" +dependencies = [ + "futures-core", + "pin-project-lite", +] + [[package]] name = "os_info" version = "3.7.0" @@ -4418,12 +4762,6 @@ dependencies = [ "winapi", ] -[[package]] -name = "os_str_bytes" -version = "6.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ceedf44fb00f2d1984b0bc98102627ce622e083e49a5bacdb3e514fa4238e267" - [[package]] name = "overload" version = "0.1.1" @@ -4470,9 +4808,15 @@ dependencies = [ "glib-sys", "gobject-sys", "libc", - "system-deps 6.0.3", + "system-deps 6.1.1", ] +[[package]] +name = "parking" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14f2252c834a40ed9bb5422029649578e63aa341ac401f74e719dd1afda8394e" + [[package]] name = "parking_lot" version = "0.11.2" @@ -4491,7 +4835,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" dependencies = [ "lock_api", - "parking_lot_core 0.9.7", + "parking_lot_core 0.9.8", ] [[package]] @@ -4503,29 +4847,29 @@ dependencies = [ "cfg-if", "instant", "libc", - "redox_syscall", + "redox_syscall 0.2.16", "smallvec", "winapi", ] [[package]] name = "parking_lot_core" -version = "0.9.7" +version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9069cbb9f99e3a5083476ccb29ceb1de18b9118cafa53e90c9551235de2b9521" +checksum = "93f00c865fe7cabf650081affecd3871070f26767e7b2070a3ffae14c654b447" dependencies = [ "cfg-if", "libc", - "redox_syscall", + "redox_syscall 0.3.5", "smallvec", - "windows-sys 0.45.0", + "windows-targets", ] [[package]] name = "paste" -version = "1.0.12" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f746c4065a8fa3fe23974dd82f15431cc8d40779821001404d10d2e79ca7d79" +checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c" [[package]] name = "pathdiff" @@ -4583,15 +4927,15 @@ dependencies = [ [[package]] name = "percent-encoding" -version = "2.2.0" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e" +checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94" [[package]] name = "pest" -version = "2.5.6" +version = "2.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8cbd939b234e95d72bc393d51788aec68aeeb5d51e748ca08ff3aad58cb722f7" +checksum = "1acb4a4365a13f749a93f1a094a7805e5cfa0955373a9de860d962eaa3a5fe5a" dependencies = [ "thiserror", "ucd-trie", @@ -4599,9 +4943,9 @@ dependencies = [ [[package]] name = "pest_derive" -version = "2.5.6" +version = "2.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a81186863f3d0a27340815be8f2078dd8050b14cd71913db9fbda795e5f707d7" +checksum = "666d00490d4ac815001da55838c500eafb0320019bbaa44444137c48b443a853" dependencies = [ "pest", "pest_generator", @@ -4609,26 +4953,26 @@ dependencies = [ [[package]] name = "pest_generator" -version = "2.5.6" +version = "2.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75a1ef20bf3193c15ac345acb32e26b3dc3223aff4d77ae4fc5359567683796b" +checksum = "68ca01446f50dbda87c1786af8770d535423fa8a53aec03b8f4e3d7eb10e0929" dependencies = [ "pest", "pest_meta", "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.28", ] [[package]] name = "pest_meta" -version = "2.5.6" +version = "2.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e3b284b1f13a20dc5ebc90aff59a51b8d7137c221131b52a7260c08cbc1cc80" +checksum = "56af0a30af74d0445c0bf6d9d051c979b516a1a5af790d251daee76005420a48" dependencies = [ "once_cell", "pest", - "sha2 0.10.6", + "sha2 0.10.7", ] [[package]] @@ -4731,29 +5075,29 @@ dependencies = [ [[package]] name = "pin-project" -version = "1.0.12" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad29a609b6bcd67fee905812e544992d216af9d755757c05ed2d0e15a74c6ecc" +checksum = "fda4ed1c6c173e3fc7a83629421152e01d7b1f9b7f65fb301e490e8cfc656422" dependencies = [ "pin-project-internal", ] [[package]] name = "pin-project-internal" -version = "1.0.12" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "069bdb1e05adc7a8990dce9cc75370895fbe4e3d58b9b73bf1aee56359344a55" +checksum = "4359fd9c9171ec6e8c62926d6faaf553a8dc3f64e1507e76da7911b4f6a04405" dependencies = [ "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.28", ] [[package]] name = "pin-project-lite" -version = "0.2.9" +version = "0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" +checksum = "12cc1b0bf1727a77a54b6654e7b5f1af8604923edc8b81885f8ec92f9e3f0a05" [[package]] name = "pin-utils" @@ -4777,40 +5121,57 @@ version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" dependencies = [ - "der 0.7.7", + "der 0.7.8", "spki 0.7.2", ] [[package]] name = "pkg-config" -version = "0.3.26" +version = "0.3.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ac9a59f73473f1b8d852421e59e64809f025994837ef743615c6d0c5b305160" +checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964" [[package]] name = "plist" -version = "1.4.3" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9bd9647b268a3d3e14ff09c23201133a62589c658db02bb7388c7246aafe0590" +checksum = "bdc0001cfea3db57a2e24bc0d818e9e20e554b5f97fabb9bc231dc240269ae06" dependencies = [ "base64 0.21.2", - "indexmap 1.9.2", + "indexmap 1.9.3", "line-wrap", - "quick-xml 0.28.1", + "quick-xml 0.29.0", "serde", "time", ] [[package]] name = "png" -version = "0.17.7" +version = "0.17.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d708eaf860a19b19ce538740d2b4bdeeb8337fa53f7738455e706623ad5c638" +checksum = "59871cc5b6cce7eaccca5a802b4173377a1c2ba90654246789a8fa2334426d11" dependencies = [ "bitflags 1.3.2", "crc32fast", + "fdeflate", "flate2", - "miniz_oxide 0.6.2", + "miniz_oxide", +] + +[[package]] +name = "polling" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b2d323e8ca7996b3e23126511a523f7e62924d93ecd5ae73b333815b0eb3dce" +dependencies = [ + "autocfg", + "bitflags 1.3.2", + "cfg-if", + "concurrent-queue", + "libc", + "log", + "pin-project-lite", + "windows-sys 0.48.0", ] [[package]] @@ -4888,9 +5249,9 @@ checksum = "dc375e1527247fe1a97d8b7156678dfe7c1af2fc075c9a4db3690ecd2a148068" [[package]] name = "proc-macro2" -version = "1.0.53" +version = "1.0.66" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba466839c78239c09faf015484e5cc04860f88242cff4d03eb038f04b4699b73" +checksum = "18fb31db3f9bddb2ea821cde30a9f70117e3f119938b5ee630b7403aa6e2ead9" dependencies = [ "unicode-ident", ] @@ -4950,18 +5311,18 @@ dependencies = [ [[package]] name = "quick-xml" -version = "0.28.1" +version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5c1a97b1bc42b1d550bfb48d4262153fe400a12bab1511821736f7eac76d7e2" +checksum = "81b9228215d82c7b61490fec1de287136b5de6f5700f6e58ea9ad61a7964ca51" dependencies = [ "memchr", ] [[package]] name = "quote" -version = "1.0.26" +version = "1.0.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4424af4bf778aae2051a77b60283332f386554255d722233d09fbfc7e30da2fc" +checksum = "50f3b39ccfb720540debaa0164757101c08ecb8d326b15358ce76a62c7e85965" dependencies = [ "proc-macro2", ] @@ -5065,9 +5426,9 @@ dependencies = [ [[package]] name = "raw-window-handle" -version = "0.5.1" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f851a03551ceefd30132e447f07f96cb7011d6b658374f3aed847333adb5559" +checksum = "f2ff9a1f06a88b01621b7ae906ef0211290d1c8a168a15542486a8f61c0833b9" [[package]] name = "rayon" @@ -5100,6 +5461,15 @@ dependencies = [ "bitflags 1.3.2", ] +[[package]] +name = "redox_syscall" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29" +dependencies = [ + "bitflags 1.3.2", +] + [[package]] name = "redox_users" version = "0.4.3" @@ -5107,19 +5477,20 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b" dependencies = [ "getrandom 0.2.10", - "redox_syscall", + "redox_syscall 0.2.16", "thiserror", ] [[package]] name = "regex" -version = "1.7.2" +version = "1.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cce168fea28d3e05f158bda4576cf0c844d5045bc2cc3620fa0292ed5bb5814c" +checksum = "81bc1d4caf89fac26a70747fe603c130093b53c773888797a6329091246d651a" dependencies = [ "aho-corasick", "memchr", - "regex-syntax", + "regex-automata 0.3.6", + "regex-syntax 0.7.4", ] [[package]] @@ -5128,7 +5499,18 @@ version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" dependencies = [ - "regex-syntax", + "regex-syntax 0.6.29", +] + +[[package]] +name = "regex-automata" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fed1ceff11a1dddaee50c9dc8e4938bd106e9d89ae372f192311e7da498e3b69" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax 0.7.4", ] [[package]] @@ -5137,6 +5519,12 @@ version = "0.6.29" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" +[[package]] +name = "regex-syntax" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5ea92a5b6195c6ef2a0295ea818b312502c6fc94dde986c5553242e18fd4ce2" + [[package]] name = "reqwest" version = "0.11.18" @@ -5167,12 +5555,14 @@ dependencies = [ "tokio", "tokio-native-tls", "tokio-socks", + "tokio-util", "tower-service", "url", "wasm-bindgen", "wasm-bindgen-futures", + "wasm-streams", "web-sys", - "winreg", + "winreg 0.10.1", ] [[package]] @@ -5246,9 +5636,9 @@ dependencies = [ [[package]] name = "rust-embed" -version = "6.6.0" +version = "6.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb133b9a38b5543fad3807fb2028ea47c5f2b566f4f5e28a11902f1a358348b6" +checksum = "a36224c3276f8c4ebc8c20f158eca7ca4359c8db89991c4925132aaaf6702661" dependencies = [ "rust-embed-impl", "rust-embed-utils", @@ -5257,25 +5647,25 @@ dependencies = [ [[package]] name = "rust-embed-impl" -version = "6.5.0" +version = "6.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d4e0f0ced47ded9a68374ac145edd65a6c1fa13a96447b873660b2a568a0fd7" +checksum = "49b94b81e5b2c284684141a2fb9e2a31be90638caf040bf9afbc5a0416afe1ac" dependencies = [ "proc-macro2", "quote", "rust-embed-utils", - "syn 1.0.109", + "syn 2.0.28", "walkdir", ] [[package]] name = "rust-embed-utils" -version = "7.5.0" +version = "7.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "512b0ab6853f7e14e3c8754acb43d6f748bb9ced66aa5915a6553ac8213f7731" +checksum = "9d38ff6bf570dc3bb7100fce9f7b60c33fa71d80e88da3f2580df4ff2bdded74" dependencies = [ "globset", - "sha2 0.10.6", + "sha2 0.10.7", "walkdir", ] @@ -5291,21 +5681,34 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" dependencies = [ - "semver 1.0.17", + "semver 1.0.18", ] [[package]] name = "rustix" -version = "0.36.11" +version = "0.37.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db4165c9963ab29e422d6c26fbc1d37f15bace6b2810221f9d925023480fcf0e" +checksum = "4d69718bf81c6127a49dc64e44a742e8bb9213c0ff8869a22c308f84c1d4ab06" dependencies = [ "bitflags 1.3.2", "errno", "io-lifetimes", "libc", - "linux-raw-sys", - "windows-sys 0.45.0", + "linux-raw-sys 0.3.8", + "windows-sys 0.48.0", +] + +[[package]] +name = "rustix" +version = "0.38.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19ed4fa021d81c8392ce04db050a3da9a60299050b7ae1cf482d862b54a7218f" +dependencies = [ + "bitflags 2.4.0", + "errno", + "libc", + "linux-raw-sys 0.4.5", + "windows-sys 0.48.0", ] [[package]] @@ -5347,24 +5750,24 @@ dependencies = [ [[package]] name = "rustls-pemfile" -version = "1.0.2" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d194b56d58803a43635bdc398cd17e383d6f71f9182b9a192c127ca42494a59b" +checksum = "2d3987094b1d07b653b7dfdc3f70ce9a1da9c51ac18c1b06b662e4f9a0e9f4b2" dependencies = [ "base64 0.21.2", ] [[package]] name = "rustversion" -version = "1.0.12" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f3208ce4d8448b3f3e7d168a73f5e0c43a61e32930de3bceeccedb388b6bf06" +checksum = "7ffc183a10b4478d04cbbbfc96d0873219d962dd5accaff2ffbd4ceb7df837f4" [[package]] name = "ryu" -version = "1.0.13" +version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f91339c0467de62360649f8d3e185ca8de4224ff281f66000de5eb2a77a79041" +checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741" [[package]] name = "safemem" @@ -5383,11 +5786,11 @@ dependencies = [ [[package]] name = "schannel" -version = "0.1.21" +version = "0.1.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "713cfb06c7059f3588fb8044c0fad1d09e3c01d225e25b9220dbfdcf16dbb1b3" +checksum = "0c3733bf4cf7ea0880754e19cb5a462007c4a8c1914bff372ccc95b464f1df88" dependencies = [ - "windows-sys 0.42.0", + "windows-sys 0.48.0", ] [[package]] @@ -5397,7 +5800,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "02c613288622e5f0c3fdc5dbd4db1c5fbe752746b1d1a56a0630b78fd00de44f" dependencies = [ "dyn-clone", - "indexmap 1.9.2", + "indexmap 1.9.3", "schemars_derive", "serde", "serde_json", @@ -5423,9 +5826,9 @@ checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" [[package]] name = "scopeguard" -version = "1.1.0" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "sct" @@ -5468,7 +5871,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc" dependencies = [ "base16ct 0.2.0", - "der 0.7.7", + "der 0.7.8", "generic-array 0.14.7", "pkcs8 0.10.2", "subtle 2.4.1", @@ -5477,9 +5880,9 @@ dependencies = [ [[package]] name = "security-framework" -version = "2.8.2" +version = "2.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a332be01508d814fed64bf28f798a146d73792121129962fdf335bb3c49a4254" +checksum = "05b64fb303737d99b81884b2c63433e9ae28abebe5eb5045dcdd175dc2ecf4de" dependencies = [ "bitflags 1.3.2", "core-foundation", @@ -5490,9 +5893,9 @@ dependencies = [ [[package]] name = "security-framework-sys" -version = "2.8.0" +version = "2.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31c9bb296072e961fcbd8853511dd39c2d8be2deb1e17c6860b1d30732b323b4" +checksum = "e932934257d3b408ed8f30db49d85ea163bfe74961f017f405b025af298f0c7a" dependencies = [ "core-foundation-sys", "libc", @@ -5529,9 +5932,9 @@ dependencies = [ [[package]] name = "semver" -version = "1.0.17" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bebd363326d05ec3e2f532ab7660680f3b02130d780c299bca73469d521bc0ed" +checksum = "b0293b4b29daaf487284529cc2f5675b8e57c61f70167ba415a463651fd6a918" dependencies = [ "serde", ] @@ -5672,14 +6075,14 @@ dependencies = [ "thiserror", "time", "url", - "uuid 1.3.0", + "uuid 1.4.1", ] [[package]] name = "serde" -version = "1.0.158" +version = "1.0.183" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "771d4d9c4163ee138805e12c710dd365e4f44be8be0503cb1bb9eb989425d9c9" +checksum = "32ac8da02677876d532745a130fc9d8e6edfa81a269b107c5b00829b91d8eb3c" dependencies = [ "serde_derive", ] @@ -5695,22 +6098,22 @@ dependencies = [ [[package]] name = "serde_bytes" -version = "0.11.9" +version = "0.11.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "416bda436f9aab92e02c8e10d49a15ddd339cea90b6e340fe51ed97abb548294" +checksum = "ab33ec92f677585af6d88c65593ae2375adde54efdbf16d597f2cbc7a6d368ff" dependencies = [ "serde", ] [[package]] name = "serde_derive" -version = "1.0.158" +version = "1.0.183" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e801c1712f48475582b7696ac71e0ca34ebb30e09338425384269d9717c62cad" +checksum = "aafe972d60b0b9bee71a91b92fee2d4fb3c9d7e8f6b179aa99f27203d99a4816" dependencies = [ "proc-macro2", "quote", - "syn 2.0.5", + "syn 2.0.28", ] [[package]] @@ -5726,24 +6129,24 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.94" +version = "1.0.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c533a59c9d8a93a09c6ab31f0fd5e5f4dd1b8fc9434804029839884765d04ea" +checksum = "076066c5f1078eac5b722a31827a8832fe108bed65dfa75e233c89f8206e976c" dependencies = [ - "itoa 1.0.6", + "itoa 1.0.9", "ryu", "serde", ] [[package]] name = "serde_repr" -version = "0.1.12" +version = "0.1.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bcec881020c684085e55a25f7fd888954d56609ef363479dc5a1305eb0d40cab" +checksum = "8725e1dfadb3a50f7e5ce0b1a540466f6ed3fe7a0fca2ac2b8b831d31316bd00" dependencies = [ "proc-macro2", "quote", - "syn 2.0.5", + "syn 2.0.28", ] [[package]] @@ -5762,31 +6165,38 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" dependencies = [ "form_urlencoded", - "itoa 1.0.6", + "itoa 1.0.9", "ryu", "serde", ] [[package]] name = "serde_with" -version = "1.14.0" +version = "3.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "678b5a069e50bf00ecd22d0cd8ddf7c236f68581b03db652061ed5eb13a312ff" +checksum = "1402f54f9a3b9e2efe71c1cea24e648acce55887983553eeb858cf3115acfd49" dependencies = [ + "base64 0.21.2", + "chrono", + "hex", + "indexmap 1.9.3", + "indexmap 2.0.0", "serde", + "serde_json", "serde_with_macros", + "time", ] [[package]] name = "serde_with_macros" -version = "1.5.2" +version = "3.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e182d6ec6f05393cc0e5ed1bf81ad6db3a8feedf8ee515ecdd369809bcce8082" +checksum = "9197f1ad0e3c173a0222d3c4404fb04c3afe87e962bcb327af73e8301fa203c7" dependencies = [ "darling", "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.28", ] [[package]] @@ -5860,9 +6270,9 @@ dependencies = [ [[package]] name = "sha2" -version = "0.10.6" +version = "0.10.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82e6b795fe2e3b1e845bafcb27aa35405c4d47cdfc92af5fc8d3002f76cebdc0" +checksum = "479fb9d862239e610720565ca91403019f2f00410f1864c5aa7479b950a76ed8" dependencies = [ "cfg-if", "cpufeatures", @@ -5878,6 +6288,16 @@ dependencies = [ "lazy_static", ] +[[package]] +name = "signal-hook" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8621587d4798caf8eb44879d42e56b9a93ea5dcd315a6487c357130095b62801" +dependencies = [ + "libc", + "signal-hook-registry", +] + [[package]] name = "signal-hook-registry" version = "1.4.1" @@ -5907,6 +6327,12 @@ dependencies = [ "rand_core 0.6.4", ] +[[package]] +name = "simd-adler32" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" + [[package]] name = "siphasher" version = "0.3.10" @@ -5924,9 +6350,9 @@ dependencies = [ [[package]] name = "smallvec" -version = "1.10.0" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0" +checksum = "62bb4feee49fdd9f707ef802e22365a35de4b7b299de4763d44bfea899442ff9" [[package]] name = "socket2" @@ -5938,6 +6364,16 @@ dependencies = [ "winapi", ] +[[package]] +name = "socket2" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2538b18701741680e0322a2302176d3253a35388e2e62f172f64f4f16605f877" +dependencies = [ + "libc", + "windows-sys 0.48.0", +] + [[package]] name = "soup2" version = "0.2.1" @@ -5998,9 +6434,9 @@ checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" [[package]] name = "spin" -version = "0.9.6" +version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5d6e0250b93c8427a177b849d144a96d5acc57006149479403d7861ab721e34" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" dependencies = [ "lock_api", ] @@ -6022,7 +6458,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9d1e996ef02c474957d681f1b05213dfb0abab947b446a62d37770b23500184a" dependencies = [ "base64ct", - "der 0.7.7", + "der 0.7.8", ] [[package]] @@ -6073,7 +6509,7 @@ version = "0.5.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e48c61941ccf5ddcada342cd59e3e5173b007c509e1e8e990dafc830294d9dc5" dependencies = [ - "ahash", + "ahash 0.7.6", "atoi 0.4.0", "bitflags 1.3.2", "byteorder", @@ -6090,8 +6526,8 @@ dependencies = [ "futures-util", "hashlink 0.7.0", "hex", - "indexmap 1.9.2", - "itoa 1.0.6", + "indexmap 1.9.3", + "itoa 1.0.9", "libc", "libsqlite3-sys", "log", @@ -6100,7 +6536,7 @@ dependencies = [ "paste", "percent-encoding", "rustls 0.19.1", - "sha2 0.10.6", + "sha2 0.10.7", "smallvec", "sqlformat 0.1.8", "sqlx-rt 0.5.13", @@ -6118,7 +6554,7 @@ version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fa8241483a83a3f33aa5fff7e7d9def398ff9990b2752b6c6112b83c6d246029" dependencies = [ - "ahash", + "ahash 0.7.6", "atoi 1.0.0", "bitflags 1.3.2", "byteorder", @@ -6134,10 +6570,10 @@ dependencies = [ "futures-executor", "futures-intrusive", "futures-util", - "hashlink 0.8.1", + "hashlink 0.8.3", "hex", - "indexmap 1.9.2", - "itoa 1.0.6", + "indexmap 1.9.3", + "itoa 1.0.9", "libc", "libsqlite3-sys", "log", @@ -6147,7 +6583,7 @@ dependencies = [ "percent-encoding", "rustls 0.20.8", "rustls-pemfile", - "sha2 0.10.6", + "sha2 0.10.7", "smallvec", "sqlformat 0.2.1", "sqlx-rt 0.6.3", @@ -6170,7 +6606,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "sha2 0.10.6", + "sha2 0.10.7", "sqlx-core 0.5.13", "sqlx-rt 0.5.13", "syn 1.0.109", @@ -6189,7 +6625,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "sha2 0.10.6", + "sha2 0.10.7", "sqlx-core 0.6.3", "sqlx-rt 0.6.3", "syn 1.0.109", @@ -6233,6 +6669,12 @@ dependencies = [ "loom", ] +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + [[package]] name = "string_cache" version = "0.8.7" @@ -6261,9 +6703,9 @@ dependencies = [ [[package]] name = "stringprep" -version = "0.1.2" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ee348cb74b87454fff4b551cbf727025810a004f88aeacae7f85b87f4e9a1c1" +checksum = "db3737bde7edce97102e0e2b15365bf7a20bfdb5f60f4f9e8d7004258a51a8da" dependencies = [ "unicode-bidi", "unicode-normalization", @@ -6284,27 +6726,6 @@ version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" -[[package]] -name = "strum" -version = "0.22.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7ac893c7d471c8a21f31cfe213ec4f6d9afeed25537c772e08ef3f005f8729e" -dependencies = [ - "strum_macros", -] - -[[package]] -name = "strum_macros" -version = "0.22.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "339f799d8b549e3744c7ac7feb216383e4005d94bdb22561b3ab8f3b808ae9fb" -dependencies = [ - "heck 0.3.3", - "proc-macro2", - "quote", - "syn 1.0.109", -] - [[package]] name = "subtle" version = "1.0.0" @@ -6345,27 +6766,15 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.5" +version = "2.0.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89c2d1c76a26822187a1fbb5964e3fff108bc208f02e820ab9dac1234f6b388a" +checksum = "04361975b3f5e348b2189d8dc55bc942f278b2d482a6a0365de5bdd62d351567" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] -[[package]] -name = "synstructure" -version = "0.12.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f36bdaa60a83aca3921b5259d5400cbf5e90fc51931376a9bd4a0eb79aa7210f" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.109", - "unicode-xid", -] - [[package]] name = "system-deps" version = "5.0.0" @@ -6381,22 +6790,22 @@ dependencies = [ [[package]] name = "system-deps" -version = "6.0.3" +version = "6.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2955b1fe31e1fa2fbd1976b71cc69a606d7d4da16f6de3333d0c92d51419aeff" +checksum = "30c2de8a4d8f4b823d634affc9cd2a74ec98c53a756f317e529a48046cbf71f3" dependencies = [ - "cfg-expr 0.11.0", + "cfg-expr 0.15.4", "heck 0.4.1", "pkg-config", - "toml 0.5.11", + "toml 0.7.6", "version-compare 0.1.1", ] [[package]] name = "tao" -version = "0.15.8" +version = "0.16.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac8e6399427c8494f9849b58694754d7cc741293348a6836b6c8d2c5aa82d8e6" +checksum = "6a6d198e01085564cea63e976ad1566c1ba2c2e4cc79578e35d9f05521505e31" dependencies = [ "bitflags 1.3.2", "cairo-rs", @@ -6410,6 +6819,7 @@ dependencies = [ "gdk", "gdk-pixbuf", "gdk-sys", + "gdkwayland-sys", "gdkx11-sys", "gio", "glib", @@ -6428,18 +6838,29 @@ dependencies = [ "objc", "once_cell", "parking_lot 0.12.1", - "paste", "png", "raw-window-handle", "scopeguard", "serde", + "tao-macros", "unicode-segmentation", - "uuid 1.3.0", + "uuid 1.4.1", "windows 0.39.0", "windows-implement", "x11-dl", ] +[[package]] +name = "tao-macros" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec114582505d158b669b136e6851f85840c109819d77c42bb7c0709f727d18c2" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "tap" version = "1.0.1" @@ -6448,9 +6869,9 @@ checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" [[package]] name = "tar" -version = "0.4.38" +version = "0.4.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b55807c0344e1e6c04d7c965f5289c39a8d94ae23ed5c0b57aabac549f871c6" +checksum = "b16afcea1f22891c49a00c751c7b63b2233284064f11a200fc624137c51e2ddb" dependencies = [ "filetime", "libc", @@ -6458,14 +6879,20 @@ dependencies = [ ] [[package]] -name = "tauri" -version = "1.2.4" +name = "target-lexicon" +version = "0.12.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe7e0f1d535e7cbbbab43c82be4fc992b84f9156c16c160955617e0260ebc449" +checksum = "9d0e916b1148c8e263850e1ebcbd046f333e0683c724876bb0da63ea4373dc8a" + +[[package]] +name = "tauri" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fbe522898e35407a8e60dc3870f7579fea2fc262a6a6072eccdd37ae1e1d91e" dependencies = [ "anyhow", - "attohttpc", - "base64 0.13.1", + "base64 0.21.2", + "bytes", "cocoa", "dirs-next", "embed_plist", @@ -6487,8 +6914,9 @@ dependencies = [ "rand 0.8.5", "raw-window-handle", "regex", + "reqwest", "rfd", - "semver 1.0.17", + "semver 1.0.18", "serde", "serde_json", "serde_repr", @@ -6504,7 +6932,7 @@ dependencies = [ "time", "tokio", "url", - "uuid 1.3.0", + "uuid 1.4.1", "webkit2gtk", "webview2-com", "windows 0.39.0", @@ -6513,27 +6941,28 @@ dependencies = [ [[package]] name = "tauri-build" -version = "1.2.1" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8807c85d656b2b93927c19fe5a5f1f1f348f96c2de8b90763b3c2d561511f9b4" +checksum = "7d2edd6a259b5591c8efdeb9d5702cb53515b82a6affebd55c7fd6d3a27b7d1b" dependencies = [ "anyhow", "cargo_toml", "heck 0.4.1", "json-patch", - "semver 1.0.17", + "semver 1.0.18", + "serde", "serde_json", "tauri-utils", - "winres", + "tauri-winres", ] [[package]] name = "tauri-codegen" -version = "1.2.1" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14388d484b6b1b5dc0f6a7d6cc6433b3b230bec85eaa576adcdf3f9fafa49251" +checksum = "54ad2d49fdeab4a08717f5b49a163bdc72efc3b1950b6758245fcde79b645e1a" dependencies = [ - "base64 0.13.1", + "base64 0.21.2", "brotli", "ico", "json-patch", @@ -6542,22 +6971,22 @@ dependencies = [ "proc-macro2", "quote", "regex", - "semver 1.0.17", + "semver 1.0.18", "serde", "serde_json", - "sha2 0.10.6", + "sha2 0.10.7", "tauri-utils", "thiserror", "time", - "uuid 1.3.0", + "uuid 1.4.1", "walkdir", ] [[package]] name = "tauri-macros" -version = "1.2.1" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "069319e5ecbe653a799b94b0690d9f9bf5d00f7b1d3989aa331c524d4e354075" +checksum = "8eb12a2454e747896929338d93b0642144bb51e0dddbb36e579035731f0d76b7" dependencies = [ "heck 0.4.1", "proc-macro2", @@ -6569,9 +6998,9 @@ dependencies = [ [[package]] name = "tauri-runtime" -version = "0.12.1" +version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c507d954d08ac8705d235bc70ec6975b9054fb95ff7823af72dbb04186596f3b" +checksum = "108683199cb18f96d2d4134187bb789964143c845d2d154848dda209191fd769" dependencies = [ "gtk", "http", @@ -6582,16 +7011,17 @@ dependencies = [ "serde_json", "tauri-utils", "thiserror", - "uuid 1.3.0", + "url", + "uuid 1.4.1", "webview2-com", "windows 0.39.0", ] [[package]] name = "tauri-runtime-wry" -version = "0.12.2" +version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36b1c5764a41a13176a4599b5b7bd0881bea7d94dfe45e1e755f789b98317e30" +checksum = "0b7aa256a1407a3a091b5d843eccc1a5042289baf0a43d1179d9f0fcfea37c1b" dependencies = [ "cocoa", "gtk", @@ -6600,7 +7030,7 @@ dependencies = [ "raw-window-handle", "tauri-runtime", "tauri-utils", - "uuid 1.3.0", + "uuid 1.4.1", "webkit2gtk", "webview2-com", "windows 0.39.0", @@ -6609,12 +7039,13 @@ dependencies = [ [[package]] name = "tauri-utils" -version = "1.2.1" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5abbc109a6eb45127956ffcc26ef0e875d160150ac16cfa45d26a6b2871686f1" +checksum = "03fc02bb6072bb397e1d473c6f76c953cda48b4a2d0cce605df284aa74a12e84" dependencies = [ "brotli", "ctor", + "dunce", "glob", "heck 0.4.1", "html5ever", @@ -6625,7 +7056,7 @@ dependencies = [ "phf 0.10.1", "proc-macro2", "quote", - "semver 1.0.17", + "semver 1.0.18", "serde", "serde_json", "serde_with", @@ -6636,27 +7067,36 @@ dependencies = [ ] [[package]] -name = "tauri-winrt-notification" -version = "0.1.0" +name = "tauri-winres" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c58de036c4d2e20717024de2a3c4bf56c301f07b21bc8ef9b57189fce06f1f3b" +checksum = "5993dc129e544393574288923d1ec447c857f3f644187f4fbf7d9a875fbfc4fb" +dependencies = [ + "embed-resource", + "toml 0.7.6", +] + +[[package]] +name = "tauri-winrt-notification" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f5bff1d532fead7c43324a0fa33643b8621a47ce2944a633be4cb6c0240898f" dependencies = [ "quick-xml 0.23.1", - "strum", "windows 0.39.0", ] [[package]] name = "tempfile" -version = "3.4.0" +version = "3.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af18f7ae1acd354b992402e9ec5864359d693cd8a79dcbef59f76891701c1e95" +checksum = "dc02fddf48964c42031a0b3fe0428320ecf3a73c401040fc0096f97794310651" dependencies = [ "cfg-if", - "fastrand", - "redox_syscall", - "rustix", - "windows-sys 0.42.0", + "fastrand 2.0.0", + "redox_syscall 0.3.5", + "rustix 0.38.8", + "windows-sys 0.48.0", ] [[package]] @@ -6667,7 +7107,7 @@ checksum = "3f0a7d05cf78524782337f8edd55cbc578d159a16ad4affe2135c92f7dbac7f0" dependencies = [ "bytes", "digest 0.10.7", - "ed25519 2.2.1", + "ed25519 2.2.2", "ed25519-consensus", "flex-error", "futures", @@ -6681,7 +7121,7 @@ dependencies = [ "serde_bytes", "serde_json", "serde_repr", - "sha2 0.10.6", + "sha2 0.10.7", "signature 2.1.0", "subtle 2.4.1", "subtle-encoding", @@ -6739,7 +7179,7 @@ dependencies = [ "hyper-rustls", "peg", "pin-project", - "semver 1.0.17", + "semver 1.0.18", "serde", "serde_bytes", "serde_json", @@ -6785,22 +7225,22 @@ checksum = "8eaa81235c7058867fa8c0e7314f33dcce9c215f535d1913822a2b3f5e289f3c" [[package]] name = "thiserror" -version = "1.0.40" +version = "1.0.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "978c9a314bd8dc99be594bc3c175faaa9794be04a5a5e153caba6915336cebac" +checksum = "611040a08a0439f8248d1990b111c95baa9c704c805fa1f62104b39655fd7f90" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.40" +version = "1.0.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9456a42c5b0d803c8cd86e73dd7cc9edd429499f37a3550d286d5e86720569f" +checksum = "090198534930841fab3a5d1bb637cde49e339654e606195f8d9c76eeb081dc96" dependencies = [ "proc-macro2", "quote", - "syn 2.0.5", + "syn 2.0.28", ] [[package]] @@ -6815,11 +7255,12 @@ dependencies = [ [[package]] name = "time" -version = "0.3.20" +version = "0.3.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd0cbfecb4d19b5ea75bb31ad904eb5b9fa13f21079c3b92017ebdf4999a5890" +checksum = "b0fdd63d58b18d663fbdf70e049f00a22c8e42be082203be7f26589213cd75ea" dependencies = [ - "itoa 1.0.6", + "deranged", + "itoa 1.0.9", "js-sys", "libc", "num_threads", @@ -6830,15 +7271,15 @@ dependencies = [ [[package]] name = "time-core" -version = "0.1.0" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e153e1f1acaef8acc537e68b44906d2db6436e2b35ac2c6b42640fff91f00fd" +checksum = "7300fbefb4dadc1af235a9cef3737cea692a9d97e1b9cbcd4ebdae6f8868e6fb" [[package]] name = "time-macros" -version = "0.2.8" +version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd80a657e71da814b8e5d60d3374fc6d35045062245d80224748ae522dd76f36" +checksum = "eb71511c991639bb078fd5bf97757e03914361c48100d52878b8e52b46fb92cd" dependencies = [ "time-core", ] @@ -6860,32 +7301,31 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.26.0" +version = "1.31.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03201d01c3c27a29c8a5cee5b55a93ddae1ccf6f08f65365c2c918f8c1b76f64" +checksum = "40de3a2ba249dcb097e01be5e67a5ff53cf250397715a071a81543e8a832a920" dependencies = [ - "autocfg", + "backtrace", "bytes", "libc", - "memchr", "mio", "num_cpus", "pin-project-lite", "signal-hook-registry", - "socket2", + "socket2 0.5.3", "tokio-macros", - "windows-sys 0.45.0", + "windows-sys 0.48.0", ] [[package]] name = "tokio-macros" -version = "1.8.2" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d266c00fde287f55d3f1c3e96c500c362a2b8c695076ec180f27918820bc6df8" +checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e" dependencies = [ "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.28", ] [[package]] @@ -6934,9 +7374,9 @@ dependencies = [ [[package]] name = "tokio-stream" -version = "0.1.12" +version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fb52b74f05dbf495a8fba459fdc331812b96aa086d9eb78101fa0d4569c3313" +checksum = "397c988d37662c7dda6d2208364a706264bf3d6138b11d436cbac0ad38832842" dependencies = [ "futures-core", "pin-project-lite", @@ -6959,9 +7399,9 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.7" +version = "0.7.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5427d89453009325de0d8f342c9490009f76e999cb7672d77e46267448f7e6b2" +checksum = "806fe8c2c87eccc8b3267cbae29ed3ab2d0bd37fca70ab622e46aaa9375ddb7d" dependencies = [ "bytes", "futures-core", @@ -6983,9 +7423,9 @@ dependencies = [ [[package]] name = "toml" -version = "0.7.4" +version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6135d499e69981f9ff0ef2167955a5333c35e36f6937d382974566b3d5b94ec" +checksum = "c17e963a819c331dcacd7ab957d80bc2b9a9c1e71c804826d2f283dd65306542" dependencies = [ "serde", "serde_spanned", @@ -7004,9 +7444,9 @@ dependencies = [ [[package]] name = "toml_edit" -version = "0.19.12" +version = "0.19.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c500344a19072298cd05a7224b3c0c629348b78692bf48466c5238656e315a78" +checksum = "f8123f27e969974a3dfba720fdb560be359f57b44302d280ba72e76a74480e8a" dependencies = [ "indexmap 2.0.0", "serde", @@ -7035,20 +7475,20 @@ dependencies = [ [[package]] name = "tracing-attributes" -version = "0.1.23" +version = "0.1.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4017f8f45139870ca7e672686113917c71c7a6e02d4924eda67186083c03081a" +checksum = "5f4f31f56159e98206da9efd823404b79b6ef3143b4a7ab76e67b1751b25a4ab" dependencies = [ "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.28", ] [[package]] name = "tracing-core" -version = "0.1.30" +version = "0.1.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24eb03ba0eab1fd845050058ce5e616558e8f8d8fca633e6b163fe25c797213a" +checksum = "0955b8137a1df6f1a2e9a37d8a6656291ff0297c1a97c24e0d8425fe2312f79a" dependencies = [ "once_cell", "valuable", @@ -7067,9 +7507,9 @@ dependencies = [ [[package]] name = "tracing-subscriber" -version = "0.3.16" +version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6176eae26dd70d0c919749377897b54a9276bd7061339665dd68777926b5a70" +checksum = "30a651bc37f915e81f087d86e62a18eec5f79550c7faff886f7090b4ea757c77" dependencies = [ "matchers", "nu-ansi-term", @@ -7085,9 +7525,9 @@ dependencies = [ [[package]] name = "treediff" -version = "3.0.2" +version = "4.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "761e8d5ad7ce14bb82b7e61ccc0ca961005a275a060b9644a2431aa11553c2ff" +checksum = "52984d277bdf2a751072b5df30ec0377febdb02f7696d64c2d7d54630bac4303" dependencies = [ "serde_json", ] @@ -7149,9 +7589,19 @@ checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba" [[package]] name = "ucd-trie" -version = "0.1.5" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e79c4d996edb816c91e4308506774452e55e95c3c9de07b6729e17e15a5ef81" +checksum = "ed646292ffc8188ef8ea4d1e0e0150fb15a5c2e12ad9b8fc191ae7a8a7f3c4b9" + +[[package]] +name = "uds_windows" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce65604324d3cce9b966701489fbd0cf318cb1f7bd9dd07ac9a4ee6fb791930d" +dependencies = [ + "tempfile", + "winapi", +] [[package]] name = "uname" @@ -7170,9 +7620,9 @@ checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460" [[package]] name = "unicode-ident" -version = "1.0.8" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5464a87b239f13a63a501f2701565754bae92d243d4bb7eb12f6d57d2269bf4" +checksum = "301abaae475aa91687eb82514b328ab47a211a533026cb25fc3e519b86adfc3c" [[package]] name = "unicode-normalization" @@ -7189,12 +7639,6 @@ version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36" -[[package]] -name = "unicode-xid" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c" - [[package]] name = "unicode_categories" version = "0.1.1" @@ -7203,9 +7647,9 @@ checksum = "39ec24b3121d976906ece63c9daad25b85969647682eee313cb5779fdd69e14e" [[package]] name = "universal-hash" -version = "0.5.0" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d3160b73c9a19f7e2939a2fdad446c57c1bbbbf4d919d3213ff1267a580d8b5" +checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea" dependencies = [ "crypto-common", "subtle 2.4.1", @@ -7232,9 +7676,9 @@ dependencies = [ [[package]] name = "url" -version = "2.3.1" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d68c799ae75762b8c3fe375feb6600ef5602c883c5d21eb51c09f22b83c4643" +checksum = "50bff7831e19200a85b17131d085c25d7811bc4e186efdaf54bbd132994a88cb" dependencies = [ "form_urlencoded", "idna", @@ -7262,9 +7706,9 @@ checksum = "bc5cf98d8186244414c848017f0e2676b3fcb46807f6668a97dfe67359a3c4b7" [[package]] name = "uuid" -version = "1.3.0" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1674845326ee10d37ca60470760d4288a6f80f304007d92e5c53bab78c9cfd79" +checksum = "79daa5ed5740825c40b389c5e50312b9c86df53fccd33f281df655642b43869d" dependencies = [ "getrandom 0.2.10", "serde", @@ -7317,6 +7761,26 @@ version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" +[[package]] +name = "vswhom" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be979b7f07507105799e854203b470ff7c78a1639e330a58f183b5fea574608b" +dependencies = [ + "libc", + "vswhom-sys", +] + +[[package]] +name = "vswhom-sys" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3b17ae1f6c8a2b28506cd96d412eebf83b4a0ff2cbefeeb952f2f9dfa44ba18" +dependencies = [ + "cc", + "libc", +] + [[package]] name = "vte" version = "0.10.1" @@ -7338,6 +7802,12 @@ dependencies = [ "quote", ] +[[package]] +name = "waker-fn" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d5b2c62b4012a3e1eca5a7e077d13b3bf498c4073e33ccd58626607748ceeca" + [[package]] name = "walkdir" version = "2.3.3" @@ -7350,11 +7820,10 @@ dependencies = [ [[package]] name = "want" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ce8a968cb1cd110d136ff8b819a556d6fb6d919363c61534f6860c7eb172ba0" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" dependencies = [ - "log", "try-lock", ] @@ -7436,6 +7905,19 @@ version = "0.2.83" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1c38c045535d93ec4f0b4defec448e4291638ee608530863b1e2ba115d4fff7f" +[[package]] +name = "wasm-streams" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6bbae3363c08332cadccd13b67db371814cd214c2524020932f0804b8cf7c078" +dependencies = [ + "futures-util", + "js-sys", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + [[package]] name = "wasm-utils" version = "0.1.0" @@ -7517,7 +7999,7 @@ dependencies = [ "pango-sys", "pkg-config", "soup2-sys", - "system-deps 6.0.3", + "system-deps 6.1.1", ] [[package]] @@ -7656,11 +8138,11 @@ dependencies = [ [[package]] name = "windows" -version = "0.44.0" +version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e745dab35a0c4c77aa3ce42d595e13d2003d6902d6b08c9ef5fc326d08da12b" +checksum = "e686886bc078bc1b0b600cac0147aadb815089b6e4da64016cbd754b6342700f" dependencies = [ - "windows-targets 0.42.2", + "windows-targets", ] [[package]] @@ -7704,44 +8186,20 @@ dependencies = [ "windows_x86_64_msvc 0.42.2", ] -[[package]] -name = "windows-sys" -version = "0.45.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" -dependencies = [ - "windows-targets 0.42.2", -] - [[package]] name = "windows-sys" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" dependencies = [ - "windows-targets 0.48.0", + "windows-targets", ] [[package]] name = "windows-targets" -version = "0.42.2" +version = "0.48.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" -dependencies = [ - "windows_aarch64_gnullvm 0.42.2", - "windows_aarch64_msvc 0.42.2", - "windows_i686_gnu 0.42.2", - "windows_i686_msvc 0.42.2", - "windows_x86_64_gnu 0.42.2", - "windows_x86_64_gnullvm 0.42.2", - "windows_x86_64_msvc 0.42.2", -] - -[[package]] -name = "windows-targets" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b1eb6f0cd7c80c79759c929114ef071b87354ce476d9d94271031c0497adfd5" +checksum = "05d4b17490f70499f20b9e791dcf6a299785ce8af4d709018206dc5b4953e95f" dependencies = [ "windows_aarch64_gnullvm 0.48.0", "windows_aarch64_msvc 0.48.0", @@ -7904,9 +8362,9 @@ checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" [[package]] name = "winnow" -version = "0.4.6" +version = "0.5.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61de7bac303dc551fe038e2b3cef0f571087a47571ea6e79a87692ac99b99699" +checksum = "5504cc7644f4b593cbc05c4a55bf9bd4e94b867c3c0bd440934174d50482427d" dependencies = [ "memchr", ] @@ -7921,19 +8379,20 @@ dependencies = [ ] [[package]] -name = "winres" -version = "0.1.12" +name = "winreg" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b68db261ef59e9e52806f688020631e987592bd83619edccda9c47d42cde4f6c" +checksum = "76a1a57ff50e9b408431e8f97d5456f2807f8eb2a2cd79b06068fc87f8ecf189" dependencies = [ - "toml 0.5.11", + "cfg-if", + "winapi", ] [[package]] name = "wry" -version = "0.23.4" +version = "0.24.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c1ad8e2424f554cc5bdebe8aa374ef5b433feff817aebabca0389961fc7ef98" +checksum = "33748f35413c8a98d45f7a08832d848c0c5915501803d1faade5a4ebcd258cea" dependencies = [ "base64 0.13.1", "block", @@ -7955,7 +8414,7 @@ dependencies = [ "once_cell", "serde", "serde_json", - "sha2 0.10.6", + "sha2 0.10.7", "soup2", "tao", "thiserror", @@ -8010,13 +8469,23 @@ dependencies = [ [[package]] name = "xattr" -version = "0.2.3" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d1526bbe5aaeb5eb06885f4d987bcdfa5e23187055de9b83fe00156a821fabc" +checksum = "f4686009f71ff3e5c4dbcf1a282d0a44db3f021ba69350cd42086b3e5f1c6985" dependencies = [ "libc", ] +[[package]] +name = "xdg-home" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2769203cd13a0c6015d515be729c526d041e9cf2c0cc478d57faee85f40c6dcd" +dependencies = [ + "nix", + "winapi", +] + [[package]] name = "yaml-rust" version = "0.4.5" @@ -8026,6 +8495,72 @@ dependencies = [ "linked-hash-map", ] +[[package]] +name = "zbus" +version = "3.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31de390a2d872e4cd04edd71b425e29853f786dc99317ed72d73d6fcf5ebb948" +dependencies = [ + "async-broadcast", + "async-executor", + "async-fs", + "async-io", + "async-lock", + "async-process", + "async-recursion", + "async-task", + "async-trait", + "blocking", + "byteorder", + "derivative", + "enumflags2", + "event-listener", + "futures-core", + "futures-sink", + "futures-util", + "hex", + "nix", + "once_cell", + "ordered-stream", + "rand 0.8.5", + "serde", + "serde_repr", + "sha1", + "static_assertions", + "tracing", + "uds_windows", + "winapi", + "xdg-home", + "zbus_macros", + "zbus_names", + "zvariant", +] + +[[package]] +name = "zbus_macros" +version = "3.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d1794a946878c0e807f55a397187c11fc7a038ba5d868e7db4f3bd7760bc9d" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "regex", + "syn 1.0.109", + "zvariant_utils", +] + +[[package]] +name = "zbus_names" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb80bb776dbda6e23d705cf0123c3b95df99c4ebeaec6c2599d4a5419902b4a9" +dependencies = [ + "serde", + "static_assertions", + "zvariant", +] + [[package]] name = "zeroize" version = "1.6.0" @@ -8037,23 +8572,60 @@ dependencies = [ [[package]] name = "zeroize_derive" -version = "1.3.3" +version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44bf07cb3e50ea2003396695d58bf46bc9887a1f362260446fad6bc4e79bd36c" +checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn 1.0.109", - "synstructure", + "syn 2.0.28", ] [[package]] name = "zip" -version = "0.6.4" +version = "0.6.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0445d0fbc924bb93539b4316c11afb121ea39296f99a3c4c9edad09e3658cdef" +checksum = "760394e246e4c28189f19d488c058bf16f564016aefac5d32bb1f3b51d5e9261" dependencies = [ "byteorder", "crc32fast", "crossbeam-utils", ] + +[[package]] +name = "zvariant" +version = "3.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44b291bee0d960c53170780af148dca5fa260a63cdd24f1962fa82e03e53338c" +dependencies = [ + "byteorder", + "enumflags2", + "libc", + "serde", + "static_assertions", + "zvariant_derive", +] + +[[package]] +name = "zvariant_derive" +version = "3.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "934d7a7dfc310d6ee06c87ffe88ef4eca7d3e37bb251dece2ef93da8f17d8ecd" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 1.0.109", + "zvariant_utils", +] + +[[package]] +name = "zvariant_utils" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7234f0d811589db492d16893e3f21e8e2fd282e6d01b0cddee310322062cc200" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] diff --git a/nym-connect/desktop/src-tauri/Cargo.toml b/nym-connect/desktop/src-tauri/Cargo.toml index 0d1089132d..2fe834fae2 100644 --- a/nym-connect/desktop/src-tauri/Cargo.toml +++ b/nym-connect/desktop/src-tauri/Cargo.toml @@ -30,7 +30,7 @@ itertools = "0.10.5" log = { version = "0.4", features = ["serde"] } pretty_env_logger = "0.4.0" rand = "0.8" -reqwest = { version = "0.11", features = ["json", "socks"] } +reqwest = { version = "0.11.18", features = ["json", "socks"] } rust-embed = { version = "6.4.2", features = ["include-exclude"] } serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" diff --git a/nym-connect/mobile/src-tauri/Cargo.lock b/nym-connect/mobile/src-tauri/Cargo.lock index 82622e8759..c8bf74afe3 100644 --- a/nym-connect/mobile/src-tauri/Cargo.lock +++ b/nym-connect/mobile/src-tauri/Cargo.lock @@ -21,7 +21,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c192eb8f11fc081b0fe4259ba5af04217d4e0faddd02417310a927911abd7c8" dependencies = [ "crypto-common", - "generic-array 0.14.6", + "generic-array 0.14.7", ] [[package]] @@ -54,7 +54,7 @@ version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47" dependencies = [ - "getrandom 0.2.8", + "getrandom 0.2.10", "once_cell", "version_check", ] @@ -199,6 +199,12 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "349a06037c7bf932dd7e7d1f653678b2038b9ad46a74102f1fc7bd7872678cce" +[[package]] +name = "base16ct" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" + [[package]] name = "base64" version = "0.13.1" @@ -207,9 +213,9 @@ checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" [[package]] name = "base64" -version = "0.21.0" +version = "0.21.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4a4ddaa51a5bc52a6948f74c06d20aaaddb71924eab79b8c97a8c556e942d6a" +checksum = "604178f6c5c21f02dc555784810edfb88d34ac2c73b2eae109655649ee73ce3d" [[package]] name = "base64ct" @@ -218,19 +224,28 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" [[package]] -name = "bip32" -version = "0.3.0" +name = "bincode" +version = "1.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "873faa4363bfc54c36a48321da034c92a0645a363eed34d948683ffc1706e37f" +checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" dependencies = [ - "bs58", - "hmac 0.11.0", - "k256", + "serde", +] + +[[package]] +name = "bip32" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e141fb0f8be1c7b45887af94c88b182472b57c96b56773250ae00cd6a14a164" +dependencies = [ + "bs58 0.5.0", + "hmac 0.12.1", + "k256 0.13.1", "once_cell", "pbkdf2", "rand_core 0.6.4", - "ripemd160", - "sha2 0.9.9", + "ripemd", + "sha2 0.10.6", "subtle 2.4.1", "zeroize", ] @@ -302,7 +317,7 @@ dependencies = [ "cc", "cfg-if", "constant_time_eq", - "digest 0.10.6", + "digest 0.10.7", ] [[package]] @@ -317,8 +332,7 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" dependencies = [ - "block-padding", - "generic-array 0.14.6", + "generic-array 0.14.7", ] [[package]] @@ -327,15 +341,9 @@ version = "0.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" dependencies = [ - "generic-array 0.14.6", + "generic-array 0.14.7", ] -[[package]] -name = "block-padding" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d696c370c750c948ada61c69a0ee2cbbb9c50b1019ddb86d9317157a99c2cae" - [[package]] name = "bls12_381" version = "0.5.0" @@ -364,6 +372,12 @@ dependencies = [ "zeroize", ] +[[package]] +name = "bnum" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "845141a4fade3f790628b7daaaa298a25b204fb28907eb54febe5142db6ce653" + [[package]] name = "brotli" version = "3.3.4" @@ -390,8 +404,14 @@ name = "bs58" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "771fe0050b883fcc3ea2359b1a96bcfbc090b7116eae7c3c512c7a083fdf23d3" + +[[package]] +name = "bs58" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f5353f36341f7451062466f0b755b96ac3a9547e4d7f6b70d603fc721a7d7896" dependencies = [ - "sha2 0.9.9", + "sha2 0.10.6", ] [[package]] @@ -433,6 +453,9 @@ name = "bytes" version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be" +dependencies = [ + "serde", +] [[package]] name = "cairo-rs" @@ -466,7 +489,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2bfbc36312494041e2cdd5f06697b7e89d4b76f42773a0b5556ac290ff22acc2" dependencies = [ "serde", - "toml", + "toml 0.5.11", ] [[package]] @@ -550,7 +573,7 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7ee52072ec15386f770805afd189a01c8841be8696bed250fa2f13c4c0d6dfb7" dependencies = [ - "generic-array 0.14.6", + "generic-array 0.14.7", ] [[package]] @@ -620,47 +643,6 @@ dependencies = [ "os_str_bytes", ] -[[package]] -name = "client-core" -version = "1.1.13" -dependencies = [ - "async-trait", - "dashmap", - "dirs", - "futures", - "gateway-client", - "gateway-requests", - "gloo-timers", - "humantime-serde", - "log", - "nym-bandwidth-controller", - "nym-config", - "nym-credential-storage", - "nym-crypto", - "nym-nonexhaustive-delayqueue", - "nym-pemstore", - "nym-sphinx", - "nym-task", - "nym-topology", - "rand 0.7.3", - "serde", - "serde_json", - "sqlx 0.6.3", - "tap", - "thiserror", - "time", - "tokio", - "tokio-stream", - "tokio-tungstenite", - "tungstenite", - "url", - "validator-client", - "wasm-bindgen", - "wasm-bindgen-futures", - "wasm-timer", - "wasm-utils", -] - [[package]] name = "cocoa" version = "0.24.1" @@ -732,9 +714,9 @@ dependencies = [ [[package]] name = "const-oid" -version = "0.7.1" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4c78c047431fee22c1a7bb92e00ad095a02a983affe4d8a72e2a2c62c1b94f3" +checksum = "28c122c3980598d243d63d9a704629a2d748d101f278052ff068be5a4423ab6f" [[package]] name = "constant_time_eq" @@ -791,8 +773,9 @@ dependencies = [ [[package]] name = "cosmos-sdk-proto" -version = "0.12.3" -source = "git+https://github.com/neacsu/cosmos-rust?branch=neacsu/feegrant_support#f63ded63ec13e753ebe8bdafe9dc503df265d67d" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73c9d2043a9e617b0d602fbc0a0ecd621568edbf3a9774890a6d562389bd8e1c" dependencies = [ "prost", "prost-types", @@ -801,17 +784,16 @@ dependencies = [ [[package]] name = "cosmrs" -version = "0.7.1" -source = "git+https://github.com/neacsu/cosmos-rust?branch=neacsu/feegrant_support#f63ded63ec13e753ebe8bdafe9dc503df265d67d" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af13955d6f356272e6def9ff5e2450a7650df536d8934f47052a20c76513d2f6" dependencies = [ "bip32", "cosmos-sdk-proto", - "ecdsa", + "ecdsa 0.16.8", "eyre", - "getrandom 0.2.8", - "k256", - "prost", - "prost-types", + "getrandom 0.2.10", + "k256 0.13.1", "rand_core 0.6.4", "serde", "serde_json", @@ -823,41 +805,68 @@ dependencies = [ [[package]] name = "cosmwasm-crypto" -version = "1.0.0" +version = "1.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5eb0afef2325df81aadbf9be1233f522ed8f6e91df870c764bc44cca2b1415bd" +checksum = "7e272708a9745dad8b591ef8a718571512130f2b39b33e3d7a27c558e3069394" dependencies = [ - "digest 0.9.0", + "digest 0.10.7", "ed25519-zebra", - "k256", + "k256 0.11.6", "rand_core 0.6.4", "thiserror", ] [[package]] name = "cosmwasm-derive" -version = "1.0.0" +version = "1.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b36e527620a2a3e00e46b6e731ab6c9b68d11069c986f7d7be8eba79ef081a4" +checksum = "296db6a3caca5283425ae0cf347f4e46999ba3f6620dbea8939a0e00347831ce" dependencies = [ "syn 1.0.109", ] [[package]] -name = "cosmwasm-std" -version = "1.0.0" +name = "cosmwasm-schema" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "875994993c2082a6fcd406937bf0fca21c349e4a624f3810253a14fa83a3a195" +checksum = "bb6b2fb76758ef59cddc77f2e2ae91c22f77da49037e9f182e9c2833f0e959b1" +dependencies = [ + "cosmwasm-schema-derive", + "schemars", + "serde", + "serde_json", + "thiserror", +] + +[[package]] +name = "cosmwasm-schema-derive" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bfa39422f0d9f1c9a6fd3711573258495314dfa3aae738ea825ecd9964bc659" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "cosmwasm-std" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f6dc2ee23313add5ecacc3ccac217b9967ad9d2d11bd56e5da6aa65a9da6138" dependencies = [ "base64 0.13.1", + "bnum", "cosmwasm-crypto", "cosmwasm-derive", + "derivative", "forward_ref", + "hex", "schemars", "serde", "serde-json-wasm", + "sha2 0.10.6", "thiserror", - "uint", ] [[package]] @@ -962,18 +971,24 @@ dependencies = [ ] [[package]] -name = "crunchy" -version = "0.2.2" +name = "crypto-bigint" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" +checksum = "ef2b4b23cddf68b89b8f8069890e8c270d54e2d5fe1b143820234805e4cb17ef" +dependencies = [ + "generic-array 0.14.7", + "rand_core 0.6.4", + "subtle 2.4.1", + "zeroize", +] [[package]] name = "crypto-bigint" -version = "0.3.2" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03c6a1d5fa1de37e071642dfa44ec552ca5b299adb128fab16138e24b548fd21" +checksum = "cf4c2f4e1afd912bc40bfd6fed5d9dc1f288e0ba01bfcc835cc5bc3eb13efe15" dependencies = [ - "generic-array 0.14.6", + "generic-array 0.14.7", "rand_core 0.6.4", "subtle 2.4.1", "zeroize", @@ -985,7 +1000,7 @@ version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" dependencies = [ - "generic-array 0.14.6", + "generic-array 0.14.7", "rand_core 0.6.4", "typenum", ] @@ -1006,7 +1021,7 @@ version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b1d1a86f49236c215f271d40892d5fc950490551400b02ef360692c29815c714" dependencies = [ - "generic-array 0.14.6", + "generic-array 0.14.7", "subtle 2.4.1", ] @@ -1088,10 +1103,38 @@ dependencies = [ ] [[package]] -name = "cw-storage-plus" -version = "0.13.4" +name = "curve25519-dalek-ng" +version = "4.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "648b1507290bbc03a8d88463d7cd9b04b1fa0155e5eef366c4fa052b9caaac7a" +checksum = "1c359b7249347e46fb28804470d071c921156ad62b3eef5d34e2ba867533dec8" +dependencies = [ + "byteorder", + "digest 0.9.0", + "rand_core 0.6.4", + "subtle-ng", + "zeroize", +] + +[[package]] +name = "cw-controllers" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5d8edce4b78785f36413f67387e4be7d0cb7d032b5d4164bcc024f9c3f3f2ea" +dependencies = [ + "cosmwasm-schema", + "cosmwasm-std", + "cw-storage-plus", + "cw-utils", + "schemars", + "serde", + "thiserror", +] + +[[package]] +name = "cw-storage-plus" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f0e92a069d62067f3472c62e30adedb4cab1754725c0f2a682b3128d2bf3c79" dependencies = [ "cosmwasm-std", "schemars", @@ -1100,51 +1143,56 @@ dependencies = [ [[package]] name = "cw-utils" -version = "0.13.4" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9dbaecb78c8e8abfd6b4258c7f4fbeb5c49a5e45ee4d910d3240ee8e1d714e1b" +checksum = "c80e93d1deccb8588db03945016a292c3c631e6325d349ebb35d2db6f4f946f7" dependencies = [ + "cosmwasm-schema", "cosmwasm-std", + "cw2", "schemars", + "semver 1.0.17", "serde", "thiserror", ] [[package]] name = "cw2" -version = "0.13.4" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04cf4639517490dd36b333bbd6c4fbd92e325fd0acf4683b41753bc5eb63bfc1" +checksum = "29ac2dc7a55ad64173ca1e0a46697c31b7a5c51342f55a1e84a724da4eb99908" dependencies = [ + "cosmwasm-schema", "cosmwasm-std", "cw-storage-plus", "schemars", "serde", + "thiserror", +] + +[[package]] +name = "cw20" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "011c45920f8200bd5d32d4fe52502506f64f2f75651ab408054d4cfc75ca3a9b" +dependencies = [ + "cosmwasm-schema", + "cosmwasm-std", + "cw-utils", + "schemars", + "serde", ] [[package]] name = "cw3" -version = "0.13.4" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe19462a7f644ba60c19d3443cb90d00c50d9b6b3b0a3a7fca93df8261af979b" +checksum = "171af3d9127de6805a7dd819fb070c7d2f6c3ea85f4193f42cef259f0a7f33d5" dependencies = [ + "cosmwasm-schema", "cosmwasm-std", "cw-utils", - "schemars", - "serde", -] - -[[package]] -name = "cw3-fixed-multisig" -version = "0.13.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df54aa54c13f405ec4ab36b6217538bc957d439eee58f89312db05a79caf6706" -dependencies = [ - "cosmwasm-std", - "cw-storage-plus", - "cw-utils", - "cw2", - "cw3", + "cw20", "schemars", "serde", "thiserror", @@ -1152,10 +1200,11 @@ dependencies = [ [[package]] name = "cw4" -version = "0.13.4" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0acc3549d5ce11c6901b3a676f2e2628684722197054d97cd0101ea174ed5cbd" +checksum = "a398696307efadaaa2d0850076f865fa706c959d493cb4203314f72be6b77a64" dependencies = [ + "cosmwasm-schema", "cosmwasm-std", "cw-storage-plus", "schemars", @@ -1223,11 +1272,33 @@ dependencies = [ [[package]] name = "der" -version = "0.5.1" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6919815d73839e7ad218de758883aae3a257ba6759ce7a9992501efbb53d705c" +checksum = "f1a467a65c5e759bce6e65eaf91cc29f466cdc57cb65777bd646872a8a1fd4de" dependencies = [ "const-oid", + "zeroize", +] + +[[package]] +name = "der" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fffa369a668c8af7dbf8b5e56c9f744fbd399949ed171606040001947de40b1c" +dependencies = [ + "const-oid", + "zeroize", +] + +[[package]] +name = "derivative" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", ] [[package]] @@ -1258,16 +1329,17 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" dependencies = [ - "generic-array 0.14.6", + "generic-array 0.14.7", ] [[package]] name = "digest" -version = "0.10.6" +version = "0.10.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8168378f4e5023e7218c89c891c0fd8ecdb5e5e4f18cb78f38cf245dd021e76f" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ "block-buffer 0.10.4", + "const-oid", "crypto-common", "subtle 2.4.1", ] @@ -1278,7 +1350,16 @@ version = "4.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ca3aa72a6f96ea37bbc5aa912f6788242832f75369bdfdadcb0e38423f100059" dependencies = [ - "dirs-sys", + "dirs-sys 0.3.7", +] + +[[package]] +name = "dirs" +version = "5.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44c45a9d03d6676652bcb5e724c7e988de1acad23a711b5217ab9cbecbec2225" +dependencies = [ + "dirs-sys 0.4.1", ] [[package]] @@ -1302,6 +1383,18 @@ dependencies = [ "winapi", ] +[[package]] +name = "dirs-sys" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "520f05a5cbd335fae5a99ff7a6ab8627577660ee5cfd6a94a6a929b52ff0321c" +dependencies = [ + "libc", + "option-ext", + "redox_users", + "windows-sys 0.48.0", +] + [[package]] name = "dirs-sys-next" version = "0.1.2" @@ -1360,14 +1453,28 @@ checksum = "68b0cf012f1230e43cd00ebb729c6bb58707ecfa8ad08b52ef3a4ccd2697fc30" [[package]] name = "ecdsa" -version = "0.13.4" +version = "0.14.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0d69ae62e0ce582d56380743515fefaf1a8c70cec685d9677636d7e30ae9dc9" +checksum = "413301934810f597c1d19ca71c8710e99a3f1ba28a0d2ebc01551a2daeea3c5c" dependencies = [ - "der", - "elliptic-curve", - "rfc6979", - "signature", + "der 0.6.1", + "elliptic-curve 0.12.3", + "rfc6979 0.3.1", + "signature 1.6.4", +] + +[[package]] +name = "ecdsa" +version = "0.16.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4b1e0c257a9e9f25f90ff76d7a68360ed497ee519c8e428d1825ef0000799d4" +dependencies = [ + "der 0.7.8", + "digest 0.10.7", + "elliptic-curve 0.13.5", + "rfc6979 0.4.0", + "signature 2.1.0", + "spki 0.7.2", ] [[package]] @@ -1376,7 +1483,30 @@ version = "1.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "91cff35c70bba8a626e3185d8cd48cc11b5437e1a5bcd15b9b5fa3c64b6dfee7" dependencies = [ - "signature", + "signature 1.6.4", +] + +[[package]] +name = "ed25519" +version = "2.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60f6d271ca33075c88028be6f04d502853d63a5ece419d269c15315d4fc1cf1d" +dependencies = [ + "pkcs8 0.10.2", + "signature 2.1.0", +] + +[[package]] +name = "ed25519-consensus" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c8465edc8ee7436ffea81d21a019b16676ee3db267aa8d5a8d729581ecf998b" +dependencies = [ + "curve25519-dalek-ng", + "hex", + "rand_core 0.6.4", + "sha2 0.9.9", + "zeroize", ] [[package]] @@ -1386,7 +1516,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c762bae6dcaf24c4c84667b8579785430908723d5c889f469d76a41d59cc7a9d" dependencies = [ "curve25519-dalek", - "ed25519", + "ed25519 1.5.3", "rand 0.7.3", "serde", "sha2 0.9.9", @@ -1416,18 +1546,39 @@ checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91" [[package]] name = "elliptic-curve" -version = "0.11.12" +version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25b477563c2bfed38a3b7a60964c49e058b2510ad3f12ba3483fd8f62c2306d6" +checksum = "e7bb888ab5300a19b8e5bceef25ac745ad065f3c9f7efc6de1b91958110891d3" dependencies = [ - "base16ct", - "crypto-bigint", - "der", - "ff 0.11.1", - "generic-array 0.14.6", - "group 0.11.0", + "base16ct 0.1.1", + "crypto-bigint 0.4.9", + "der 0.6.1", + "digest 0.10.7", + "ff 0.12.1", + "generic-array 0.14.7", + "group 0.12.1", + "pkcs8 0.9.0", "rand_core 0.6.4", - "sec1", + "sec1 0.3.0", + "subtle 2.4.1", + "zeroize", +] + +[[package]] +name = "elliptic-curve" +version = "0.13.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "968405c8fdc9b3bf4df0a6638858cc0b52462836ab6b1c87377785dd09cf1c0b" +dependencies = [ + "base16ct 0.2.0", + "crypto-bigint 0.5.2", + "digest 0.10.7", + "ff 0.13.0", + "generic-array 0.14.7", + "group 0.13.0", + "pkcs8 0.10.2", + "rand_core 0.6.4", + "sec1 0.7.3", "subtle 2.4.1", "zeroize", ] @@ -1480,6 +1631,12 @@ dependencies = [ "termcolor", ] +[[package]] +name = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + [[package]] name = "errno" version = "0.2.8" @@ -1556,6 +1713,26 @@ dependencies = [ "subtle 2.4.1", ] +[[package]] +name = "ff" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d013fc25338cc558c5c2cfbad646908fb23591e2404481826742b651c9af7160" +dependencies = [ + "rand_core 0.6.4", + "subtle 2.4.1", +] + +[[package]] +name = "ff" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ded41244b729663b1e574f1b4fb731469f69f79c17667b5d776b16cda0479449" +dependencies = [ + "rand_core 0.6.4", + "subtle 2.4.1", +] + [[package]] name = "field-offset" version = "0.3.5" @@ -1876,12 +2053,14 @@ dependencies = [ [[package]] name = "generic-array" -version = "0.14.6" +version = "0.14.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bff49e947297f3312447abdca79f45f4738097cc82b06e72054d2223f601f1b9" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" dependencies = [ + "serde", "typenum", "version_check", + "zeroize", ] [[package]] @@ -1899,9 +2078,9 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.8" +version = "0.2.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31" +checksum = "be4136b2a15dd319360be1c07d9933517ccf0be8f16bf62a3bee4f0d618df427" dependencies = [ "cfg-if", "js-sys", @@ -2081,6 +2260,28 @@ dependencies = [ "subtle 2.4.1", ] +[[package]] +name = "group" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5dfbfb3a6cfbd390d5c9564ab283a0349b9b9fcd46a706c1eb10e0db70bfbac7" +dependencies = [ + "ff 0.12.1", + "rand_core 0.6.4", + "subtle 2.4.1", +] + +[[package]] +name = "group" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" +dependencies = [ + "ff 0.13.0", + "rand_core 0.6.4", + "subtle 2.4.1", +] + [[package]] name = "gtk" version = "0.16.2" @@ -2138,9 +2339,9 @@ dependencies = [ [[package]] name = "h2" -version = "0.3.16" +version = "0.3.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5be7b54589b581f624f566bf5d8eb2bab1db736c51528720b6bd36b96b55924d" +checksum = "97ec8491ebaf99c8eaa73058b045fe58073cd6be7f596ac993ced0b0a0c01049" dependencies = [ "bytes", "fnv", @@ -2148,7 +2349,7 @@ dependencies = [ "futures-sink", "futures-util", "http", - "indexmap", + "indexmap 1.9.2", "slab", "tokio", "tokio-util", @@ -2187,6 +2388,12 @@ dependencies = [ "ahash", ] +[[package]] +name = "hashbrown" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c6201b9ff9fd90a5a3bac2e56a830d0caa509576f0e503818ee82c181b3437a" + [[package]] name = "hashlink" version = "0.7.0" @@ -2319,7 +2526,7 @@ version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" dependencies = [ - "digest 0.10.6", + "digest 0.10.7", ] [[package]] @@ -2403,9 +2610,9 @@ dependencies = [ [[package]] name = "hyper" -version = "0.14.25" +version = "0.14.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc5e554ff619822309ffd57d8734d77cd5ce6238bc956f037ea06c58238c9899" +checksum = "ffb1cfd654a8219eaef89881fdb3bb3b1cdc5fa75ded05d6933b2b382e395468" dependencies = [ "bytes", "futures-channel", @@ -2549,6 +2756,16 @@ dependencies = [ "serde", ] +[[package]] +name = "indexmap" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5477fe2230a79769d8dc68e0eabf5437907c0457a5614a9e8dddb67f65eb65d" +dependencies = [ + "equivalent", + "hashbrown 0.14.0", +] + [[package]] name = "infer" version = "0.12.0" @@ -2564,7 +2781,7 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5" dependencies = [ - "generic-array 0.14.6", + "generic-array 0.14.7", ] [[package]] @@ -2583,9 +2800,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" dependencies = [ "cfg-if", - "js-sys", - "wasm-bindgen", - "web-sys", ] [[package]] @@ -2724,25 +2938,28 @@ dependencies = [ [[package]] name = "k256" -version = "0.10.4" +version = "0.11.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19c3a5e0a0b8450278feda242592512e09f61c72e018b8cd5c859482802daf2d" +checksum = "72c1e0b51e7ec0a97369623508396067a486bd0cbed95a2659a4b863d28cfc8b" dependencies = [ "cfg-if", - "ecdsa", - "elliptic-curve", - "sec1", - "sha2 0.9.9", - "sha3", + "ecdsa 0.14.8", + "elliptic-curve 0.12.3", + "sha2 0.10.6", ] [[package]] -name = "keccak" -version = "0.1.3" +name = "k256" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3afef3b6eff9ce9d8ff9b3601125eec7f0c8cbac7abd14f355d053fa56c98768" +checksum = "cadb76004ed8e97623117f3df85b17aaa6626ab0b0831e6573f104df16cd1bcc" dependencies = [ - "cpufeatures", + "cfg-if", + "ecdsa 0.16.8", + "elliptic-curve 0.13.5", + "once_cell", + "sha2 0.10.6", + "signature 2.1.0", ] [[package]] @@ -2771,9 +2988,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.140" +version = "0.2.147" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99227334921fae1a979cf0bfdfcc6b3e5ce376ef57e16fb6fb3ea2ed6095f80c" +checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3" [[package]] name = "libdbus-sys" @@ -3174,7 +3391,7 @@ dependencies = [ name = "nym-api-requests" version = "0.1.0" dependencies = [ - "bs58", + "bs58 0.4.0", "cosmrs", "cosmwasm-std", "getset", @@ -3194,15 +3411,15 @@ dependencies = [ "nym-credentials", "nym-crypto", "nym-network-defaults", + "nym-validator-client", "rand 0.7.3", "thiserror", "url", - "validator-client", ] [[package]] name = "nym-bin-common" -version = "0.3.0" +version = "0.6.0" dependencies = [ "atty", "clap", @@ -3217,19 +3434,24 @@ dependencies = [ [[package]] name = "nym-client-core" -version = "1.1.13" +version = "1.1.15" dependencies = [ "async-trait", + "base64 0.21.2", "dashmap", - "dirs", + "dirs 4.0.0", "futures", "gloo-timers", "humantime-serde", "log", + "nym-bandwidth-controller", "nym-config", + "nym-credential-storage", "nym-crypto", + "nym-explorer-api-requests", "nym-gateway-client", "nym-gateway-requests", + "nym-network-defaults", "nym-nonexhaustive-delayqueue", "nym-pemstore", "nym-sphinx", @@ -3237,8 +3459,10 @@ dependencies = [ "nym-topology", "nym-validator-client", "rand 0.7.3", + "reqwest", "serde", "serde_json", + "sha2 0.10.6", "sqlx 0.6.3", "tap", "thiserror", @@ -3250,8 +3474,9 @@ dependencies = [ "url", "wasm-bindgen", "wasm-bindgen-futures", - "wasm-timer", "wasm-utils", + "wasmtimer", + "zeroize", ] [[package]] @@ -3259,10 +3484,10 @@ name = "nym-coconut" version = "0.5.0" dependencies = [ "bls12_381 0.6.0", - "bs58", + "bs58 0.4.0", "digest 0.9.0", "ff 0.11.1", - "getrandom 0.2.8", + "getrandom 0.2.10", "group 0.11.0", "itertools", "nym-dkg", @@ -3278,29 +3503,27 @@ dependencies = [ name = "nym-coconut-bandwidth-contract-common" version = "0.1.0" dependencies = [ + "cosmwasm-schema", "cosmwasm-std", "nym-multisig-contract-common", - "schemars", - "serde", ] [[package]] name = "nym-coconut-dkg-common" version = "0.1.0" dependencies = [ + "cosmwasm-schema", "cosmwasm-std", "cw-utils", "nym-contracts-common", "nym-multisig-contract-common", - "schemars", - "serde", ] [[package]] name = "nym-coconut-interface" version = "0.1.0" dependencies = [ - "bs58", + "bs58 0.4.0", "getset", "nym-coconut", "serde", @@ -3311,12 +3534,12 @@ dependencies = [ name = "nym-config" version = "0.1.0" dependencies = [ - "cfg-if", + "dirs 5.0.1", "handlebars", "log", "nym-network-defaults", "serde", - "toml", + "toml 0.7.6", "url", ] @@ -3326,7 +3549,7 @@ version = "1.1.12" dependencies = [ "anyhow", "bip39", - "dirs", + "dirs 4.0.0", "eyre", "fern", "fix-path-env", @@ -3353,7 +3576,6 @@ dependencies = [ "tauri", "tauri-build", "tempfile", - "tendermint-rpc", "thiserror", "time", "tokio", @@ -3364,9 +3586,10 @@ dependencies = [ [[package]] name = "nym-contracts-common" -version = "0.3.0" +version = "0.5.0" dependencies = [ - "bs58", + "bs58 0.4.0", + "cosmwasm-schema", "cosmwasm-std", "schemars", "serde", @@ -3399,16 +3622,16 @@ dependencies = [ [[package]] name = "nym-crypto" -version = "0.2.0" +version = "0.4.0" dependencies = [ "aes 0.8.2", "blake3", - "bs58", + "bs58 0.4.0", "cipher 0.4.4", "ctr 0.9.2", - "digest 0.10.6", + "digest 0.10.7", "ed25519-dalek", - "generic-array 0.14.6", + "generic-array 0.14.7", "hkdf 0.12.3", "hmac 0.12.1", "nym-pemstore", @@ -3417,6 +3640,7 @@ dependencies = [ "subtle-encoding", "thiserror", "x25519-dalek", + "zeroize", ] [[package]] @@ -3425,7 +3649,7 @@ version = "0.1.0" dependencies = [ "bitvec", "bls12_381 0.6.0", - "bs58", + "bs58 0.4.0", "ff 0.11.1", "group 0.11.0", "lazy_static", @@ -3440,20 +3664,29 @@ dependencies = [ "zeroize", ] +[[package]] +name = "nym-explorer-api-requests" +version = "0.1.0" +dependencies = [ + "nym-contracts-common", + "nym-mixnet-contract-common", + "nym-validator-client", + "schemars", + "serde", +] + [[package]] name = "nym-gateway-client" version = "0.1.0" dependencies = [ - "async-trait", "futures", - "getrandom 0.2.8", + "getrandom 0.2.10", "log", + "nym-bandwidth-controller", "nym-coconut-interface", "nym-credential-storage", - "nym-credentials", "nym-crypto", "nym-gateway-requests", - "nym-mobile-storage", "nym-network-defaults", "nym-pemstore", "nym-sphinx", @@ -3469,16 +3702,17 @@ dependencies = [ "url", "wasm-bindgen", "wasm-bindgen-futures", - "wasm-timer", "wasm-utils", + "wasmtimer", ] [[package]] name = "nym-gateway-requests" version = "0.1.0" dependencies = [ - "bs58", + "bs58 0.4.0", "futures", + "generic-array 0.14.7", "log", "nym-coconut-interface", "nym-credentials", @@ -3490,12 +3724,15 @@ dependencies = [ "serde_json", "thiserror", "tungstenite", + "zeroize", ] [[package]] name = "nym-group-contract-common" version = "0.1.0" dependencies = [ + "cosmwasm-schema", + "cw-controllers", "cw4", "schemars", "serde", @@ -3503,9 +3740,10 @@ dependencies = [ [[package]] name = "nym-mixnet-contract-common" -version = "0.3.0" +version = "0.6.0" dependencies = [ - "bs58", + "bs58 0.4.0", + "cosmwasm-schema", "cosmwasm-std", "humantime-serde", "log", @@ -3519,23 +3757,29 @@ dependencies = [ ] [[package]] -name = "nym-mobile-storage" +name = "nym-multisig-contract-common" version = "0.1.0" dependencies = [ - "async-trait", + "cosmwasm-schema", + "cosmwasm-std", + "cw-storage-plus", + "cw-utils", + "cw3", + "cw4", + "schemars", + "serde", "thiserror", ] [[package]] -name = "nym-multisig-contract-common" +name = "nym-name-service-common" version = "0.1.0" dependencies = [ + "cosmwasm-schema", "cosmwasm-std", + "cw-controllers", "cw-utils", - "cw3", - "cw3-fixed-multisig", - "cw4", - "schemars", + "nym-contracts-common", "serde", "thiserror", ] @@ -3557,12 +3801,10 @@ dependencies = [ name = "nym-nonexhaustive-delayqueue" version = "0.1.0" dependencies = [ - "futures-core", - "slab", "tokio", "tokio-stream", "tokio-util", - "wasm-timer", + "wasmtimer", ] [[package]] @@ -3581,19 +3823,34 @@ dependencies = [ "chacha20", "chacha20poly1305", "curve25519-dalek", - "getrandom 0.2.8", + "getrandom 0.2.10", + "log", + "rand 0.7.3", "rayon", + "sphinx-packet", "thiserror", "zeroize", ] [[package]] name = "nym-pemstore" -version = "0.2.0" +version = "0.3.0" dependencies = [ "pem", ] +[[package]] +name = "nym-service-provider-directory-common" +version = "0.1.0" +dependencies = [ + "cosmwasm-schema", + "cosmwasm-std", + "cw-controllers", + "cw-utils", + "nym-contracts-common", + "thiserror", +] + [[package]] name = "nym-service-providers-common" version = "0.1.0" @@ -3611,12 +3868,15 @@ dependencies = [ name = "nym-socks5-client-core" version = "0.1.0" dependencies = [ - "dirs", + "dirs 4.0.0", "futures", "log", "nym-bandwidth-controller", + "nym-client-core", "nym-config", + "nym-contracts-common", "nym-credential-storage", + "nym-mixnet-contract-common", "nym-network-defaults", "nym-service-providers-common", "nym-socks5-proxy-helpers", @@ -3626,10 +3886,13 @@ dependencies = [ "nym-validator-client", "pin-project", "rand 0.7.3", + "reqwest", + "schemars", "serde", "tap", "thiserror", "tokio", + "url", ] [[package]] @@ -3650,10 +3913,13 @@ dependencies = [ name = "nym-socks5-requests" version = "0.1.0" dependencies = [ + "bincode", + "log", "nym-service-providers-common", "nym-sphinx-addressing", "serde", "serde_json", + "tap", "thiserror", ] @@ -3663,7 +3929,6 @@ version = "0.1.0" dependencies = [ "log", "nym-crypto", - "nym-outfox", "nym-sphinx-acknowledgements", "nym-sphinx-addressing", "nym-sphinx-anonymous-replies", @@ -3672,6 +3937,7 @@ dependencies = [ "nym-sphinx-forwarding", "nym-sphinx-framing", "nym-sphinx-params", + "nym-sphinx-routing", "nym-sphinx-types", "nym-topology", "rand 0.7.3", @@ -3688,10 +3954,12 @@ dependencies = [ "nym-pemstore", "nym-sphinx-addressing", "nym-sphinx-params", + "nym-sphinx-routing", "nym-sphinx-types", "nym-topology", "rand 0.7.3", "thiserror", + "zeroize", ] [[package]] @@ -3708,10 +3976,11 @@ dependencies = [ name = "nym-sphinx-anonymous-replies" version = "0.1.0" dependencies = [ - "bs58", + "bs58 0.4.0", "nym-crypto", "nym-sphinx-addressing", "nym-sphinx-params", + "nym-sphinx-routing", "nym-sphinx-types", "nym-topology", "rand 0.7.3", @@ -3742,6 +4011,7 @@ dependencies = [ "nym-sphinx-chunking", "nym-sphinx-forwarding", "nym-sphinx-params", + "nym-sphinx-routing", "nym-sphinx-types", "nym-topology", "rand 0.7.3", @@ -3752,9 +4022,11 @@ dependencies = [ name = "nym-sphinx-forwarding" version = "0.1.0" dependencies = [ + "nym-outfox", "nym-sphinx-addressing", "nym-sphinx-params", "nym-sphinx-types", + "thiserror", ] [[package]] @@ -3774,6 +4046,16 @@ version = "0.1.0" dependencies = [ "nym-crypto", "nym-sphinx-types", + "serde", + "thiserror", +] + +[[package]] +name = "nym-sphinx-routing" +version = "0.1.0" +dependencies = [ + "nym-sphinx-addressing", + "nym-sphinx-types", "thiserror", ] @@ -3781,7 +4063,9 @@ dependencies = [ name = "nym-sphinx-types" version = "0.2.0" dependencies = [ + "nym-outfox", "sphinx-packet", + "thiserror", ] [[package]] @@ -3801,12 +4085,13 @@ name = "nym-topology" version = "0.1.0" dependencies = [ "async-trait", - "bs58", + "bs58 0.4.0", "log", "nym-bin-common", "nym-crypto", "nym-mixnet-contract-common", "nym-sphinx-addressing", + "nym-sphinx-routing", "nym-sphinx-types", "rand 0.7.3", "thiserror", @@ -3838,14 +4123,17 @@ dependencies = [ "nym-group-contract-common", "nym-mixnet-contract-common", "nym-multisig-contract-common", + "nym-name-service-common", "nym-network-defaults", - "nym-vesting-contract", + "nym-service-provider-directory-common", "nym-vesting-contract-common", + "openssl", "prost", "reqwest", "serde", "serde_json", "sha2 0.9.9", + "tendermint-rpc", "thiserror", "tokio", "url", @@ -3853,32 +4141,15 @@ dependencies = [ ] [[package]] -name = "nym-vesting-contract" -version = "1.2.0" +name = "nym-vesting-contract-common" +version = "0.7.0" dependencies = [ - "cosmwasm-derive", + "cosmwasm-schema", "cosmwasm-std", - "cw-storage-plus", - "cw2", "nym-contracts-common", "nym-mixnet-contract-common", - "nym-vesting-contract-common", - "schemars", - "semver 1.0.17", "serde", "thiserror", - "vergen", -] - -[[package]] -name = "nym-vesting-contract-common" -version = "0.3.0" -dependencies = [ - "cosmwasm-std", - "nym-contracts-common", - "nym-mixnet-contract-common", - "schemars", - "serde", ] [[package]] @@ -3950,9 +4221,9 @@ dependencies = [ [[package]] name = "openssl" -version = "0.10.47" +version = "0.10.56" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8b277f87dacc05a6b709965d1cbafac4649d6ce9f3ce9ceb88508b5666dfec9" +checksum = "729b745ad4a5575dd06a3e1af1414bd330ee561c01b3899eb584baeaa8def17e" dependencies = [ "bitflags 1.3.2", "cfg-if", @@ -3991,11 +4262,10 @@ dependencies = [ [[package]] name = "openssl-sys" -version = "0.9.82" +version = "0.9.91" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a95792af3c4e0153c3914df2261bedd30a98476f94dc892b67dfe1d89d433a04" +checksum = "866b5f16f90776b9bb8dc1e1802ac6f0513de3a7a7465867bfbc563dc737faac" dependencies = [ - "autocfg", "cc", "libc", "openssl-src", @@ -4003,6 +4273,12 @@ dependencies = [ "vcpkg", ] +[[package]] +name = "option-ext" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" + [[package]] name = "os_str_bytes" version = "6.5.0" @@ -4121,11 +4397,12 @@ checksum = "8835116a5c179084a830efb3adc117ab007512b535bc1a21c991d3b32a6b44dd" [[package]] name = "pbkdf2" -version = "0.9.0" +version = "0.12.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f05894bce6a1ba4be299d0c5f29563e08af2bc18bb7d48313113bed71e904739" +checksum = "f8ed6a7761f76e3b9f92dfb0a60a6a6477c61024b775147ff0973a02653abaf2" dependencies = [ - "crypto-mac 0.11.1", + "digest 0.10.7", + "hmac 0.12.1", ] [[package]] @@ -4348,13 +4625,22 @@ checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] name = "pkcs8" -version = "0.8.0" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7cabda3fb821068a9a4fab19a683eac3af12edf0f34b94a8be53c4972b8149d0" +checksum = "9eca2c590a5f85da82668fa685c09ce2888b9430e83299debf1f34b65fd4a4ba" dependencies = [ - "der", - "spki", - "zeroize", + "der 0.6.1", + "spki 0.6.0", +] + +[[package]] +name = "pkcs8" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" +dependencies = [ + "der 0.7.8", + "spki 0.7.2", ] [[package]] @@ -4369,8 +4655,8 @@ version = "1.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9bd9647b268a3d3e14ff09c23201133a62589c658db02bb7388c7246aafe0590" dependencies = [ - "base64 0.21.0", - "indexmap", + "base64 0.21.2", + "indexmap 1.9.2", "line-wrap", "quick-xml 0.28.1", "serde", @@ -4473,9 +4759,9 @@ dependencies = [ [[package]] name = "prost" -version = "0.10.4" +version = "0.11.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "71adf41db68aa0daaefc69bb30bcd68ded9b9abaad5d1fbb6304c4fb390e083e" +checksum = "0b82eaa1d779e9a4bc1c3217db8ffbeabaae1dca241bf70183242128d48681cd" dependencies = [ "bytes", "prost-derive", @@ -4483,9 +4769,9 @@ dependencies = [ [[package]] name = "prost-derive" -version = "0.10.1" +version = "0.11.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b670f45da57fb8542ebdbb6105a925fe571b67f9e7ed9f47a06a84e72b4e7cc" +checksum = "e5d2d8d10f3c6ded6da8b05b5fb3b8a5082514344d56c9f871412d29b4e075b4" dependencies = [ "anyhow", "itertools", @@ -4496,11 +4782,10 @@ dependencies = [ [[package]] name = "prost-types" -version = "0.10.1" +version = "0.11.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d0a014229361011dc8e69c8a1ec6c2e8d0f2af7c91e3ea3f5b2170298461e68" +checksum = "213622a1460818959ac1181aaeb2dc9c7f63df720db7d788b3e24eacd1983e13" dependencies = [ - "bytes", "prost", ] @@ -4609,7 +4894,7 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ - "getrandom 0.2.8", + "getrandom 0.2.10", ] [[package]] @@ -4683,7 +4968,7 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b" dependencies = [ - "getrandom 0.2.8", + "getrandom 0.2.10", "redox_syscall", "thiserror", ] @@ -4716,11 +5001,11 @@ checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" [[package]] name = "reqwest" -version = "0.11.15" +version = "0.11.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ba30cc2c0cd02af1222ed216ba659cdb2f879dfe3181852fe7c50b1d0005949" +checksum = "cde824a14b7c14f85caff81225f411faacc04a2013f41670f41443742b1c1c55" dependencies = [ - "base64 0.21.0", + "base64 0.21.2", "bytes", "encoding_rs", "futures-core", @@ -4754,15 +5039,25 @@ dependencies = [ [[package]] name = "rfc6979" -version = "0.1.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96ef608575f6392792f9ecf7890c00086591d29a83910939d430753f7c050525" +checksum = "7743f17af12fa0b03b803ba12cd6a8d9483a587e89c69445e3909655c0b9fabb" dependencies = [ - "crypto-bigint", - "hmac 0.11.0", + "crypto-bigint 0.4.9", + "hmac 0.12.1", "zeroize", ] +[[package]] +name = "rfc6979" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2" +dependencies = [ + "hmac 0.12.1", + "subtle 2.4.1", +] + [[package]] name = "rfd" version = "0.11.2" @@ -4802,14 +5097,12 @@ dependencies = [ ] [[package]] -name = "ripemd160" -version = "0.9.1" +name = "ripemd" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2eca4ecc81b7f313189bf73ce724400a07da2a6dac19588b03c8bd76a2dcc251" +checksum = "bd124222d17ad93a644ed9d011a40f4fb64aa54275c08cc216524a9ea82fb09f" dependencies = [ - "block-buffer 0.9.0", - "digest 0.9.0", - "opaque-debug 0.3.0", + "digest 0.10.7", ] [[package]] @@ -4913,7 +5206,7 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d194b56d58803a43635bdc398cd17e383d6f71f9182b9a192c127ca42494a59b" dependencies = [ - "base64 0.21.0", + "base64 0.21.2", ] [[package]] @@ -4959,7 +5252,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "02c613288622e5f0c3fdc5dbd4db1c5fbe752746b1d1a56a0630b78fd00de44f" dependencies = [ "dyn-clone", - "indexmap", + "indexmap 1.9.2", "schemars_derive", "serde", "serde_json", @@ -5011,13 +5304,28 @@ dependencies = [ [[package]] name = "sec1" -version = "0.2.1" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08da66b8b0965a5555b6bd6639e68ccba85e1e2506f5fbb089e93f8a04e1a2d1" +checksum = "3be24c1842290c45df0a7bf069e0c268a747ad05a192f2fd7dcfdbc1cba40928" dependencies = [ - "der", - "generic-array 0.14.6", - "pkcs8", + "base16ct 0.1.1", + "der 0.6.1", + "generic-array 0.14.7", + "pkcs8 0.9.0", + "subtle 2.4.1", + "zeroize", +] + +[[package]] +name = "sec1" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc" +dependencies = [ + "base16ct 0.2.0", + "der 0.7.8", + "generic-array 0.14.7", + "pkcs8 0.10.2", "subtle 2.4.1", "zeroize", ] @@ -5103,9 +5411,9 @@ dependencies = [ [[package]] name = "serde-json-wasm" -version = "0.4.1" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "479b4dbc401ca13ee8ce902851b834893251404c4f3c65370a49e047a6be09a5" +checksum = "a15bee9b04dd165c3f4e142628982ddde884c2022a89e8ddf99c4829bf2c3a58" dependencies = [ "serde", ] @@ -5163,6 +5471,15 @@ dependencies = [ "syn 2.0.5", ] +[[package]] +name = "serde_spanned" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96426c9936fd7a0124915f9185ea1d20aa9445cc9821142f0a73bc9207a2e186" +dependencies = [ + "serde", +] + [[package]] name = "serde_urlencoded" version = "0.7.1" @@ -5250,7 +5567,7 @@ checksum = "f04293dc80c3993519f2d7f6f511707ee7094fe0c6d3406feb330cdb3540eba3" dependencies = [ "cfg-if", "cpufeatures", - "digest 0.10.6", + "digest 0.10.7", ] [[package]] @@ -5274,19 +5591,7 @@ checksum = "82e6b795fe2e3b1e845bafcb27aa35405c4d47cdfc92af5fc8d3002f76cebdc0" dependencies = [ "cfg-if", "cpufeatures", - "digest 0.10.6", -] - -[[package]] -name = "sha3" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f81199417d4e5de3f04b1e871023acea7389672c4135918f05aa9cbf2f2fa809" -dependencies = [ - "block-buffer 0.9.0", - "digest 0.9.0", - "keccak", - "opaque-debug 0.3.0", + "digest 0.10.7", ] [[package]] @@ -5309,11 +5614,21 @@ dependencies = [ [[package]] name = "signature" -version = "1.4.0" +version = "1.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02658e48d89f2bec991f9a78e69cfa4c316f8d6a6c4ec12fae1aeb263d486788" +checksum = "74233d3b3b2f6d4b006dc19dee745e73e2a6bfb6f93607cd3b02bd5b00797d7c" dependencies = [ - "digest 0.9.0", + "digest 0.10.7", + "rand_core 0.6.4", +] + +[[package]] +name = "signature" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e1788eed21689f9cf370582dfc467ef36ed9c707f073528ddafa8d83e3b8500" +dependencies = [ + "digest 0.10.7", "rand_core 0.6.4", ] @@ -5385,7 +5700,7 @@ dependencies = [ "aes 0.7.5", "arrayref", "blake2", - "bs58", + "bs58 0.4.0", "byteorder", "chacha", "curve25519-dalek", @@ -5417,12 +5732,22 @@ dependencies = [ [[package]] name = "spki" -version = "0.5.4" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44d01ac02a6ccf3e07db148d2be087da624fea0221a16152ed01f0496a6b0a27" +checksum = "67cf02bbac7a337dc36e4f5a693db6c21e7863f45070f7064577eb4367a3212b" dependencies = [ "base64ct", - "der", + "der 0.6.1", +] + +[[package]] +name = "spki" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d1e996ef02c474957d681f1b05213dfb0abab947b446a62d37770b23500184a" +dependencies = [ + "base64ct", + "der 0.7.8", ] [[package]] @@ -5490,7 +5815,7 @@ dependencies = [ "futures-util", "hashlink 0.7.0", "hex", - "indexmap", + "indexmap 1.9.2", "itoa 1.0.6", "libc", "libsqlite3-sys", @@ -5536,7 +5861,7 @@ dependencies = [ "futures-util", "hashlink 0.8.1", "hex", - "indexmap", + "indexmap 1.9.2", "itoa 1.0.6", "libc", "libsqlite3-sys", @@ -5633,12 +5958,6 @@ dependencies = [ "loom", ] -[[package]] -name = "static_assertions" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" - [[package]] name = "string_cache" version = "0.8.7" @@ -5732,13 +6051,19 @@ dependencies = [ "zeroize", ] +[[package]] +name = "subtle-ng" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "734676eb262c623cec13c3155096e08d1f8f29adce39ba17948b18dad1e54142" + [[package]] name = "swift-rs" version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2eab55f44d02ced20ffa22fd5b4bd083ab59c940c7637d37fec4426b1a01d769" dependencies = [ - "base64 0.21.0", + "base64 0.21.2", "serde", "serde_json", ] @@ -5786,7 +6111,7 @@ dependencies = [ "cfg-expr", "heck 0.4.1", "pkg-config", - "toml", + "toml 0.5.11", "version-compare", ] @@ -6052,7 +6377,7 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b7a78dc04f75fb5ab815e66ac561c81e92a968a40f29e7c21afd152d694fad8" dependencies = [ - "toml", + "toml 0.5.11", "version_check", ] @@ -6082,28 +6407,28 @@ dependencies = [ [[package]] name = "tendermint" -version = "0.23.7" +version = "0.32.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ca881fa4dedd2b46334f13be7fbc8cc1549ba4be5a833fe4e73d1a1baaf7949" +checksum = "3f0a7d05cf78524782337f8edd55cbc578d159a16ad4affe2135c92f7dbac7f0" dependencies = [ - "async-trait", "bytes", - "ed25519", - "ed25519-dalek", + "digest 0.10.7", + "ed25519 2.2.2", + "ed25519-consensus", "flex-error", "futures", - "k256", + "k256 0.13.1", "num-traits", "once_cell", "prost", "prost-types", - "ripemd160", + "ripemd", "serde", "serde_bytes", "serde_json", "serde_repr", - "sha2 0.9.9", - "signature", + "sha2 0.10.6", + "signature 2.1.0", "subtle 2.4.1", "subtle-encoding", "tendermint-proto", @@ -6113,23 +6438,23 @@ dependencies = [ [[package]] name = "tendermint-config" -version = "0.23.7" +version = "0.32.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6c56ee93f4e9b7e7daba86d171f44572e91b741084384d0ae00df7991873dfd" +checksum = "71a72dbbea6dde12045d261f2c70c0de039125675e8a026c8d5ad34522756372" dependencies = [ "flex-error", "serde", "serde_json", "tendermint", - "toml", + "toml 0.5.11", "url", ] [[package]] name = "tendermint-proto" -version = "0.23.7" +version = "0.32.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b71f925d74903f4abbdc4af0110635a307b3cb05b175fdff4a7247c14a4d0874" +checksum = "c0cec054567d16d85e8c3f6a3139963d1a66d9d3051ed545d31562550e9bcc3d" dependencies = [ "bytes", "flex-error", @@ -6145,24 +6470,26 @@ dependencies = [ [[package]] name = "tendermint-rpc" -version = "0.23.7" +version = "0.32.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a13e63f57ee05a1e927887191c76d1b139de9fa40c180b9f8727ee44377242a6" +checksum = "d119d83a130537fc4a98c3c9eb6899ebe857fea4860400a61675bfb5f0b35129" dependencies = [ "async-trait", "bytes", "flex-error", "futures", - "getrandom 0.2.8", + "getrandom 0.2.10", "http", "hyper", "hyper-proxy", "hyper-rustls", "peg", "pin-project", + "semver 1.0.17", "serde", "serde_bytes", "serde_json", + "subtle 2.4.1", "subtle-encoding", "tendermint", "tendermint-config", @@ -6401,18 +6728,35 @@ dependencies = [ ] [[package]] -name = "toml_datetime" -version = "0.6.1" +name = "toml" +version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ab8ed2edee10b50132aed5f331333428b011c99402b5a534154ed15746f9622" +checksum = "c17e963a819c331dcacd7ab957d80bc2b9a9c1e71c804826d2f283dd65306542" +dependencies = [ + "serde", + "serde_spanned", + "toml_datetime", + "toml_edit", +] + +[[package]] +name = "toml_datetime" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cda73e2f1397b1262d6dfdcef8aafae14d1de7748d66822d3bfeeb6d03e5e4b" +dependencies = [ + "serde", +] [[package]] name = "toml_edit" -version = "0.19.7" +version = "0.19.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc18466501acd8ac6a3f615dd29a3438f8ca6bb3b19537138b3106e575621274" +checksum = "f8123f27e969974a3dfba720fdb560be359f57b44302d280ba72e76a74480e8a" dependencies = [ - "indexmap", + "indexmap 2.0.0", + "serde", + "serde_spanned", "toml_datetime", "winnow", ] @@ -6564,18 +6908,6 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9e79c4d996edb816c91e4308506774452e55e95c3c9de07b6729e17e15a5ef81" -[[package]] -name = "uint" -version = "0.9.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76f64bba2c53b04fcab63c01a7d7427eadc821e3bc48c34dc9ba29c501164b52" -dependencies = [ - "byteorder", - "crunchy", - "hex", - "static_assertions", -] - [[package]] name = "unicode-bidi" version = "0.3.13" @@ -6667,7 +6999,7 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1674845326ee10d37ca60470760d4288a6f80f304007d92e5c53bab78c9cfd79" dependencies = [ - "getrandom 0.2.8", + "getrandom 0.2.10", ] [[package]] @@ -6831,29 +7163,30 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1c38c045535d93ec4f0b4defec448e4291638ee608530863b1e2ba115d4fff7f" [[package]] -name = "wasm-timer" -version = "0.2.5" -source = "git+https://github.com/mmsinclair/wasm-timer?rev=b9d1a54ad514c2f230a026afe0dde341e98cd7b6#b9d1a54ad514c2f230a026afe0dde341e98cd7b6" +name = "wasm-utils" +version = "0.1.0" dependencies = [ "futures", + "getrandom 0.2.10", "js-sys", - "parking_lot 0.11.2", - "pin-utils", + "tungstenite", "wasm-bindgen", "wasm-bindgen-futures", "web-sys", ] [[package]] -name = "wasm-utils" -version = "0.1.0" +name = "wasmtimer" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f656cd8858a5164932d8a90f936700860976ec21eb00e0fe2aa8cab13f6b4cf" dependencies = [ "futures", "js-sys", - "tungstenite", + "parking_lot 0.12.1", + "pin-utils", + "slab", "wasm-bindgen", - "wasm-bindgen-futures", - "web-sys", ] [[package]] @@ -7039,7 +7372,7 @@ checksum = "9e745dab35a0c4c77aa3ce42d595e13d2003d6902d6b08c9ef5fc326d08da12b" dependencies = [ "windows-implement 0.44.0", "windows-interface", - "windows-targets", + "windows-targets 0.42.2", ] [[package]] @@ -7096,12 +7429,12 @@ version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" dependencies = [ - "windows_aarch64_gnullvm", + "windows_aarch64_gnullvm 0.42.2", "windows_aarch64_msvc 0.42.2", "windows_i686_gnu 0.42.2", "windows_i686_msvc 0.42.2", "windows_x86_64_gnu 0.42.2", - "windows_x86_64_gnullvm", + "windows_x86_64_gnullvm 0.42.2", "windows_x86_64_msvc 0.42.2", ] @@ -7111,7 +7444,16 @@ version = "0.45.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" dependencies = [ - "windows-targets", + "windows-targets 0.42.2", +] + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.1", ] [[package]] @@ -7120,15 +7462,30 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" dependencies = [ - "windows_aarch64_gnullvm", + "windows_aarch64_gnullvm 0.42.2", "windows_aarch64_msvc 0.42.2", "windows_i686_gnu 0.42.2", "windows_i686_msvc 0.42.2", "windows_x86_64_gnu 0.42.2", - "windows_x86_64_gnullvm", + "windows_x86_64_gnullvm 0.42.2", "windows_x86_64_msvc 0.42.2", ] +[[package]] +name = "windows-targets" +version = "0.48.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05d4b17490f70499f20b9e791dcf6a299785ce8af4d709018206dc5b4953e95f" +dependencies = [ + "windows_aarch64_gnullvm 0.48.0", + "windows_aarch64_msvc 0.48.0", + "windows_i686_gnu 0.48.0", + "windows_i686_msvc 0.48.0", + "windows_x86_64_gnu 0.48.0", + "windows_x86_64_gnullvm 0.48.0", + "windows_x86_64_msvc 0.48.0", +] + [[package]] name = "windows-tokens" version = "0.39.0" @@ -7141,6 +7498,12 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc" + [[package]] name = "windows_aarch64_msvc" version = "0.39.0" @@ -7153,6 +7516,12 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3" + [[package]] name = "windows_i686_gnu" version = "0.39.0" @@ -7165,6 +7534,12 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" +[[package]] +name = "windows_i686_gnu" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241" + [[package]] name = "windows_i686_msvc" version = "0.39.0" @@ -7177,6 +7552,12 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" +[[package]] +name = "windows_i686_msvc" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00" + [[package]] name = "windows_x86_64_gnu" version = "0.39.0" @@ -7189,12 +7570,24 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1" + [[package]] name = "windows_x86_64_gnullvm" version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953" + [[package]] name = "windows_x86_64_msvc" version = "0.39.0" @@ -7208,10 +7601,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" [[package]] -name = "winnow" -version = "0.3.6" +name = "windows_x86_64_msvc" +version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23d020b441f92996c80d94ae9166e8501e59c7bb56121189dc9eab3bd8216966" +checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" + +[[package]] +name = "winnow" +version = "0.5.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5504cc7644f4b593cbc05c4a55bf9bd4e94b867c3c0bd440934174d50482427d" dependencies = [ "memchr", ] @@ -7324,9 +7723,9 @@ dependencies = [ [[package]] name = "zeroize" -version = "1.5.7" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c394b5bd0c6f669e7275d9c20aa90ae064cb22e75a1cad54e1b34088034b149f" +checksum = "2a0956f1ba7c7909bfb66c2e9e4124ab6f6482560f6628b5aaeba39207c9aad9" dependencies = [ "zeroize_derive", ] diff --git a/nym-connect/mobile/src-tauri/Cargo.toml b/nym-connect/mobile/src-tauri/Cargo.toml index 28ea437cdc..2056047897 100644 --- a/nym-connect/mobile/src-tauri/Cargo.toml +++ b/nym-connect/mobile/src-tauri/Cargo.toml @@ -35,7 +35,7 @@ itertools = "0.10.5" log = { version = "0.4", features = ["serde"] } pretty_env_logger = "0.4.0" rand = "0.8" -reqwest = { version = "0.11", features = ["json", "socks"] } +reqwest = { version = "0.11.18", features = ["json", "socks"] } rust-embed = { version = "6.4.2", features = ["include-exclude"] } serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" @@ -50,7 +50,7 @@ tokio = { version = "1.24.1", features = ["sync", "time"] } url = "2.2" yaml-rust = "0.4" -nym-client-core = { path = "../../../common/client-core", features = ["mobile"], default-features = false } +nym-client-core = { path = "../../../common/client-core", features = [], default-features = false } nym-api-requests = { path = "../../../nym-api/nym-api-requests" } nym-contracts-common = { path = "../../../common/cosmwasm-smart-contracts/contracts-common"} nym-config-common = { path = "../../../common/config", package = "nym-config" } diff --git a/nym-wallet/Cargo.lock b/nym-wallet/Cargo.lock index da53aee71f..c549a1a2e7 100644 --- a/nym-wallet/Cargo.lock +++ b/nym-wallet/Cargo.lock @@ -8,6 +8,15 @@ version = "0.11.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fe438c63458706e03479442743baae6c88256498e6431708f6dfc520a26515d3" +[[package]] +name = "addr2line" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4fa78e18c64fce05e902adecd7a5eed15a5e0a3439f7b0e169f0252214865e3" +dependencies = [ + "gimli", +] + [[package]] name = "adler" version = "1.0.2" @@ -39,9 +48,9 @@ dependencies = [ [[package]] name = "aes" -version = "0.8.2" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "433cfd6710c9986c576a25ca913c39d66a6474107b406f34f91d4a8923395241" +checksum = "ac1f845298e95f983ff1944b728ae08b8cebab80d684f0a832ed0fc74dfa27e2" dependencies = [ "cfg-if", "cipher 0.4.4", @@ -50,12 +59,12 @@ dependencies = [ [[package]] name = "aes-gcm" -version = "0.10.1" +version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82e1366e0c69c9f927b1fa5ce2c7bf9eafc8f9268c0b9800729e8b267612447c" +checksum = "209b47e8954a928e1d72e86eca7000ebb6655fe1436d33eefc2201cad027e237" dependencies = [ "aead", - "aes 0.8.2", + "aes 0.8.3", "cipher 0.4.4", "ctr 0.9.2", "ghash", @@ -75,9 +84,9 @@ dependencies = [ [[package]] name = "aho-corasick" -version = "0.7.20" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc936419f96fa211c1b9166887b38e5e40b19958e5b895be7c1f93adec7071ac" +checksum = "86b8f9420f797f2d9e935edf629310eb938a0d839f984e25327f3c7eed22300c" dependencies = [ "memchr", ] @@ -98,37 +107,102 @@ dependencies = [ ] [[package]] -name = "anyhow" -version = "1.0.69" +name = "android-tzdata" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "224afbd727c3d6e4b90103ece64b8d1b67fbb1973b1046c2281eed3f3803f800" +checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + +[[package]] +name = "anstream" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ca84f3628370c59db74ee214b3263d58f9aadd9b4fe7e711fd87dc452b7f163" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is-terminal", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a30da5c5f2d5e72842e00bcb57657162cdabef0931f40e2deb9b4140440cecd" + +[[package]] +name = "anstyle-parse" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "938874ff5980b03a87c5524b3ae5b59cf99b1d6bc836848df7bc5ada9643c333" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ca11d4be1bab0c8bc8734a9aa7bf4ee8316d462a08c6ac5052f888fef5b494b" +dependencies = [ + "windows-sys 0.48.0", +] + +[[package]] +name = "anstyle-wincon" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c677ab05e09154296dd37acecd46420c17b9713e8366facafa8fc0885167cf4c" +dependencies = [ + "anstyle", + "windows-sys 0.48.0", +] + +[[package]] +name = "anyhow" +version = "1.0.72" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b13c32d80ecc7ab747b80c3784bce54ee8a7a0cc4fbda9bf4cda2cf6fe90854" [[package]] name = "argon2" -version = "0.5.0" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95c2fcf79ad1932ac6269a738109997a83c227c09b75842ae564dc8ede6a861c" +checksum = "b2e554a8638bdc1e4eae9984845306cc95f8a9208ba8d49c3859fd958b46774d" dependencies = [ "base64ct", "blake2 0.10.6", + "cpufeatures", "password-hash", ] [[package]] name = "arrayref" -version = "0.3.6" +version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4c527152e37cf757a3f78aae5a06fbeefdb07ccc535c980a3208ee3060dd544" +checksum = "6b4930d2cb77ce62f89ee5d5289b4ac049559b1c45539271f5ed4fdc7db34545" [[package]] name = "async-trait" -version = "0.1.64" +version = "0.1.73" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cd7fce9ba8c3c042128ce72d8b2ddbf3a05747efb67ea0313c635e10bda47a2" +checksum = "bc00ceb34980c03614e35a3a4e218276a0a824e911d07651cd0d858a51e8c0f0" dependencies = [ "proc-macro2", "quote", - "syn 1.0.107", + "syn 2.0.28", ] [[package]] @@ -138,7 +212,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2c3d816ce6f0e2909a96830d6911c2aff044370b1ef92d7f267b43bae5addedd" dependencies = [ "atk-sys", - "bitflags", + "bitflags 1.3.2", "glib", "libc", ] @@ -152,7 +226,7 @@ dependencies = [ "glib-sys", "gobject-sys", "libc", - "system-deps 6.0.3", + "system-deps 6.1.1", ] [[package]] @@ -188,6 +262,21 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" +[[package]] +name = "backtrace" +version = "0.3.68" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4319208da049c43661739c5fade2ba182f09d1dc2299b32298d3a31692b17e12" +dependencies = [ + "addr2line", + "cc", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", +] + [[package]] name = "base16ct" version = "0.1.1" @@ -208,15 +297,15 @@ checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" [[package]] name = "base64" -version = "0.21.0" +version = "0.21.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4a4ddaa51a5bc52a6948f74c06d20aaaddb71924eab79b8c97a8c556e942d6a" +checksum = "604178f6c5c21f02dc555784810edfb88d34ac2c73b2eae109655649ee73ce3d" [[package]] name = "base64ct" -version = "1.0.1" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a32fd6af2b5827bce66c29053ba0e7c42b9dcab01835835058558c10851a46b" +checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" [[package]] name = "bip32" @@ -231,7 +320,7 @@ dependencies = [ "pbkdf2", "rand_core 0.6.4", "ripemd", - "sha2 0.10.6", + "sha2 0.10.7", "subtle 2.4.1", "zeroize", ] @@ -262,6 +351,12 @@ version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" +[[package]] +name = "bitflags" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4682ae6287fcf752ecaabbfcc7b6f9b72aa33933dc23a554d853aea8eea8635" + [[package]] name = "bitvec" version = "1.0.1" @@ -312,9 +407,9 @@ dependencies = [ [[package]] name = "block-buffer" -version = "0.10.3" +version = "0.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69cce20737498f97b993470a6e536b8523f0af7892a4f928cceb1ac5e52ebe7e" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" dependencies = [ "generic-array 0.14.7", ] @@ -372,14 +467,14 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f5353f36341f7451062466f0b755b96ac3a9547e4d7f6b70d603fc721a7d7896" dependencies = [ - "sha2 0.10.6", + "sha2 0.10.7", ] [[package]] name = "bstr" -version = "1.2.0" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7f0778972c64420fdedc63f09919c8a88bda7b25135357fd25a5d9f3257e832" +checksum = "6798148dccfbff0fae41c7574d2fa8f1ef3492fba0face179de5d8d447d67b05" dependencies = [ "memchr", "serde", @@ -387,9 +482,9 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.12.0" +version = "3.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d261e256854913907f67ed06efbc3338dfe6179796deefc1ff763fc1aee5535" +checksum = "a3e2c3daef883ecc1b5d58c15adae93470a91d425f3532ba1695849656af3fc1" [[package]] name = "byte-tools" @@ -399,9 +494,9 @@ checksum = "e3b5ca7a04898ad4bcd41c90c5285445ff5b791899bb1b0abdd2a2aa791211d7" [[package]] name = "bytemuck" -version = "1.13.0" +version = "1.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c041d3eab048880cb0b86b256447da3f18859a163c3b8d8893f4e6368abe6393" +checksum = "17febce684fd15d89027105661fec94afb475cb995fbc59d2865198446ba2eea" [[package]] name = "byteorder" @@ -424,7 +519,7 @@ version = "0.15.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c76ee391b03d35510d9fa917357c7f1855bd9a6659c95a1b392e33f49b3369bc" dependencies = [ - "bitflags", + "bitflags 1.3.2", "cairo-sys-rs", "glib", "libc", @@ -439,7 +534,7 @@ checksum = "3c55d429bef56ac9172d25fecb85dc8068307d17acd74b377866b7a1ef25d3c8" dependencies = [ "glib-sys", "libc", - "system-deps 6.0.3", + "system-deps 6.1.1", ] [[package]] @@ -454,11 +549,12 @@ dependencies = [ [[package]] name = "cc" -version = "1.0.79" +version = "1.0.82" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f" +checksum = "305fe645edc1442a0fa8b6726ba61d422798d37a52e12eaecf4b022ebbb88f01" dependencies = [ "jobserver", + "libc", ] [[package]] @@ -469,12 +565,13 @@ checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" [[package]] name = "cfb" -version = "0.6.1" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74f89d248799e3f15f91b70917f65381062a01bb8e222700ea0e5a7ff9785f9c" +checksum = "d38f2da7a0a2c4ccf0065be06397cc26a81f4e528be095826eee9d4adbb8c60f" dependencies = [ "byteorder", - "uuid 0.8.2", + "fnv", + "uuid 1.4.1", ] [[package]] @@ -488,11 +585,12 @@ dependencies = [ [[package]] name = "cfg-expr" -version = "0.11.0" +version = "0.15.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0357a6402b295ca3a86bc148e84df46c02e41f41fef186bda662557ef6328aa" +checksum = "b40ccee03b5175c18cde8f37e7d2a33bcef6f8ec8f7cc0d81090d1bb380949c9" dependencies = [ "smallvec", + "target-lexicon", ] [[package]] @@ -511,6 +609,19 @@ dependencies = [ "keystream", ] +[[package]] +name = "chrono" +version = "0.4.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec837a71355b28f6556dbd569b37b3f363091c0bd4b2e735674521b4c5fd9bc5" +dependencies = [ + "android-tzdata", + "iana-time-zone", + "num-traits", + "serde", + "winapi", +] + [[package]] name = "cipher" version = "0.3.0" @@ -532,33 +643,41 @@ dependencies = [ [[package]] name = "clap" -version = "4.1.4" +version = "4.3.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f13b9c79b5d1dd500d20ef541215a6423c75829ef43117e1b4d17fd8af0b5d76" +checksum = "c27cdf28c0f604ba3f512b0c9a409f8de8513e4816705deb0498b627e7c3a3fd" dependencies = [ - "bitflags", + "clap_builder", "clap_derive", - "clap_lex", - "is-terminal", "once_cell", +] + +[[package]] +name = "clap_builder" +version = "4.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08a9f1ab5e9f01a9b81f202e8562eb9a10de70abf9eaeac1be465c28b75aa4aa" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", "strsim", - "termcolor", ] [[package]] name = "clap_complete" -version = "4.1.4" +version = "4.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "501ff0a401473ea1d4c3b125ff95506b62c5bc5768d818634195fbb7c4ad5ff4" +checksum = "5fc443334c81a804575546c5a8a79b4913b50e28d69232903604cada1de817ce" dependencies = [ "clap", ] [[package]] name = "clap_complete_fig" -version = "4.1.1" +version = "4.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "63a06158a72dbb088f864887b4409fd22600ba379f3cee3040f842234cc5c2d0" +checksum = "99fee1d30a51305a6c2ed3fc5709be3c8af626c9c958e04dd9ae94e27bcbce9f" dependencies = [ "clap", "clap_complete", @@ -566,25 +685,21 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.1.0" +version = "4.3.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "684a277d672e91966334af371f1a7b5833f9aa00b07c84e92fbce95e00208ce8" +checksum = "54a9bb5758fc5dfe728d1019941681eccaf0cf8a4189b692a0ee2f2ecf90a050" dependencies = [ "heck 0.4.1", - "proc-macro-error", "proc-macro2", "quote", - "syn 1.0.107", + "syn 2.0.28", ] [[package]] name = "clap_lex" -version = "0.3.1" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "783fe232adfca04f90f56201b26d79682d4cd2625e0bc7290b95123afe558ade" -dependencies = [ - "os_str_bytes", -] +checksum = "2da6da31387c7e4ef160ffab6d5e7f00c42626fe39aea70a7b0f1773f7dd6c1b" [[package]] name = "cocoa" @@ -592,7 +707,7 @@ version = "0.24.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f425db7937052c684daec3bd6375c8abe2d146dca4b8b143d6db777c39138f3a" dependencies = [ - "bitflags", + "bitflags 1.3.2", "block", "cocoa-foundation", "core-foundation", @@ -604,11 +719,11 @@ dependencies = [ [[package]] name = "cocoa-foundation" -version = "0.1.0" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ade49b65d560ca58c403a479bb396592b155c0185eada742ee323d1d68d6318" +checksum = "931d3837c286f56e3c58423ce4eba12d08db2374461a785c86f672b08b5650d6" dependencies = [ - "bitflags", + "bitflags 1.3.2", "block", "core-foundation", "core-graphics-types", @@ -624,25 +739,31 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" [[package]] -name = "colored" -version = "1.9.3" +name = "colorchoice" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4ffc801dacf156c5854b9df4f425a626539c3a6ef7893cc0c5084a23f0b6c59" +checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" + +[[package]] +name = "colored" +version = "1.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a5f741c91823341bebf717d4c71bda820630ce065443b58bd1b7451af008355" dependencies = [ - "atty", + "is-terminal", "lazy_static", "winapi", ] [[package]] name = "colored" -version = "2.0.0" +version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3616f750b84d8f0de8a58bda93e08e2a81ad3f523089b05f1dffecab48c6cbd" +checksum = "2674ec482fbc38012cf31e6c42ba0177b431a0cb6f15fe40efa5aab1bda516f6" dependencies = [ - "atty", + "is-terminal", "lazy_static", - "winapi", + "windows-sys 0.48.0", ] [[package]] @@ -657,9 +778,9 @@ dependencies = [ [[package]] name = "const-oid" -version = "0.9.2" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "520fbf3c07483f94e3e3ca9d0cfd913d7718ef2483d2cfd91c0d9e91474ab913" +checksum = "28c122c3980598d243d63d9a704629a2d748d101f278052ff068be5a4423ab6f" [[package]] name = "convert_case" @@ -679,9 +800,9 @@ dependencies = [ [[package]] name = "core-foundation-sys" -version = "0.8.3" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc" +checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa" [[package]] name = "core-graphics" @@ -689,7 +810,7 @@ version = "0.22.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2581bbab3b8ffc6fcbd550bf46c355135d16e9ff2a6ea032ad6b9bf1d7efe4fb" dependencies = [ - "bitflags", + "bitflags 1.3.2", "core-foundation", "core-graphics-types", "foreign-types", @@ -698,13 +819,12 @@ dependencies = [ [[package]] name = "core-graphics-types" -version = "0.1.1" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a68b68b3446082644c91ac778bf50cd4104bfb002b5a6a7c44cca5a2c70788b" +checksum = "2bb142d41022986c1d8ff29103a1411c8a3dfad3552f87a4f8dc50d61d4f4e33" dependencies = [ - "bitflags", + "bitflags 1.3.2", "core-foundation", - "foreign-types", "libc", ] @@ -742,9 +862,9 @@ dependencies = [ [[package]] name = "cosmwasm-crypto" -version = "1.3.0" +version = "1.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d076a08ec01ed23c4396aca98ec73a38fa1fee5f310465add52b4108181c7a8" +checksum = "7e272708a9745dad8b591ef8a718571512130f2b39b33e3d7a27c558e3069394" dependencies = [ "digest 0.10.7", "ed25519-zebra", @@ -755,11 +875,11 @@ dependencies = [ [[package]] name = "cosmwasm-derive" -version = "1.3.0" +version = "1.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dec361f3c09d7b41221948fc17be9b3c96cb58e55a02f82da36f888a651f2584" +checksum = "296db6a3caca5283425ae0cf347f4e46999ba3f6620dbea8939a0e00347831ce" dependencies = [ - "syn 1.0.107", + "syn 1.0.109", ] [[package]] @@ -783,7 +903,7 @@ checksum = "2bfa39422f0d9f1c9a6fd3711573258495314dfa3aae738ea825ecd9964bc659" dependencies = [ "proc-macro2", "quote", - "syn 1.0.107", + "syn 1.0.109", ] [[package]] @@ -802,15 +922,15 @@ dependencies = [ "schemars", "serde", "serde-json-wasm", - "sha2 0.10.6", + "sha2 0.10.7", "thiserror", ] [[package]] name = "cpufeatures" -version = "0.2.5" +version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28d997bd5e24a5928dd43e46dc529867e207907fe0b239c3477d924f7f2ca320" +checksum = "a17b76ff3a4162b0b27f354a0c87015ddad39d35f9c0c36607a3bdd175dde1f1" dependencies = [ "libc", ] @@ -826,9 +946,9 @@ dependencies = [ [[package]] name = "crossbeam-channel" -version = "0.5.6" +version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2dd04ddaf88237dc3b8d8f9a3c1004b506b54b3313403944054d23c0870c521" +checksum = "a33c2bf77f2df06183c3aa30d1e96c0695a313d4f9c453cc3762a6db39f99200" dependencies = [ "cfg-if", "crossbeam-utils", @@ -836,9 +956,9 @@ dependencies = [ [[package]] name = "crossbeam-utils" -version = "0.8.14" +version = "0.8.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fb766fa798726286dbbb842f174001dab8abc7b627a1dd86e0b7222a95d929f" +checksum = "5a22b2d63d4d1dc0b7f1b6b2747dd0088008a9be28b6ddf0b1e7d335e3037294" dependencies = [ "cfg-if", ] @@ -912,17 +1032,17 @@ dependencies = [ "proc-macro2", "quote", "smallvec", - "syn 1.0.107", + "syn 1.0.109", ] [[package]] name = "cssparser-macros" -version = "0.6.0" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfae75de57f2b2e85e8768c3ea840fd159c8f33e2b6522c7835b7abac81be16e" +checksum = "13b588ba4ac1a99f7f2964d24b3d896ddc6bf847ee3855dbd4366f058cfcd331" dependencies = [ "quote", - "syn 1.0.107", + "syn 2.0.28", ] [[package]] @@ -941,7 +1061,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6d2301688392eb071b0bf1a37be05c469d3cc4dbbd95df672fe28ab021e6a096" dependencies = [ "quote", - "syn 1.0.107", + "syn 1.0.109", ] [[package]] @@ -962,12 +1082,6 @@ dependencies = [ "cipher 0.4.4", ] -[[package]] -name = "cty" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b365fabc795046672053e29c954733ec3b05e4be654ab130fe8f1f94d7051f35" - [[package]] name = "curve25519-dalek" version = "3.2.0" @@ -1030,7 +1144,7 @@ dependencies = [ "cosmwasm-std", "cw2", "schemars", - "semver 1.0.16", + "semver 1.0.18", "serde", "thiserror", ] @@ -1092,9 +1206,9 @@ dependencies = [ [[package]] name = "darling" -version = "0.13.4" +version = "0.20.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a01d95850c592940db9b8194bc39f4bc0e89dee5c4265e4b1807c34a9aba453c" +checksum = "0209d94da627ab5605dcccf08bb18afa5009cfbef48d8a8b7d7bdbc79be25c5e" dependencies = [ "darling_core", "darling_macro", @@ -1102,27 +1216,27 @@ dependencies = [ [[package]] name = "darling_core" -version = "0.13.4" +version = "0.20.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "859d65a907b6852c9361e3185c862aae7fafd2887876799fa55f5f99dc40d610" +checksum = "177e3443818124b357d8e76f53be906d60937f0d3a90773a664fa63fa253e621" dependencies = [ "fnv", "ident_case", "proc-macro2", "quote", "strsim", - "syn 1.0.107", + "syn 2.0.28", ] [[package]] name = "darling_macro" -version = "0.13.4" +version = "0.20.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c972679f83bdf9c42bd905396b6c3588a843a17f0f16dfcfa3e2c5d57441835" +checksum = "836a9bbc7ad63342d6d6e7b815ccab164bc77a2d95d84bc3117a8c0d5c98e2d5" dependencies = [ "darling_core", "quote", - "syn 1.0.107", + "syn 2.0.28", ] [[package]] @@ -1137,14 +1251,23 @@ dependencies = [ [[package]] name = "der" -version = "0.7.7" +version = "0.7.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c7ed52955ce76b1554f509074bb357d3fb8ac9b51288a65a3fd480d1dfba946" +checksum = "fffa369a668c8af7dbf8b5e56c9f744fbd399949ed171606040001947de40b1c" dependencies = [ "const-oid", "zeroize", ] +[[package]] +name = "deranged" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7684a49fb1af197853ef7b2ee694bc1f5b4179556f1e5710e1760c5db6f5e929" +dependencies = [ + "serde", +] + [[package]] name = "derivative" version = "2.2.0" @@ -1153,7 +1276,7 @@ checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b" dependencies = [ "proc-macro2", "quote", - "syn 1.0.107", + "syn 1.0.109", ] [[package]] @@ -1165,8 +1288,8 @@ dependencies = [ "convert_case", "proc-macro2", "quote", - "rustc_version 0.4.0", - "syn 1.0.107", + "rustc_version", + "syn 1.0.109", ] [[package]] @@ -1193,7 +1316,7 @@ version = "0.10.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ - "block-buffer 0.10.3", + "block-buffer 0.10.4", "const-oid", "crypto-common", "subtle 2.4.1", @@ -1269,36 +1392,36 @@ checksum = "bd0c93bb4b0c6d9b77f4435b0ae98c24d17f1c45b2ff844c6151a07256ca923b" [[package]] name = "dotenvy" -version = "0.15.6" +version = "0.15.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03d8c417d7a8cb362e0c37e5d815f5eb7c37f79ff93707329d5a194e42e54ca0" +checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b" [[package]] name = "dtoa" -version = "0.4.8" +version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56899898ce76aaf4a0f24d914c97ea6ed976d42fec6ad33fcbb0a1103e07b2b0" +checksum = "dcbb2bf8e87535c23f7a8a321e364ce21462d0ff10cb6407820e8e96dfff6653" [[package]] name = "dtoa-short" -version = "0.3.3" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bde03329ae10e79ede66c9ce4dc930aa8599043b0743008548680f25b91502d6" +checksum = "dbaceec3c6e4211c79e7b1800fb9680527106beb2f9c51904a3210c03a448c74" dependencies = [ "dtoa", ] [[package]] name = "dunce" -version = "1.0.3" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bd4b30a6560bbd9b4620f4de34c3f14f60848e58a9b7216801afcb4c7b31c3c" +checksum = "56ce8c6da7551ec6c462cbaf3bfbc75131ebbfa1c944aeaa9dab51ca1c5f0c3b" [[package]] name = "dyn-clone" -version = "1.0.10" +version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9b0705efd4599c15a38151f4721f7bc388306f61084d3bfd50bd07fbca5cb60" +checksum = "304e6508efa593091e97a9abbc10f90aa7ca635b6d2784feff3c89d41dd12272" [[package]] name = "ecdsa" @@ -1318,7 +1441,7 @@ version = "0.16.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a4b1e0c257a9e9f25f90ff76d7a68360ed497ee519c8e428d1825ef0000799d4" dependencies = [ - "der 0.7.7", + "der 0.7.8", "digest 0.10.7", "elliptic-curve 0.13.5", "rfc6979 0.4.0", @@ -1337,9 +1460,9 @@ dependencies = [ [[package]] name = "ed25519" -version = "2.2.1" +version = "2.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fb04eee5d9d907f29e80ee6b0e78f7e2c82342c63e3580d8c4f69d9d5aad963" +checksum = "60f6d271ca33075c88028be6f04d502853d63a5ece419d269c15315d4fc1cf1d" dependencies = [ "pkcs8 0.10.2", "signature 2.1.0", @@ -1379,7 +1502,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7c24f403d068ad0b359e577a77f92392118be3f3c927538f2bb544a5ecd828c6" dependencies = [ "curve25519-dalek", - "hashbrown", + "hashbrown 0.12.3", "hex", "rand_core 0.6.4", "serde", @@ -1389,9 +1512,9 @@ dependencies = [ [[package]] name = "either" -version = "1.8.1" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91" +checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" [[package]] name = "elliptic-curve" @@ -1458,13 +1581,13 @@ dependencies = [ [[package]] name = "enum-iterator-derive" -version = "1.1.0" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "828de45d0ca18782232dfb8f3ea9cc428e8ced380eb26a520baaacfc70de39ce" +checksum = "eecf8589574ce9b895052fa12d69af7a233f99e6107f5cb8dd1044f2a17bfdcb" dependencies = [ "proc-macro2", "quote", - "syn 1.0.107", + "syn 2.0.28", ] [[package]] @@ -1481,14 +1604,20 @@ dependencies = [ ] [[package]] -name = "errno" -version = "0.2.8" +name = "equivalent" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f639046355ee4f37944e44f60642c6f3a7efa3cf6b78c78a0d989a8ce6c396a1" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + +[[package]] +name = "errno" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b30f669a7961ef1631673d2766cc92f52d64f7ef354d4fe0ddfd30ed52f0f4f" dependencies = [ "errno-dragonfly", "libc", - "winapi", + "windows-sys 0.48.0", ] [[package]] @@ -1513,20 +1642,26 @@ dependencies = [ [[package]] name = "fastrand" -version = "1.8.0" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7a407cfaa3385c4ae6b23e84623d48c2798d06e3e6a1878f7f59f17b3f86499" +checksum = "6999dc1837253364c2ebb0704ba97994bd874e8f195d665c50b7548f6ea92764" + +[[package]] +name = "fdeflate" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d329bdeac514ee06249dabc27877490f17f5d371ec693360768b838e19f3ae10" dependencies = [ - "instant", + "simd-adler32", ] [[package]] name = "fern" -version = "0.6.1" +version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3bdd7b0849075e79ee9a1836df22c717d1eba30451796fdc631b04565dd11e2a" +checksum = "d9f0c14694cbd524c8720dd69b0e3179344f04ebb5f90f2e4a440c6ea3b2f1ee" dependencies = [ - "colored 1.9.3", + "colored 1.9.4", "log", ] @@ -1562,31 +1697,31 @@ dependencies = [ [[package]] name = "field-offset" -version = "0.3.4" +version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e1c54951450cbd39f3dbcf1005ac413b49487dabf18a720ad2383eccfeffb92" +checksum = "38e2275cc4e4fc009b0669731a1e5ab7ebf11f469eaede2bab9309a5b4d6057f" dependencies = [ "memoffset", - "rustc_version 0.3.3", + "rustc_version", ] [[package]] name = "filetime" -version = "0.2.20" +version = "0.2.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a3de6e8d11b22ff9edc6d916f890800597d60f8b2da1caf2955c274638d6412" +checksum = "d4029edd3e734da6fe05b6cd7bd2960760a616bd2ddd0d59a0124746d6272af0" dependencies = [ "cfg-if", "libc", - "redox_syscall", - "windows-sys 0.45.0", + "redox_syscall 0.3.5", + "windows-sys 0.48.0", ] [[package]] name = "flate2" -version = "1.0.25" +version = "1.0.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8a2db397cb1c8772f31494cb8917e48cd1e64f0fa7efac59fbd741a0a8ce841" +checksum = "3b9429470923de8e8cbd4d2dc513535400b4b3fef0319fb5c4e1f520a7bef743" dependencies = [ "crc32fast", "miniz_oxide", @@ -1625,9 +1760,9 @@ checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" [[package]] name = "form_urlencoded" -version = "1.1.0" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9c384f161156f5260c24a097c56119f9be8c798586aecc13afbcbe7b7e26bf8" +checksum = "a62bc1cf6f830c2ec14a513a9fb124d0a213a629668a4186f329db21fe045652" dependencies = [ "percent-encoding", ] @@ -1656,9 +1791,9 @@ dependencies = [ [[package]] name = "futures" -version = "0.3.26" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13e2792b0ff0340399d58445b88fd9770e3489eff258a4cbc1523418f12abf84" +checksum = "23342abe12aba583913b2e62f22225ff9c950774065e4bfb61a19cd9770fec40" dependencies = [ "futures-channel", "futures-core", @@ -1671,9 +1806,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.26" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e5317663a9089767a1ec00a487df42e0ca174b61b4483213ac24448e4664df5" +checksum = "955518d47e09b25bbebc7a18df10b81f0c766eaf4c4f1cccef2fca5f2a4fb5f2" dependencies = [ "futures-core", "futures-sink", @@ -1681,15 +1816,15 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.26" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec90ff4d0fe1f57d600049061dc6bb68ed03c7d2fbd697274c41805dcb3f8608" +checksum = "4bca583b7e26f571124fe5b7561d49cb2868d79116cfa0eefce955557c6fee8c" [[package]] name = "futures-executor" -version = "0.3.26" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8de0a35a6ab97ec8869e32a2473f4b1324459e14c29275d14b10cb1fd19b50e" +checksum = "ccecee823288125bd88b4d7f565c9e58e41858e47ab72e8ea2d64e93624386e0" dependencies = [ "futures-core", "futures-task", @@ -1698,38 +1833,38 @@ dependencies = [ [[package]] name = "futures-io" -version = "0.3.26" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfb8371b6fb2aeb2d280374607aeabfc99d95c72edfe51692e42d3d7f0d08531" +checksum = "4fff74096e71ed47f8e023204cfd0aa1289cd54ae5430a9523be060cdb849964" [[package]] name = "futures-macro" -version = "0.3.26" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95a73af87da33b5acf53acfebdc339fe592ecf5357ac7c0a7734ab9d8c876a70" +checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72" dependencies = [ "proc-macro2", "quote", - "syn 1.0.107", + "syn 2.0.28", ] [[package]] name = "futures-sink" -version = "0.3.26" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f310820bb3e8cfd46c80db4d7fb8353e15dfff853a127158425f31e0be6c8364" +checksum = "f43be4fe21a13b9781a69afa4985b0f6ee0e1afab2c6f454a8cf30e2b2237b6e" [[package]] name = "futures-task" -version = "0.3.26" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcf79a1bf610b10f42aea489289c5a2c478a786509693b80cd39c44ccd936366" +checksum = "76d3d132be6c0e6aa1534069c705a74a5997a356c0dc2f86a47765e5617c5b65" [[package]] name = "futures-util" -version = "0.3.26" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c1d6de3acfef38d2be4b1f543f553131788603495be83da675e180c8d6b7bd1" +checksum = "26b01e40b772d54cf6c6d721c1d1abd0647a0106a12ecaa1c186273392a69533" dependencies = [ "futures-channel", "futures-core", @@ -1758,7 +1893,7 @@ version = "0.15.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a6e05c1f572ab0e1f15be94217f0dc29088c248b14f792a5ff0af0d84bcda9e8" dependencies = [ - "bitflags", + "bitflags 1.3.2", "cairo-rs", "gdk-pixbuf", "gdk-sys", @@ -1774,7 +1909,7 @@ version = "0.15.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ad38dd9cc8b099cceecdf41375bb6d481b1b5a7cd5cd603e10a69a9383f8619a" dependencies = [ - "bitflags", + "bitflags 1.3.2", "gdk-pixbuf-sys", "gio", "glib", @@ -1791,7 +1926,7 @@ dependencies = [ "glib-sys", "gobject-sys", "libc", - "system-deps 6.0.3", + "system-deps 6.1.1", ] [[package]] @@ -1808,7 +1943,7 @@ dependencies = [ "libc", "pango-sys", "pkg-config", - "system-deps 6.0.3", + "system-deps 6.1.1", ] [[package]] @@ -1820,21 +1955,21 @@ dependencies = [ "gdk-sys", "glib-sys", "libc", - "system-deps 6.0.3", + "system-deps 6.1.1", "x11", ] [[package]] name = "generator" -version = "0.7.2" +version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d266041a359dfa931b370ef684cceb84b166beb14f7f0421f4a6a3d0c446d12e" +checksum = "5cc16584ff22b460a382b7feec54b23d2908d858152e5739a120b949293bd74e" dependencies = [ "cc", "libc", "log", "rustversion", - "windows 0.39.0", + "windows 0.48.0", ] [[package]] @@ -1892,7 +2027,7 @@ dependencies = [ "proc-macro-error", "proc-macro2", "quote", - "syn 1.0.107", + "syn 1.0.109", ] [[package]] @@ -1905,13 +2040,19 @@ dependencies = [ "polyval", ] +[[package]] +name = "gimli" +version = "0.27.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c80984affa11d98d1b88b66ac8853f143217b399d3c74116778ff8fdb4ed2e" + [[package]] name = "gio" version = "0.15.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "68fdbc90312d462781a395f7a16d96a2b379bb6ef8cd6310a2df272771c4283b" dependencies = [ - "bitflags", + "bitflags 1.3.2", "futures-channel", "futures-core", "futures-io", @@ -1931,7 +2072,7 @@ dependencies = [ "glib-sys", "gobject-sys", "libc", - "system-deps 6.0.3", + "system-deps 6.1.1", "winapi", ] @@ -1941,7 +2082,7 @@ version = "0.14.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d0155506aab710a86160ddb504a480d2964d7ab5b9e62419be69e0032bc5931c" dependencies = [ - "bitflags", + "bitflags 1.3.2", "libc", "libgit2-sys", "log", @@ -1954,7 +2095,7 @@ version = "0.15.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "edb0306fbad0ab5428b0ca674a23893db909a98582969c9b537be4ced78c505d" dependencies = [ - "bitflags", + "bitflags 1.3.2", "futures-channel", "futures-core", "futures-executor", @@ -1970,9 +2111,9 @@ dependencies = [ [[package]] name = "glib-macros" -version = "0.15.11" +version = "0.15.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25a68131a662b04931e71891fb14aaf65ee4b44d08e8abc10f49e77418c86c64" +checksum = "10c6ae9f6fa26f4fb2ac16b528d138d971ead56141de489f8111e259b9df3c4a" dependencies = [ "anyhow", "heck 0.4.1", @@ -1980,7 +2121,7 @@ dependencies = [ "proc-macro-error", "proc-macro2", "quote", - "syn 1.0.107", + "syn 1.0.109", ] [[package]] @@ -1990,7 +2131,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ef4b192f8e65e9cf76cbf4ea71fa8e3be4a0e18ffe3d68b8da6836974cc5bad4" dependencies = [ "libc", - "system-deps 6.0.3", + "system-deps 6.1.1", ] [[package]] @@ -2001,9 +2142,9 @@ checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" [[package]] name = "globset" -version = "0.4.10" +version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "029d74589adefde59de1a0c4f4732695c32805624aec7b68d91503d4dba79afc" +checksum = "759c97c1e17c55525b57192c06a267cda0ac5210b222d6b82189a2338fa1c13d" dependencies = [ "aho-corasick", "bstr", @@ -2020,7 +2161,7 @@ checksum = "0d57ce44246becd17153bd035ab4d32cfee096a657fc01f2231c9278378d1e0a" dependencies = [ "glib-sys", "libc", - "system-deps 6.0.3", + "system-deps 6.1.1", ] [[package]] @@ -2064,7 +2205,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "92e3004a2d5d6d8b5057d2b57b3712c9529b62e82c77f25c1fecde1fd5c23bd0" dependencies = [ "atk", - "bitflags", + "bitflags 1.3.2", "cairo-rs", "field-offset", "futures-channel", @@ -2095,28 +2236,28 @@ dependencies = [ "gobject-sys", "libc", "pango-sys", - "system-deps 6.0.3", + "system-deps 6.1.1", ] [[package]] name = "gtk3-macros" -version = "0.15.4" +version = "0.15.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24f518afe90c23fba585b2d7697856f9e6a7bbc62f65588035e66f6afb01a2e9" +checksum = "684c0456c086e8e7e9af73ec5b84e35938df394712054550e81558d21c44ab0d" dependencies = [ "anyhow", "proc-macro-crate", "proc-macro-error", "proc-macro2", "quote", - "syn 1.0.107", + "syn 1.0.109", ] [[package]] name = "h2" -version = "0.3.15" +version = "0.3.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f9f29bc9dda355256b2916cf526ab02ce0aeaaaf2bad60d65ef3f12f11dd0f4" +checksum = "97ec8491ebaf99c8eaa73058b045fe58073cd6be7f596ac993ced0b0a0c01049" dependencies = [ "bytes", "fnv", @@ -2124,7 +2265,7 @@ dependencies = [ "futures-sink", "futures-util", "http", - "indexmap", + "indexmap 1.9.3", "slab", "tokio", "tokio-util", @@ -2154,6 +2295,12 @@ dependencies = [ "ahash", ] +[[package]] +name = "hashbrown" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c6201b9ff9fd90a5a3bac2e56a830d0caa509576f0e503818ee82c181b3437a" + [[package]] name = "headers" version = "0.3.8" @@ -2161,7 +2308,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f3e372db8e5c0d213e0cd0b9be18be2aca3d44cf2fe30a9d46a65581cd454584" dependencies = [ "base64 0.13.1", - "bitflags", + "bitflags 1.3.2", "bytes", "headers-core", "http", @@ -2205,18 +2352,9 @@ dependencies = [ [[package]] name = "hermit-abi" -version = "0.2.6" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee512640fe35acbfb4bb779db6f0d80704c2cacfa2e39b601ef3e3f47d1ae4c7" -dependencies = [ - "libc", -] - -[[package]] -name = "hermit-abi" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fed44880c466736ef9a5c5b5facefb5ed0785676d0c02d612db14e54f0d84286" +checksum = "443144c8cdadd93ebf52ddb4056d257f5b52c04d3c804e657d19eb73fc33668b" [[package]] name = "hex" @@ -2270,18 +2408,18 @@ dependencies = [ "markup5ever", "proc-macro2", "quote", - "syn 1.0.107", + "syn 1.0.109", ] [[package]] name = "http" -version = "0.2.8" +version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75f43d41e26995c17e71ee126451dd3941010b0514a81a9d11f3b341debc2399" +checksum = "bd6effc99afb63425aff9b05836f029929e345a6148a14b7ecd5ab67af944482" dependencies = [ "bytes", "fnv", - "itoa 1.0.5", + "itoa 1.0.9", ] [[package]] @@ -2309,9 +2447,9 @@ checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" [[package]] name = "httpdate" -version = "1.0.2" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" [[package]] name = "humantime" @@ -2340,9 +2478,9 @@ dependencies = [ [[package]] name = "hyper" -version = "0.14.24" +version = "0.14.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e011372fa0b68db8350aa7a248930ecc7839bf46d8485577d69f117a75f164c" +checksum = "ffb1cfd654a8219eaef89881fdb3bb3b1cdc5fa75ded05d6933b2b382e395468" dependencies = [ "bytes", "futures-channel", @@ -2353,9 +2491,9 @@ dependencies = [ "http-body", "httparse", "httpdate", - "itoa 1.0.5", + "itoa 1.0.9", "pin-project-lite", - "socket2", + "socket2 0.4.9", "tokio", "tower-service", "tracing", @@ -2412,6 +2550,29 @@ dependencies = [ "tokio-native-tls", ] +[[package]] +name = "iana-time-zone" +version = "0.1.57" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fad5b825842d2b38bd206f3e81d6957625fd7f0a361e345c30e01a0ae2dd613" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "wasm-bindgen", + "windows 0.48.0", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + [[package]] name = "ico" version = "0.2.0" @@ -2430,11 +2591,10 @@ checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" [[package]] name = "idna" -version = "0.2.1" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de910d521f7cc3135c4de8db1cb910e0b5ed1dc6f57c381cd07e8e661ce10094" +checksum = "7d20d6b07bfbc108882d88ed8e37d39636dcc260e15e30c45e6ba089610b917c" dependencies = [ - "matches", "unicode-bidi", "unicode-normalization", ] @@ -2458,9 +2618,9 @@ dependencies = [ [[package]] name = "image" -version = "0.24.5" +version = "0.24.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69b7ea949b537b0fd0af141fff8c77690f2ce96f4f41f042ccb6c69c6c965945" +checksum = "6f3dfdbdd72063086ff443e297b61695500514b1e41095b6fb9a5ab48a70a711" dependencies = [ "bytemuck", "byteorder", @@ -2477,20 +2637,31 @@ checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683" [[package]] name = "indexmap" -version = "1.9.2" +version = "1.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1885e79c1fc4b10f0e172c475f458b7f7b93061064d98c3293e98c5ba0c8b399" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" dependencies = [ "autocfg", - "hashbrown", + "hashbrown 0.12.3", + "serde", +] + +[[package]] +name = "indexmap" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5477fe2230a79769d8dc68e0eabf5437907c0457a5614a9e8dddb67f65eb65d" +dependencies = [ + "equivalent", + "hashbrown 0.14.0", "serde", ] [[package]] name = "infer" -version = "0.7.0" +version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20b2b533137b9cad970793453d4f921c2e91312a6d88b1085c07bc15fc51bb3b" +checksum = "a898e4b7951673fce96614ce5751d13c40fc5674bc2d759288e46c3ab62598b3" dependencies = [ "cfb", ] @@ -2513,32 +2684,21 @@ dependencies = [ "cfg-if", ] -[[package]] -name = "io-lifetimes" -version = "1.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1abeb7a0dd0f8181267ff8adc397075586500b81b28a73e8a0208b00fc170fb3" -dependencies = [ - "libc", - "windows-sys 0.45.0", -] - [[package]] name = "ipnet" -version = "2.7.1" +version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30e22bd8629359895450b59ea7a776c850561b96a3b1d31321c1949d9e6c9146" +checksum = "28b29a3cd74f0f4598934efe3aeba42bae0eb4680554128851ebbecb02af14e6" [[package]] name = "is-terminal" -version = "0.4.3" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22e18b0a45d56fe973d6db23972bf5bc46f988a4a2385deac9cc29572f09daef" +checksum = "cb0889898416213fab133e1d33a0e5858a48177452750691bde3666d0fdbaf8b" dependencies = [ - "hermit-abi 0.3.1", - "io-lifetimes", + "hermit-abi 0.3.2", "rustix", - "windows-sys 0.45.0", + "windows-sys 0.48.0", ] [[package]] @@ -2558,9 +2718,9 @@ checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4" [[package]] name = "itoa" -version = "1.0.5" +version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fad582f4b9e86b6caa621cabeb0963332d92eea04729ab12892c2533951e6440" +checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" [[package]] name = "javascriptcore-rs" @@ -2568,7 +2728,7 @@ version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bf053e7843f2812ff03ef5afe34bb9c06ffee120385caad4f6b9967fcd37d41c" dependencies = [ - "bitflags", + "bitflags 1.3.2", "glib", "javascriptcore-rs-sys", ] @@ -2607,18 +2767,18 @@ checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" [[package]] name = "jobserver" -version = "0.1.25" +version = "0.1.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "068b1ee6743e4d11fb9c6a1e6064b3693a1b600e7f5f5988047d98b3dc9fb90b" +checksum = "936cfd212a0155903bcbc060e316fb6cc7cbf2e1907329391ebadc1fe0ce77c2" dependencies = [ "libc", ] [[package]] name = "js-sys" -version = "0.3.60" +version = "0.3.64" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49409df3e3bf0856b916e2ceaca09ee28e6871cf7d9ce97a692cacfdb2a25a47" +checksum = "c5f195fe497f702db0f318b07fdd68edb16955aed830df8363d837542f8f935a" dependencies = [ "wasm-bindgen", ] @@ -2631,7 +2791,19 @@ checksum = "eb3fa5a61630976fc4c353c70297f2e93f1930e3ccee574d59d618ccbd5154ce" dependencies = [ "serde", "serde_json", - "treediff", + "treediff 3.0.2", +] + +[[package]] +name = "json-patch" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f54898088ccb91df1b492cc80029a6fdf1c48ca0db7c6822a8babad69c94658" +dependencies = [ + "serde", + "serde_json", + "thiserror", + "treediff 4.0.2", ] [[package]] @@ -2643,7 +2815,7 @@ dependencies = [ "cfg-if", "ecdsa 0.14.8", "elliptic-curve 0.12.3", - "sha2 0.10.6", + "sha2 0.10.7", ] [[package]] @@ -2656,7 +2828,7 @@ dependencies = [ "ecdsa 0.16.8", "elliptic-curve 0.13.5", "once_cell", - "sha2 0.10.6", + "sha2 0.10.7", "signature 2.1.0", ] @@ -2686,9 +2858,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.146" +version = "0.2.147" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f92be4933c13fd498862a9e02a3055f8a8d9c039ce33db97306fd5a6caa7f29b" +checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3" [[package]] name = "libgit2-sys" @@ -2710,9 +2882,9 @@ checksum = "f7012b1bbb0719e1097c47611d3898568c546d597c2e74d66f6087edd5233ff4" [[package]] name = "libz-sys" -version = "1.1.8" +version = "1.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9702761c3935f8cc2f101793272e202c72b99da8f4224a19ddcf1279a6450bbf" +checksum = "d97137b25e321a73eef1418d1d5d2eda4d77e12813f8e6dead84bc52c5870a7b" dependencies = [ "cc", "libc", @@ -2731,9 +2903,9 @@ dependencies = [ [[package]] name = "linux-raw-sys" -version = "0.1.4" +version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f051f77a7c8e6957c0696eac88f26b0117e54f52d3fc682ab19397a8812846a4" +checksum = "57bcfdad1b858c2db7c38303a6d2ad4dfaf5eb53dfeb0910128b2c26d6158503" [[package]] name = "lioness" @@ -2749,9 +2921,9 @@ dependencies = [ [[package]] name = "lock_api" -version = "0.4.9" +version = "0.4.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "435011366fe56583b16cf956f9df0095b405b82d76425bc8981c0e22e60ec4df" +checksum = "c1cc9717a20b1bb222f333e6a92fd32f7d8a18ddc5a3191a11af45dcbf4dcd16" dependencies = [ "autocfg", "scopeguard", @@ -2759,11 +2931,10 @@ dependencies = [ [[package]] name = "log" -version = "0.4.17" +version = "0.4.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" +checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" dependencies = [ - "cfg-if", "serde", ] @@ -2817,7 +2988,7 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558" dependencies = [ - "regex-automata", + "regex-automata 0.1.10", ] [[package]] @@ -2834,18 +3005,18 @@ checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" [[package]] name = "memoffset" -version = "0.6.5" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce" +checksum = "5a634b1c61a95585bd15607c6ab0c4e5b226e695ff2800ba0cdccddf208c406c" dependencies = [ "autocfg", ] [[package]] name = "mime" -version = "0.3.16" +version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" [[package]] name = "minisign-verify" @@ -2855,23 +3026,23 @@ checksum = "933dca44d65cdd53b355d0b73d380a2ff5da71f87f036053188bf1eab6a19881" [[package]] name = "miniz_oxide" -version = "0.6.2" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b275950c28b37e794e8c55d88aeb5e139d0ce23fdbbeda68f8d7174abdf9e8fa" +checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7" dependencies = [ "adler", + "simd-adler32", ] [[package]] name = "mio" -version = "0.8.5" +version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5d732bc30207a6423068df043e3d02e0735b155ad7ce1a6f76fe2baa5b158de" +checksum = "927a765cd3fc26206e66b296465fa9d3e5ab003e651c1b3c060e7956d96b19d2" dependencies = [ "libc", - "log", "wasi 0.11.0+wasi-snapshot-preview1", - "windows-sys 0.42.0", + "windows-sys 0.48.0", ] [[package]] @@ -2898,7 +3069,7 @@ version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2032c77e030ddee34a6787a64166008da93f6a352b629261d0fee232b8742dd4" dependencies = [ - "bitflags", + "bitflags 1.3.2", "jni-sys", "ndk-sys", "num_enum", @@ -2932,15 +3103,6 @@ version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72ef4a56884ca558e5ddb05a1d1e7e1bfd9a68d9ed024c21704cc98872dae1bb" -[[package]] -name = "nom8" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae01545c9c7fc4486ab7debaf2aad7003ac19431791868fb2e8066df97fad2f8" -dependencies = [ - "memchr", -] - [[package]] name = "nu-ansi-term" version = "0.46.0" @@ -2959,7 +3121,7 @@ checksum = "876a53fff98e03a936a674b29568b0e605f06b29372c2489ff4de23f1949743d" dependencies = [ "proc-macro2", "quote", - "syn 1.0.107", + "syn 1.0.109", ] [[package]] @@ -2985,9 +3147,9 @@ dependencies = [ [[package]] name = "num-traits" -version = "0.2.15" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" +checksum = "f30b0abd723be7e2ffca1272140fac1a2f084c77ec3e123c192b66af1ee9e6c2" dependencies = [ "autocfg", "libm", @@ -2995,33 +3157,33 @@ dependencies = [ [[package]] name = "num_cpus" -version = "1.15.0" +version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fac9e2da13b5eb447a6ce3d392f23a29d8694bff781bf03a16cd9ac8697593b" +checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" dependencies = [ - "hermit-abi 0.2.6", + "hermit-abi 0.3.2", "libc", ] [[package]] name = "num_enum" -version = "0.5.9" +version = "0.5.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d829733185c1ca374f17e52b762f24f535ec625d2cc1f070e34c8a9068f341b" +checksum = "1f646caf906c20226733ed5b1374287eb97e3c2a5c227ce668c1f2ce20ae57c9" dependencies = [ "num_enum_derive", ] [[package]] name = "num_enum_derive" -version = "0.5.9" +version = "0.5.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2be1598bf1c313dcdd12092e3f1920f463462525a21b7b4e11b4168353d0123e" +checksum = "dcbff9bc912032c62bf65ef1d5aea88983b420f4f839db1e9b0c281a25c9c799" dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 1.0.107", + "syn 1.0.109", ] [[package]] @@ -3122,7 +3284,7 @@ dependencies = [ "log", "nym-network-defaults", "serde", - "toml 0.7.4", + "toml 0.7.6", "url", ] @@ -3318,7 +3480,7 @@ dependencies = [ "async-trait", "base64 0.13.1", "bip39", - "colored 2.0.0", + "colored 2.0.4", "cosmrs", "cosmwasm-std", "cw3", @@ -3408,7 +3570,7 @@ dependencies = [ "base64 0.13.1", "bip39", "cfg-if", - "colored 2.0.0", + "colored 2.0.4", "cosmrs", "cosmwasm-std", "dirs 4.0.0", @@ -3491,10 +3653,19 @@ dependencies = [ ] [[package]] -name = "once_cell" -version = "1.17.0" +name = "object" +version = "0.31.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f61fba1741ea2b3d6a1e3178721804bb716a68a6aeba1149b5d52e3d464ea66" +checksum = "8bda667d9f2b5051b8833f59f3bf748b28ef54f850f4fcb389a252aa383866d1" +dependencies = [ + "memchr", +] + +[[package]] +name = "once_cell" +version = "1.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" [[package]] name = "opaque-debug" @@ -3520,11 +3691,11 @@ dependencies = [ [[package]] name = "openssl" -version = "0.10.45" +version = "0.10.56" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b102428fd03bc5edf97f62620f7298614c45cedf287c271e7ed450bbaf83f2e1" +checksum = "729b745ad4a5575dd06a3e1af1414bd330ee561c01b3899eb584baeaa8def17e" dependencies = [ - "bitflags", + "bitflags 1.3.2", "cfg-if", "foreign-types", "libc", @@ -3535,13 +3706,13 @@ dependencies = [ [[package]] name = "openssl-macros" -version = "0.1.0" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b501e44f11665960c7e7fcf062c7d96a14ade4aa98116c004b2e37b5be7d736c" +checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 1.0.107", + "syn 2.0.28", ] [[package]] @@ -3552,20 +3723,19 @@ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" [[package]] name = "openssl-src" -version = "111.26.0+1.1.1u" +version = "111.27.0+1.1.1v" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "efc62c9f12b22b8f5208c23a7200a442b2e5999f8bdf80233852122b5a4f6f37" +checksum = "06e8f197c82d7511c5b014030c9b1efeda40d7d5f99d23b4ceed3524a5e63f02" dependencies = [ "cc", ] [[package]] name = "openssl-sys" -version = "0.9.80" +version = "0.9.91" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23bbbf7854cd45b83958ebe919f0e8e516793727652e27fda10a8384cfc790b7" +checksum = "866b5f16f90776b9bb8dc1e1802ac6f0513de3a7a7465867bfbc563dc737faac" dependencies = [ - "autocfg", "cc", "libc", "openssl-src", @@ -3579,12 +3749,6 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" -[[package]] -name = "os_str_bytes" -version = "6.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b7820b9daea5457c9f21c69448905d723fbd21136ccf521748f23fd49e723ee" - [[package]] name = "overload" version = "0.1.1" @@ -3606,7 +3770,7 @@ version = "0.15.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "22e4045548659aee5313bde6c582b0d83a627b7904dd20dc2d9ef0895d414e4f" dependencies = [ - "bitflags", + "bitflags 1.3.2", "glib", "libc", "once_cell", @@ -3622,7 +3786,7 @@ dependencies = [ "glib-sys", "gobject-sys", "libc", - "system-deps 6.0.3", + "system-deps 6.1.1", ] [[package]] @@ -3637,15 +3801,15 @@ dependencies = [ [[package]] name = "parking_lot_core" -version = "0.9.7" +version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9069cbb9f99e3a5083476ccb29ceb1de18b9118cafa53e90c9551235de2b9521" +checksum = "93f00c865fe7cabf650081affecd3871070f26767e7b2070a3ffae14c654b447" dependencies = [ "cfg-if", "libc", - "redox_syscall", + "redox_syscall 0.3.5", "smallvec", - "windows-sys 0.45.0", + "windows-targets", ] [[package]] @@ -3661,9 +3825,9 @@ dependencies = [ [[package]] name = "paste" -version = "1.0.11" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d01a5bd0424d00070b0098dd17ebca6f961a959dead1dbcbbbc1d1cd8d3deeba" +checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c" [[package]] name = "pathdiff" @@ -3721,15 +3885,15 @@ dependencies = [ [[package]] name = "percent-encoding" -version = "2.2.0" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e" +checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94" [[package]] name = "pest" -version = "2.5.5" +version = "2.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "028accff104c4e513bad663bbcd2ad7cfd5304144404c31ed0a77ac103d00660" +checksum = "1acb4a4365a13f749a93f1a094a7805e5cfa0955373a9de860d962eaa3a5fe5a" dependencies = [ "thiserror", "ucd-trie", @@ -3737,9 +3901,9 @@ dependencies = [ [[package]] name = "pest_derive" -version = "2.5.5" +version = "2.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ac3922aac69a40733080f53c1ce7f91dcf57e1a5f6c52f421fadec7fbdc4b69" +checksum = "666d00490d4ac815001da55838c500eafb0320019bbaa44444137c48b443a853" dependencies = [ "pest", "pest_generator", @@ -3747,26 +3911,26 @@ dependencies = [ [[package]] name = "pest_generator" -version = "2.5.5" +version = "2.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d06646e185566b5961b4058dd107e0a7f56e77c3f484549fb119867773c0f202" +checksum = "68ca01446f50dbda87c1786af8770d535423fa8a53aec03b8f4e3d7eb10e0929" dependencies = [ "pest", "pest_meta", "proc-macro2", "quote", - "syn 1.0.107", + "syn 2.0.28", ] [[package]] name = "pest_meta" -version = "2.5.5" +version = "2.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6f60b2ba541577e2a0c307c8f39d1439108120eb7903adeb6497fa880c59616" +checksum = "56af0a30af74d0445c0bf6d9d051c979b516a1a5af790d251daee76005420a48" dependencies = [ "once_cell", "pest", - "sha2 0.10.6", + "sha2 0.10.7", ] [[package]] @@ -3832,7 +3996,7 @@ dependencies = [ "proc-macro-hack", "proc-macro2", "quote", - "syn 1.0.107", + "syn 1.0.109", ] [[package]] @@ -3846,7 +4010,7 @@ dependencies = [ "proc-macro-hack", "proc-macro2", "quote", - "syn 1.0.107", + "syn 1.0.109", ] [[package]] @@ -3869,29 +4033,29 @@ dependencies = [ [[package]] name = "pin-project" -version = "1.0.12" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad29a609b6bcd67fee905812e544992d216af9d755757c05ed2d0e15a74c6ecc" +checksum = "fda4ed1c6c173e3fc7a83629421152e01d7b1f9b7f65fb301e490e8cfc656422" dependencies = [ "pin-project-internal", ] [[package]] name = "pin-project-internal" -version = "1.0.12" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "069bdb1e05adc7a8990dce9cc75370895fbe4e3d58b9b73bf1aee56359344a55" +checksum = "4359fd9c9171ec6e8c62926d6faaf553a8dc3f64e1507e76da7911b4f6a04405" dependencies = [ "proc-macro2", "quote", - "syn 1.0.107", + "syn 2.0.28", ] [[package]] name = "pin-project-lite" -version = "0.2.9" +version = "0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" +checksum = "12cc1b0bf1727a77a54b6654e7b5f1af8604923edc8b81885f8ec92f9e3f0a05" [[package]] name = "pin-utils" @@ -3915,24 +4079,24 @@ version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" dependencies = [ - "der 0.7.7", + "der 0.7.8", "spki 0.7.2", ] [[package]] name = "pkg-config" -version = "0.3.26" +version = "0.3.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ac9a59f73473f1b8d852421e59e64809f025994837ef743615c6d0c5b305160" +checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964" [[package]] name = "plist" -version = "1.4.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5329b8f106a176ab0dce4aae5da86bfcb139bb74fb00882859e03745011f3635" +checksum = "bdc0001cfea3db57a2e24bc0d818e9e20e554b5f97fabb9bc231dc240269ae06" dependencies = [ - "base64 0.13.1", - "indexmap", + "base64 0.21.2", + "indexmap 1.9.3", "line-wrap", "quick-xml", "serde", @@ -3941,21 +4105,22 @@ dependencies = [ [[package]] name = "png" -version = "0.17.7" +version = "0.17.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d708eaf860a19b19ce538740d2b4bdeeb8337fa53f7738455e706623ad5c638" +checksum = "59871cc5b6cce7eaccca5a802b4173377a1c2ba90654246789a8fa2334426d11" dependencies = [ - "bitflags", + "bitflags 1.3.2", "crc32fast", + "fdeflate", "flate2", "miniz_oxide", ] [[package]] name = "polyval" -version = "0.6.0" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ef234e08c11dfcb2e56f79fd70f6f2eb7f025c0ce2333e82f4f0518ecad30c6" +checksum = "d52cff9d1d4dee5fe6d03729099f4a310a41179e0a10dbf542039873f2e826fb" dependencies = [ "cfg-if", "cpufeatures", @@ -3987,12 +4152,12 @@ dependencies = [ [[package]] name = "proc-macro-crate" -version = "1.3.0" +version = "1.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "66618389e4ec1c7afe67d51a9bf34ff9236480f8d51e7489b7d5ab0303c13f34" +checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919" dependencies = [ "once_cell", - "toml_edit 0.18.1", + "toml_edit", ] [[package]] @@ -4004,7 +4169,7 @@ dependencies = [ "proc-macro-error-attr", "proc-macro2", "quote", - "syn 1.0.107", + "syn 1.0.109", "version_check", ] @@ -4027,9 +4192,9 @@ checksum = "dc375e1527247fe1a97d8b7156678dfe7c1af2fc075c9a4db3690ecd2a148068" [[package]] name = "proc-macro2" -version = "1.0.56" +version = "1.0.66" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b63bdb0cd06f1f4dedf69b254734f9b45af66e4a031e42a7480257d9898b435" +checksum = "18fb31db3f9bddb2ea821cde30a9f70117e3f119938b5ee630b7403aa6e2ead9" dependencies = [ "unicode-ident", ] @@ -4054,7 +4219,7 @@ dependencies = [ "itertools", "proc-macro2", "quote", - "syn 1.0.107", + "syn 1.0.109", ] [[package]] @@ -4080,18 +4245,18 @@ checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3" [[package]] name = "quick-xml" -version = "0.26.0" +version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f50b1c63b38611e7d4d7f68b82d3ad0cc71a2ad2e7f61fc10f1328d917c93cd" +checksum = "81b9228215d82c7b61490fec1de287136b5de6f5700f6e58ea9ad61a7964ca51" dependencies = [ "memchr", ] [[package]] name = "quote" -version = "1.0.26" +version = "1.0.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4424af4bf778aae2051a77b60283332f386554255d722233d09fbfc7e30da2fc" +checksum = "50f3b39ccfb720540debaa0164757101c08ecb8d326b15358ce76a62c7e85965" dependencies = [ "proc-macro2", ] @@ -4195,12 +4360,9 @@ dependencies = [ [[package]] name = "raw-window-handle" -version = "0.5.0" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed7e3d950b66e19e0c372f3fa3fbbcf85b1746b571f74e0c2af6042a5c93420a" -dependencies = [ - "cty", -] +checksum = "f2ff9a1f06a88b01621b7ae906ef0211290d1c8a168a15542486a8f61c0833b9" [[package]] name = "redox_syscall" @@ -4208,7 +4370,16 @@ version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" dependencies = [ - "bitflags", + "bitflags 1.3.2", +] + +[[package]] +name = "redox_syscall" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29" +dependencies = [ + "bitflags 1.3.2", ] [[package]] @@ -4218,19 +4389,20 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b" dependencies = [ "getrandom 0.2.10", - "redox_syscall", + "redox_syscall 0.2.16", "thiserror", ] [[package]] name = "regex" -version = "1.7.1" +version = "1.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48aaa5748ba571fb95cd2c85c09f629215d3a6ece942baa100950af03a34f733" +checksum = "81bc1d4caf89fac26a70747fe603c130093b53c773888797a6329091246d651a" dependencies = [ "aho-corasick", "memchr", - "regex-syntax", + "regex-automata 0.3.6", + "regex-syntax 0.7.4", ] [[package]] @@ -4239,31 +4411,39 @@ version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" dependencies = [ - "regex-syntax", + "regex-syntax 0.6.29", +] + +[[package]] +name = "regex-automata" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fed1ceff11a1dddaee50c9dc8e4938bd106e9d89ae372f192311e7da498e3b69" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax 0.7.4", ] [[package]] name = "regex-syntax" -version = "0.6.28" +version = "0.6.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848" +checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" [[package]] -name = "remove_dir_all" -version = "0.5.3" +name = "regex-syntax" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7" -dependencies = [ - "winapi", -] +checksum = "e5ea92a5b6195c6ef2a0295ea818b312502c6fc94dde986c5553242e18fd4ce2" [[package]] name = "reqwest" -version = "0.11.14" +version = "0.11.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21eed90ec8570952d53b772ecf8f206aa1ec9a3d76b2521c56c42973f2d91ee9" +checksum = "cde824a14b7c14f85caff81225f411faacc04a2013f41670f41443742b1c1c55" dependencies = [ - "base64 0.21.0", + "base64 0.21.2", "bytes", "encoding_rs", "futures-core", @@ -4364,13 +4544,10 @@ dependencies = [ ] [[package]] -name = "rustc_version" -version = "0.3.3" +name = "rustc-demangle" +version = "0.1.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0dfe2087c51c460008730de8b57e6a320782fbfb312e1f4d520e6c6fae155ee" -dependencies = [ - "semver 0.11.0", -] +checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" [[package]] name = "rustc_version" @@ -4378,21 +4555,20 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" dependencies = [ - "semver 1.0.16", + "semver 1.0.18", ] [[package]] name = "rustix" -version = "0.36.8" +version = "0.38.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f43abb88211988493c1abb44a70efa56ff0ce98f233b7b276146f1f3f7ba9644" +checksum = "19ed4fa021d81c8392ce04db050a3da9a60299050b7ae1cf482d862b54a7218f" dependencies = [ - "bitflags", + "bitflags 2.4.0", "errno", - "io-lifetimes", "libc", "linux-raw-sys", - "windows-sys 0.45.0", + "windows-sys 0.48.0", ] [[package]] @@ -4422,15 +4598,15 @@ dependencies = [ [[package]] name = "rustversion" -version = "1.0.11" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5583e89e108996506031660fe09baa5011b9dd0341b89029313006d1fb508d70" +checksum = "7ffc183a10b4478d04cbbbfc96d0873219d962dd5accaff2ffbd4ceb7df837f4" [[package]] name = "ryu" -version = "1.0.12" +version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b4b9743ed687d4b4bcedf9ff5eaa7398495ae14e61cba0a295704edbc7decde" +checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741" [[package]] name = "safemem" @@ -4449,21 +4625,21 @@ dependencies = [ [[package]] name = "schannel" -version = "0.1.21" +version = "0.1.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "713cfb06c7059f3588fb8044c0fad1d09e3c01d225e25b9220dbfdcf16dbb1b3" +checksum = "0c3733bf4cf7ea0880754e19cb5a462007c4a8c1914bff372ccc95b464f1df88" dependencies = [ - "windows-sys 0.42.0", + "windows-sys 0.48.0", ] [[package]] name = "schemars" -version = "0.8.11" +version = "0.8.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a5fb6c61f29e723026dc8e923d94c694313212abbecbbe5f55a7748eec5b307" +checksum = "02c613288622e5f0c3fdc5dbd4db1c5fbe752746b1d1a56a0630b78fd00de44f" dependencies = [ "dyn-clone", - "indexmap", + "indexmap 1.9.3", "schemars_derive", "serde", "serde_json", @@ -4471,14 +4647,14 @@ dependencies = [ [[package]] name = "schemars_derive" -version = "0.8.11" +version = "0.8.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f188d036977451159430f3b8dc82ec76364a42b7e289c2b18a9a18f4470058e9" +checksum = "109da1e6b197438deb6db99952990c7f959572794b80ff93707d55a232545e7c" dependencies = [ "proc-macro2", "quote", "serde_derive_internals", - "syn 1.0.107", + "syn 1.0.109", ] [[package]] @@ -4489,9 +4665,9 @@ checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" [[package]] name = "scopeguard" -version = "1.1.0" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "sct" @@ -4524,7 +4700,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc" dependencies = [ "base16ct 0.2.0", - "der 0.7.7", + "der 0.7.8", "generic-array 0.14.7", "pkcs8 0.10.2", "subtle 2.4.1", @@ -4533,11 +4709,11 @@ dependencies = [ [[package]] name = "security-framework" -version = "2.8.2" +version = "2.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a332be01508d814fed64bf28f798a146d73792121129962fdf335bb3c49a4254" +checksum = "05b64fb303737d99b81884b2c63433e9ae28abebe5eb5045dcdd175dc2ecf4de" dependencies = [ - "bitflags", + "bitflags 1.3.2", "core-foundation", "core-foundation-sys", "libc", @@ -4546,9 +4722,9 @@ dependencies = [ [[package]] name = "security-framework-sys" -version = "2.8.0" +version = "2.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31c9bb296072e961fcbd8853511dd39c2d8be2deb1e17c6860b1d30732b323b4" +checksum = "e932934257d3b408ed8f30db49d85ea163bfe74961f017f405b025af298f0c7a" dependencies = [ "core-foundation-sys", "libc", @@ -4560,7 +4736,7 @@ version = "0.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df320f1889ac4ba6bc0cdc9c9af7af4bd64bb927bccdf32d81140dc1f9be12fe" dependencies = [ - "bitflags", + "bitflags 1.3.2", "cssparser", "derive_more", "fxhash", @@ -4585,9 +4761,9 @@ dependencies = [ [[package]] name = "semver" -version = "1.0.16" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58bc9567378fc7690d6b2addae4e60ac2eeea07becb2c64b9f218b53865cba2a" +checksum = "b0293b4b29daaf487284529cc2f5675b8e57c61f70167ba415a463651fd6a918" dependencies = [ "serde", ] @@ -4603,9 +4779,9 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.160" +version = "1.0.183" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb2f3770c8bce3bcda7e149193a069a0f4365bda1fa5cd88e03bca26afc1216c" +checksum = "32ac8da02677876d532745a130fc9d8e6edfa81a269b107c5b00829b91d8eb3c" dependencies = [ "serde_derive", ] @@ -4621,22 +4797,22 @@ dependencies = [ [[package]] name = "serde_bytes" -version = "0.11.9" +version = "0.11.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "416bda436f9aab92e02c8e10d49a15ddd339cea90b6e340fe51ed97abb548294" +checksum = "ab33ec92f677585af6d88c65593ae2375adde54efdbf16d597f2cbc7a6d368ff" dependencies = [ "serde", ] [[package]] name = "serde_derive" -version = "1.0.160" +version = "1.0.183" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "291a097c63d8497e00160b166a967a4a79c64f3facdd01cbd7502231688d77df" +checksum = "aafe972d60b0b9bee71a91b92fee2d4fb3c9d7e8f6b179aa99f27203d99a4816" dependencies = [ "proc-macro2", "quote", - "syn 2.0.15", + "syn 2.0.28", ] [[package]] @@ -4647,36 +4823,36 @@ checksum = "85bf8229e7920a9f636479437026331ce11aa132b4dde37d121944a44d6e5f3c" dependencies = [ "proc-macro2", "quote", - "syn 1.0.107", + "syn 1.0.109", ] [[package]] name = "serde_json" -version = "1.0.93" +version = "1.0.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cad406b69c91885b5107daf2c29572f6c8cdb3c66826821e286c533490c0bc76" +checksum = "076066c5f1078eac5b722a31827a8832fe108bed65dfa75e233c89f8206e976c" dependencies = [ - "itoa 1.0.5", + "itoa 1.0.9", "ryu", "serde", ] [[package]] name = "serde_repr" -version = "0.1.10" +version = "0.1.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a5ec9fa74a20ebbe5d9ac23dac1fc96ba0ecfe9f50f2843b52e537b10fbcb4e" +checksum = "8725e1dfadb3a50f7e5ce0b1a540466f6ed3fe7a0fca2ac2b8b831d31316bd00" dependencies = [ "proc-macro2", "quote", - "syn 1.0.107", + "syn 2.0.28", ] [[package]] name = "serde_spanned" -version = "0.6.2" +version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93107647184f6027e3b7dcb2e11034cf95ffa1e3a682c67951963ac69c1c007d" +checksum = "96426c9936fd7a0124915f9185ea1d20aa9445cc9821142f0a73bc9207a2e186" dependencies = [ "serde", ] @@ -4688,31 +4864,38 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" dependencies = [ "form_urlencoded", - "itoa 1.0.5", + "itoa 1.0.9", "ryu", "serde", ] [[package]] name = "serde_with" -version = "1.14.0" +version = "3.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "678b5a069e50bf00ecd22d0cd8ddf7c236f68581b03db652061ed5eb13a312ff" +checksum = "1402f54f9a3b9e2efe71c1cea24e648acce55887983553eeb858cf3115acfd49" dependencies = [ + "base64 0.21.2", + "chrono", + "hex", + "indexmap 1.9.3", + "indexmap 2.0.0", "serde", + "serde_json", "serde_with_macros", + "time", ] [[package]] name = "serde_with_macros" -version = "1.5.2" +version = "3.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e182d6ec6f05393cc0e5ed1bf81ad6db3a8feedf8ee515ecdd369809bcce8082" +checksum = "9197f1ad0e3c173a0222d3c4404fb04c3afe87e962bcb327af73e8301fa203c7" dependencies = [ "darling", "proc-macro2", "quote", - "syn 1.0.107", + "syn 2.0.28", ] [[package]] @@ -4734,7 +4917,7 @@ checksum = "74064874e9f6a15f04c1f3cb627902d0e6b410abbf36668afa873c61889f1763" dependencies = [ "proc-macro2", "quote", - "syn 1.0.107", + "syn 1.0.109", ] [[package]] @@ -4773,9 +4956,9 @@ dependencies = [ [[package]] name = "sha2" -version = "0.10.6" +version = "0.10.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82e6b795fe2e3b1e845bafcb27aa35405c4d47cdfc92af5fc8d3002f76cebdc0" +checksum = "479fb9d862239e610720565ca91403019f2f00410f1864c5aa7479b950a76ed8" dependencies = [ "cfg-if", "cpufeatures", @@ -4820,6 +5003,12 @@ dependencies = [ "rand_core 0.6.4", ] +[[package]] +name = "simd-adler32" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" + [[package]] name = "siphasher" version = "0.3.10" @@ -4828,36 +5017,46 @@ checksum = "7bd3e3206899af3f8b12af284fafc038cc1dc2b41d1b89dd17297221c5d225de" [[package]] name = "slab" -version = "0.4.7" +version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4614a76b2a8be0058caa9dbbaf66d988527d86d003c11a94fbd335d7661edcef" +checksum = "6528351c9bc8ab22353f9d776db39a20288e8d6c37ef8cfe3317cf875eecfc2d" dependencies = [ "autocfg", ] [[package]] name = "smallvec" -version = "1.10.0" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0" +checksum = "62bb4feee49fdd9f707ef802e22365a35de4b7b299de4763d44bfea899442ff9" [[package]] name = "socket2" -version = "0.4.7" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02e2d2db9033d13a1567121ddd7a095ee144db4e1ca1b1bda3419bc0da294ebd" +checksum = "64a4a911eed85daf18834cfaa86a79b7d266ff93ff5ba14005426219480ed662" dependencies = [ "libc", "winapi", ] +[[package]] +name = "socket2" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2538b18701741680e0322a2302176d3253a35388e2e62f172f64f4f16605f877" +dependencies = [ + "libc", + "windows-sys 0.48.0", +] + [[package]] name = "soup2" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b2b4d76501d8ba387cf0fefbe055c3e0a59891d09f0f995ae4e4b16f6b60f3c0" dependencies = [ - "bitflags", + "bitflags 1.3.2", "gio", "glib", "libc", @@ -4871,7 +5070,7 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "009ef427103fcb17f802871647a7fa6c60cbb654b4c4e4c0ac60a31c5f6dc9cf" dependencies = [ - "bitflags", + "bitflags 1.3.2", "gio-sys", "glib-sys", "gobject-sys", @@ -4926,7 +5125,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9d1e996ef02c474957d681f1b05213dfb0abab947b446a62d37770b23500184a" dependencies = [ "base64ct", - "der 0.7.7", + "der 0.7.8", ] [[package]] @@ -4946,9 +5145,9 @@ dependencies = [ [[package]] name = "string_cache" -version = "0.8.4" +version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "213494b7a2b503146286049378ce02b482200519accc31872ee8be91fa820a08" +checksum = "f91138e76242f575eb1d3b38b4f1362f10d3a43f47d182a5b359af488a02293b" dependencies = [ "new_debug_unreachable", "once_cell", @@ -4995,7 +5194,7 @@ dependencies = [ "proc-macro2", "quote", "rustversion", - "syn 1.0.107", + "syn 1.0.109", ] [[package]] @@ -5027,9 +5226,9 @@ checksum = "734676eb262c623cec13c3155096e08d1f8f29adce39ba17948b18dad1e54142" [[package]] name = "syn" -version = "1.0.107" +version = "1.0.109" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f4064b5b16e03ae50984a5a8ed5d4f8803e6bc1fd170a3cda91a1be4b18e3f5" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" dependencies = [ "proc-macro2", "quote", @@ -5038,27 +5237,15 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.15" +version = "2.0.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a34fcf3e8b60f57e6a14301a2e916d323af98b0ea63c599441eec8558660c822" +checksum = "04361975b3f5e348b2189d8dc55bc942f278b2d482a6a0365de5bdd62d351567" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] -[[package]] -name = "synstructure" -version = "0.12.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f36bdaa60a83aca3921b5259d5400cbf5e90fc51931376a9bd4a0eb79aa7210f" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.107", - "unicode-xid", -] - [[package]] name = "system-deps" version = "5.0.0" @@ -5074,14 +5261,14 @@ dependencies = [ [[package]] name = "system-deps" -version = "6.0.3" +version = "6.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2955b1fe31e1fa2fbd1976b71cc69a606d7d4da16f6de3333d0c92d51419aeff" +checksum = "30c2de8a4d8f4b823d634affc9cd2a74ec98c53a756f317e529a48046cbf71f3" dependencies = [ - "cfg-expr 0.11.0", + "cfg-expr 0.15.4", "heck 0.4.1", "pkg-config", - "toml 0.5.11", + "toml 0.7.6", "version-compare 0.1.1", ] @@ -5091,7 +5278,7 @@ version = "0.15.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac8e6399427c8494f9849b58694754d7cc741293348a6836b6c8d2c5aa82d8e6" dependencies = [ - "bitflags", + "bitflags 1.3.2", "cairo-rs", "cc", "cocoa", @@ -5125,7 +5312,7 @@ dependencies = [ "scopeguard", "serde", "unicode-segmentation", - "uuid 1.3.0", + "uuid 1.4.1", "windows 0.39.0", "windows-implement", "x11-dl", @@ -5139,15 +5326,21 @@ checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" [[package]] name = "tar" -version = "0.4.38" +version = "0.4.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b55807c0344e1e6c04d7c965f5289c39a8d94ae23ed5c0b57aabac549f871c6" +checksum = "b16afcea1f22891c49a00c751c7b63b2233284064f11a200fc624137c51e2ddb" dependencies = [ "filetime", "libc", "xattr", ] +[[package]] +name = "target-lexicon" +version = "0.12.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d0e916b1148c8e263850e1ebcbd046f333e0683c724876bb0da63ea4373dc8a" + [[package]] name = "tauri" version = "1.2.3" @@ -5178,7 +5371,7 @@ dependencies = [ "raw-window-handle", "regex", "rfd", - "semver 1.0.16", + "semver 1.0.18", "serde", "serde_json", "serde_repr", @@ -5194,7 +5387,7 @@ dependencies = [ "time", "tokio", "url", - "uuid 1.3.0", + "uuid 1.4.1", "webkit2gtk", "webview2-com", "windows 0.39.0", @@ -5210,8 +5403,8 @@ dependencies = [ "anyhow", "cargo_toml", "heck 0.4.1", - "json-patch", - "semver 1.0.16", + "json-patch 0.2.7", + "semver 1.0.18", "serde_json", "tauri-utils", "winres", @@ -5226,20 +5419,20 @@ dependencies = [ "base64 0.13.1", "brotli", "ico", - "json-patch", + "json-patch 0.2.7", "plist", "png", "proc-macro2", "quote", "regex", - "semver 1.0.16", + "semver 1.0.18", "serde", "serde_json", - "sha2 0.10.6", + "sha2 0.10.7", "tauri-utils", "thiserror", "time", - "uuid 1.3.0", + "uuid 1.4.1", "walkdir", ] @@ -5252,16 +5445,16 @@ dependencies = [ "heck 0.4.1", "proc-macro2", "quote", - "syn 1.0.107", + "syn 1.0.109", "tauri-codegen", "tauri-utils", ] [[package]] name = "tauri-runtime" -version = "0.12.1" +version = "0.12.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c507d954d08ac8705d235bc70ec6975b9054fb95ff7823af72dbb04186596f3b" +checksum = "dc36898ad4acb6c381878acf903c320a36cf29b68b74f6e791d6045b6557128c" dependencies = [ "gtk", "http", @@ -5272,16 +5465,17 @@ dependencies = [ "serde_json", "tauri-utils", "thiserror", - "uuid 1.3.0", + "url", + "uuid 1.4.1", "webview2-com", "windows 0.39.0", ] [[package]] name = "tauri-runtime-wry" -version = "0.12.2" +version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36b1c5764a41a13176a4599b5b7bd0881bea7d94dfe45e1e755f789b98317e30" +checksum = "e2ebc22bc5566ba33310744fadd86709fa591ed163491b165855474523ac1aab" dependencies = [ "cocoa", "gtk", @@ -5290,7 +5484,8 @@ dependencies = [ "raw-window-handle", "tauri-runtime", "tauri-utils", - "uuid 1.3.0", + "url", + "uuid 1.4.1", "webkit2gtk", "webview2-com", "windows 0.39.0", @@ -5299,23 +5494,24 @@ dependencies = [ [[package]] name = "tauri-utils" -version = "1.2.1" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5abbc109a6eb45127956ffcc26ef0e875d160150ac16cfa45d26a6b2871686f1" +checksum = "03fc02bb6072bb397e1d473c6f76c953cda48b4a2d0cce605df284aa74a12e84" dependencies = [ "brotli", "ctor", + "dunce", "glob", "heck 0.4.1", "html5ever", "infer", - "json-patch", + "json-patch 1.0.0", "kuchiki", "memchr", "phf 0.10.1", "proc-macro2", "quote", - "semver 1.0.16", + "semver 1.0.18", "serde", "serde_json", "serde_with", @@ -5327,16 +5523,15 @@ dependencies = [ [[package]] name = "tempfile" -version = "3.3.0" +version = "3.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5cdb1ef4eaeeaddc8fbd371e5017057064af0911902ef36b39801f67cc6d79e4" +checksum = "dc02fddf48964c42031a0b3fe0428320ecf3a73c401040fc0096f97794310651" dependencies = [ "cfg-if", "fastrand", - "libc", - "redox_syscall", - "remove_dir_all", - "winapi", + "redox_syscall 0.3.5", + "rustix", + "windows-sys 0.48.0", ] [[package]] @@ -5347,7 +5542,7 @@ checksum = "3f0a7d05cf78524782337f8edd55cbc578d159a16ad4affe2135c92f7dbac7f0" dependencies = [ "bytes", "digest 0.10.7", - "ed25519 2.2.1", + "ed25519 2.2.2", "ed25519-consensus", "flex-error", "futures", @@ -5361,7 +5556,7 @@ dependencies = [ "serde_bytes", "serde_json", "serde_repr", - "sha2 0.10.6", + "sha2 0.10.7", "signature 2.1.0", "subtle 2.4.1", "subtle-encoding", @@ -5419,7 +5614,7 @@ dependencies = [ "hyper-rustls", "peg", "pin-project", - "semver 1.0.16", + "semver 1.0.18", "serde", "serde_bytes", "serde_json", @@ -5465,22 +5660,22 @@ checksum = "8eaa81235c7058867fa8c0e7314f33dcce9c215f535d1913822a2b3f5e289f3c" [[package]] name = "thiserror" -version = "1.0.38" +version = "1.0.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a9cd18aa97d5c45c6603caea1da6628790b37f7a34b6ca89522331c5180fed0" +checksum = "611040a08a0439f8248d1990b111c95baa9c704c805fa1f62104b39655fd7f90" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.38" +version = "1.0.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fb327af4685e4d03fa8cbcf1716380da910eeb2bb8be417e7f9fd3fb164f36f" +checksum = "090198534930841fab3a5d1bb637cde49e339654e606195f8d9c76eeb081dc96" dependencies = [ "proc-macro2", "quote", - "syn 1.0.107", + "syn 2.0.28", ] [[package]] @@ -5495,11 +5690,12 @@ dependencies = [ [[package]] name = "time" -version = "0.3.17" +version = "0.3.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a561bf4617eebd33bca6434b988f39ed798e527f51a1e797d0ee4f61c0a38376" +checksum = "b0fdd63d58b18d663fbdf70e049f00a22c8e42be082203be7f26589213cd75ea" dependencies = [ - "itoa 1.0.5", + "deranged", + "itoa 1.0.9", "libc", "num_threads", "serde", @@ -5509,15 +5705,15 @@ dependencies = [ [[package]] name = "time-core" -version = "0.1.0" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e153e1f1acaef8acc537e68b44906d2db6436e2b35ac2c6b42640fff91f00fd" +checksum = "7300fbefb4dadc1af235a9cef3737cea692a9d97e1b9cbcd4ebdae6f8868e6fb" [[package]] name = "time-macros" -version = "0.2.6" +version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d967f99f534ca7e495c575c62638eebc2898a8c84c119b89e250477bc4ba16b2" +checksum = "eb71511c991639bb078fd5bf97757e03914361c48100d52878b8e52b46fb92cd" dependencies = [ "time-core", ] @@ -5539,33 +5735,32 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.25.0" +version = "1.31.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8e00990ebabbe4c14c08aca901caed183ecd5c09562a12c824bb53d3c3fd3af" +checksum = "40de3a2ba249dcb097e01be5e67a5ff53cf250397715a071a81543e8a832a920" dependencies = [ - "autocfg", + "backtrace", "bytes", "libc", - "memchr", "mio", "num_cpus", "parking_lot", "pin-project-lite", "signal-hook-registry", - "socket2", + "socket2 0.5.3", "tokio-macros", - "windows-sys 0.42.0", + "windows-sys 0.48.0", ] [[package]] name = "tokio-macros" -version = "1.8.2" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d266c00fde287f55d3f1c3e96c500c362a2b8c695076ec180f27918820bc6df8" +checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e" dependencies = [ "proc-macro2", "quote", - "syn 1.0.107", + "syn 2.0.28", ] [[package]] @@ -5591,9 +5786,9 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.7" +version = "0.7.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5427d89453009325de0d8f342c9490009f76e999cb7672d77e46267448f7e6b2" +checksum = "806fe8c2c87eccc8b3267cbae29ed3ab2d0bd37fca70ab622e46aaa9375ddb7d" dependencies = [ "bytes", "futures-core", @@ -5614,52 +5809,35 @@ dependencies = [ [[package]] name = "toml" -version = "0.7.4" +version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6135d499e69981f9ff0ef2167955a5333c35e36f6937d382974566b3d5b94ec" +checksum = "c17e963a819c331dcacd7ab957d80bc2b9a9c1e71c804826d2f283dd65306542" dependencies = [ "serde", "serde_spanned", - "toml_datetime 0.6.2", - "toml_edit 0.19.10", + "toml_datetime", + "toml_edit", ] [[package]] name = "toml_datetime" -version = "0.5.1" +version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4553f467ac8e3d374bc9a177a26801e5d0f9b211aa1673fb137a403afd1c9cf5" - -[[package]] -name = "toml_datetime" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a76a9312f5ba4c2dec6b9161fdf25d87ad8a09256ccea5a556fef03c706a10f" +checksum = "7cda73e2f1397b1262d6dfdcef8aafae14d1de7748d66822d3bfeeb6d03e5e4b" dependencies = [ "serde", ] [[package]] name = "toml_edit" -version = "0.18.1" +version = "0.19.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56c59d8dd7d0dcbc6428bf7aa2f0e823e26e43b3c9aca15bbc9475d23e5fa12b" +checksum = "f8123f27e969974a3dfba720fdb560be359f57b44302d280ba72e76a74480e8a" dependencies = [ - "indexmap", - "nom8", - "toml_datetime 0.5.1", -] - -[[package]] -name = "toml_edit" -version = "0.19.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2380d56e8670370eee6566b0bfd4265f65b3f432e8c6d85623f728d4fa31f739" -dependencies = [ - "indexmap", + "indexmap 2.0.0", "serde", "serde_spanned", - "toml_datetime 0.6.2", + "toml_datetime", "winnow", ] @@ -5683,20 +5861,20 @@ dependencies = [ [[package]] name = "tracing-attributes" -version = "0.1.23" +version = "0.1.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4017f8f45139870ca7e672686113917c71c7a6e02d4924eda67186083c03081a" +checksum = "5f4f31f56159e98206da9efd823404b79b6ef3143b4a7ab76e67b1751b25a4ab" dependencies = [ "proc-macro2", "quote", - "syn 1.0.107", + "syn 2.0.28", ] [[package]] name = "tracing-core" -version = "0.1.30" +version = "0.1.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24eb03ba0eab1fd845050058ce5e616558e8f8d8fca633e6b163fe25c797213a" +checksum = "0955b8137a1df6f1a2e9a37d8a6656291ff0297c1a97c24e0d8425fe2312f79a" dependencies = [ "once_cell", "valuable", @@ -5715,9 +5893,9 @@ dependencies = [ [[package]] name = "tracing-subscriber" -version = "0.3.16" +version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6176eae26dd70d0c919749377897b54a9276bd7061339665dd68777926b5a70" +checksum = "30a651bc37f915e81f087d86e62a18eec5f79550c7faff886f7090b4ea757c77" dependencies = [ "matchers", "nu-ansi-term", @@ -5740,6 +5918,15 @@ dependencies = [ "serde_json", ] +[[package]] +name = "treediff" +version = "4.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52984d277bdf2a751072b5df30ec0377febdb02f7696d64c2d7d54630bac4303" +dependencies = [ + "serde_json", +] + [[package]] name = "try-lock" version = "0.2.4" @@ -5765,7 +5952,7 @@ dependencies = [ "Inflector", "proc-macro2", "quote", - "syn 1.0.107", + "syn 1.0.109", "termcolor", ] @@ -5777,21 +5964,21 @@ checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba" [[package]] name = "ucd-trie" -version = "0.1.5" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e79c4d996edb816c91e4308506774452e55e95c3c9de07b6729e17e15a5ef81" +checksum = "ed646292ffc8188ef8ea4d1e0e0150fb15a5c2e12ad9b8fc191ae7a8a7f3c4b9" [[package]] name = "unicode-bidi" -version = "0.3.10" +version = "0.3.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d54675592c1dbefd78cbd98db9bacd89886e1ca50692a0692baefffdeb92dd58" +checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460" [[package]] name = "unicode-ident" -version = "1.0.6" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84a22b9f218b40614adcb3f4ff08b703773ad44fa9423e4e0d346d5db86e4ebc" +checksum = "301abaae475aa91687eb82514b328ab47a211a533026cb25fc3e519b86adfc3c" [[package]] name = "unicode-normalization" @@ -5808,17 +5995,11 @@ version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36" -[[package]] -name = "unicode-xid" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c" - [[package]] name = "universal-hash" -version = "0.5.0" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d3160b73c9a19f7e2939a2fdad446c57c1bbbbf4d919d3213ff1267a580d8b5" +checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea" dependencies = [ "crypto-common", "subtle 2.4.1", @@ -5832,9 +6013,9 @@ checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" [[package]] name = "url" -version = "2.3.0" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22fe195a4f217c25b25cb5058ced57059824a678474874038dc88d211bf508d3" +checksum = "50bff7831e19200a85b17131d085c25d7811bc4e186efdaf54bbd132994a88cb" dependencies = [ "form_urlencoded", "idna", @@ -5848,6 +6029,12 @@ version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" +[[package]] +name = "utf8parse" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" + [[package]] name = "uuid" version = "0.8.2" @@ -5856,9 +6043,9 @@ checksum = "bc5cf98d8186244414c848017f0e2676b3fcb46807f6668a97dfe67359a3c4b7" [[package]] name = "uuid" -version = "1.3.0" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1674845326ee10d37ca60470760d4288a6f80f304007d92e5c53bab78c9cfd79" +checksum = "79daa5ed5740825c40b389c5e50312b9c86df53fccd33f281df655642b43869d" dependencies = [ "getrandom 0.2.10", ] @@ -5886,7 +6073,7 @@ dependencies = [ "enum-iterator", "getset", "git2", - "rustc_version 0.4.0", + "rustc_version", "rustversion", "thiserror", "time", @@ -5912,22 +6099,20 @@ checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" [[package]] name = "walkdir" -version = "2.3.2" +version = "2.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "808cf2735cd4b6866113f648b791c6adc5714537bc222d9347bb203386ffda56" +checksum = "36df944cda56c7d8d8b7496af378e6b16de9284591917d307c9b4d313c44e698" dependencies = [ "same-file", - "winapi", "winapi-util", ] [[package]] name = "want" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ce8a968cb1cd110d136ff8b819a556d6fb6d919363c61534f6860c7eb172ba0" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" dependencies = [ - "log", "try-lock", ] @@ -5945,9 +6130,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.83" +version = "0.2.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eaf9f5aceeec8be17c128b2e93e031fb8a4d469bb9c4ae2d7dc1888b26887268" +checksum = "7706a72ab36d8cb1f80ffbf0e071533974a60d0a308d01a5d0375bf60499a342" dependencies = [ "cfg-if", "wasm-bindgen-macro", @@ -5955,24 +6140,24 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.83" +version = "0.2.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c8ffb332579b0557b52d268b91feab8df3615f265d5270fec2a8c95b17c1142" +checksum = "5ef2b6d3c510e9625e5fe6f509ab07d66a760f0885d858736483c32ed7809abd" dependencies = [ "bumpalo", "log", "once_cell", "proc-macro2", "quote", - "syn 1.0.107", + "syn 2.0.28", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-futures" -version = "0.4.33" +version = "0.4.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23639446165ca5a5de86ae1d8896b737ae80319560fbaa4c2887b7da6e7ebd7d" +checksum = "c02dbc21516f9f1f04f187958890d7e6026df8d16540b7ad9492bc34a67cea03" dependencies = [ "cfg-if", "js-sys", @@ -5982,9 +6167,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.83" +version = "0.2.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "052be0f94026e6cbc75cdefc9bae13fd6052cdcaf532fa6c45e7ae33a1e6c810" +checksum = "dee495e55982a3bd48105a7b947fd2a9b4a8ae3010041b9e0faab3f9cd028f1d" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -5992,28 +6177,28 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.83" +version = "0.2.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07bc0c051dc5f23e307b13285f9d75df86bfdf816c5721e573dec1f9b8aa193c" +checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b" dependencies = [ "proc-macro2", "quote", - "syn 1.0.107", + "syn 2.0.28", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.83" +version = "0.2.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c38c045535d93ec4f0b4defec448e4291638ee608530863b1e2ba115d4fff7f" +checksum = "ca6ad05a4870b2bf5fe995117d3728437bd27d7cd5f06f13c17443ef369775a1" [[package]] name = "web-sys" -version = "0.3.60" +version = "0.3.64" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bcda906d8be16e728fd5adc5b729afad4e444e106ab28cd1c7256e54fa61510f" +checksum = "9b85cbef8c220a6abc02aefd892dfc0fc23afb1c6a426316ec33253a3877249b" dependencies = [ "js-sys", "wasm-bindgen", @@ -6025,7 +6210,7 @@ version = "0.18.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b8f859735e4a452aeb28c6c56a852967a8a76c8eb1cc32dbf931ad28a13d6370" dependencies = [ - "bitflags", + "bitflags 1.3.2", "cairo-rs", "gdk", "gdk-sys", @@ -6050,7 +6235,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4d76ca6ecc47aeba01ec61e480139dda143796abcae6f83bcddf50d6b5b1dcf3" dependencies = [ "atk-sys", - "bitflags", + "bitflags 1.3.2", "cairo-sys-rs", "gdk-pixbuf-sys", "gdk-sys", @@ -6063,7 +6248,7 @@ dependencies = [ "pango-sys", "pkg-config", "soup2-sys", - "system-deps 6.0.3", + "system-deps 6.1.1", ] [[package]] @@ -6105,7 +6290,7 @@ checksum = "eaebe196c01691db62e9e4ca52c5ef1e4fd837dcae27dae3ada599b5a8fd05ac" dependencies = [ "proc-macro2", "quote", - "syn 1.0.107", + "syn 1.0.109", ] [[package]] @@ -6181,6 +6366,15 @@ dependencies = [ "windows_x86_64_msvc 0.39.0", ] +[[package]] +name = "windows" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e686886bc078bc1b0b600cac0147aadb815089b6e4da64016cbd754b6342700f" +dependencies = [ + "windows-targets", +] + [[package]] name = "windows-bindgen" version = "0.39.0" @@ -6197,7 +6391,7 @@ version = "0.39.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ba01f98f509cb5dc05f4e5fc95e535f78260f15fea8fe1a8abdd08f774f1cee7" dependencies = [ - "syn 1.0.107", + "syn 1.0.109", "windows-tokens", ] @@ -6213,22 +6407,13 @@ version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" dependencies = [ - "windows_aarch64_gnullvm 0.42.1", - "windows_aarch64_msvc 0.42.1", - "windows_i686_gnu 0.42.1", - "windows_i686_msvc 0.42.1", - "windows_x86_64_gnu 0.42.1", - "windows_x86_64_gnullvm 0.42.1", - "windows_x86_64_msvc 0.42.1", -] - -[[package]] -name = "windows-sys" -version = "0.45.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" -dependencies = [ - "windows-targets 0.42.1", + "windows_aarch64_gnullvm 0.42.2", + "windows_aarch64_msvc 0.42.2", + "windows_i686_gnu 0.42.2", + "windows_i686_msvc 0.42.2", + "windows_x86_64_gnu 0.42.2", + "windows_x86_64_gnullvm 0.42.2", + "windows_x86_64_msvc 0.42.2", ] [[package]] @@ -6237,29 +6422,14 @@ version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" dependencies = [ - "windows-targets 0.48.0", + "windows-targets", ] [[package]] name = "windows-targets" -version = "0.42.1" +version = "0.48.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e2522491fbfcd58cc84d47aeb2958948c4b8982e9a2d8a2a35bbaed431390e7" -dependencies = [ - "windows_aarch64_gnullvm 0.42.1", - "windows_aarch64_msvc 0.42.1", - "windows_i686_gnu 0.42.1", - "windows_i686_msvc 0.42.1", - "windows_x86_64_gnu 0.42.1", - "windows_x86_64_gnullvm 0.42.1", - "windows_x86_64_msvc 0.42.1", -] - -[[package]] -name = "windows-targets" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b1eb6f0cd7c80c79759c929114ef071b87354ce476d9d94271031c0497adfd5" +checksum = "05d4b17490f70499f20b9e791dcf6a299785ce8af4d709018206dc5b4953e95f" dependencies = [ "windows_aarch64_gnullvm 0.48.0", "windows_aarch64_msvc 0.48.0", @@ -6278,9 +6448,9 @@ checksum = "f838de2fe15fe6bac988e74b798f26499a8b21a9d97edec321e79b28d1d7f597" [[package]] name = "windows_aarch64_gnullvm" -version = "0.42.1" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c9864e83243fdec7fc9c5444389dcbbfd258f745e7853198f365e3c4968a608" +checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" [[package]] name = "windows_aarch64_gnullvm" @@ -6302,9 +6472,9 @@ checksum = "ec7711666096bd4096ffa835238905bb33fb87267910e154b18b44eaabb340f2" [[package]] name = "windows_aarch64_msvc" -version = "0.42.1" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c8b1b673ffc16c47a9ff48570a9d85e25d265735c503681332589af6253c6c7" +checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" [[package]] name = "windows_aarch64_msvc" @@ -6326,9 +6496,9 @@ checksum = "763fc57100a5f7042e3057e7e8d9bdd7860d330070251a73d003563a3bb49e1b" [[package]] name = "windows_i686_gnu" -version = "0.42.1" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de3887528ad530ba7bdbb1faa8275ec7a1155a45ffa57c37993960277145d640" +checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" [[package]] name = "windows_i686_gnu" @@ -6350,9 +6520,9 @@ checksum = "7bc7cbfe58828921e10a9f446fcaaf649204dcfe6c1ddd712c5eebae6bda1106" [[package]] name = "windows_i686_msvc" -version = "0.42.1" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf4d1122317eddd6ff351aa852118a2418ad4214e6613a50e0191f7004372605" +checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" [[package]] name = "windows_i686_msvc" @@ -6374,9 +6544,9 @@ checksum = "6868c165637d653ae1e8dc4d82c25d4f97dd6605eaa8d784b5c6e0ab2a252b65" [[package]] name = "windows_x86_64_gnu" -version = "0.42.1" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1040f221285e17ebccbc2591ffdc2d44ee1f9186324dd3e84e99ac68d699c45" +checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" [[package]] name = "windows_x86_64_gnu" @@ -6386,9 +6556,9 @@ checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1" [[package]] name = "windows_x86_64_gnullvm" -version = "0.42.1" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "628bfdf232daa22b0d64fdb62b09fcc36bb01f05a3939e20ab73aaf9470d0463" +checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" [[package]] name = "windows_x86_64_gnullvm" @@ -6410,9 +6580,9 @@ checksum = "5e4d40883ae9cae962787ca76ba76390ffa29214667a111db9e0a1ad8377e809" [[package]] name = "windows_x86_64_msvc" -version = "0.42.1" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "447660ad36a13288b1db4d4248e857b510e8c3a225c822ba4fb748c0aafecffd" +checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" [[package]] name = "windows_x86_64_msvc" @@ -6422,9 +6592,9 @@ checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" [[package]] name = "winnow" -version = "0.4.6" +version = "0.5.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61de7bac303dc551fe038e2b3cef0f571087a47571ea6e79a87692ac99b99699" +checksum = "5504cc7644f4b593cbc05c4a55bf9bd4e94b867c3c0bd440934174d50482427d" dependencies = [ "memchr", ] @@ -6473,7 +6643,7 @@ dependencies = [ "once_cell", "serde", "serde_json", - "sha2 0.10.6", + "sha2 0.10.7", "soup2", "tao", "thiserror", @@ -6528,9 +6698,9 @@ dependencies = [ [[package]] name = "xattr" -version = "0.2.3" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d1526bbe5aaeb5eb06885f4d987bcdfa5e23187055de9b83fe00156a821fabc" +checksum = "f4686009f71ff3e5c4dbcf1a282d0a44db3f021ba69350cd42086b3e5f1c6985" dependencies = [ "libc", ] @@ -6547,21 +6717,20 @@ dependencies = [ [[package]] name = "zeroize_derive" -version = "1.3.3" +version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44bf07cb3e50ea2003396695d58bf46bc9887a1f362260446fad6bc4e79bd36c" +checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn 1.0.107", - "synstructure", + "syn 2.0.28", ] [[package]] name = "zip" -version = "0.6.4" +version = "0.6.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0445d0fbc924bb93539b4316c11afb121ea39296f99a3c4c9edad09e3658cdef" +checksum = "760394e246e4c28189f19d488c058bf16f564016aefac5d32bb1f3b51d5e9261" dependencies = [ "byteorder", "crc32fast", diff --git a/nym-wallet/src-tauri/Cargo.toml b/nym-wallet/src-tauri/Cargo.toml index 8c96f181f1..0e9f178f6b 100644 --- a/nym-wallet/src-tauri/Cargo.toml +++ b/nym-wallet/src-tauri/Cargo.toml @@ -32,7 +32,7 @@ itertools = "0.10" log = { version = "0.4", features = ["serde"] } once_cell = "1.7.2" pretty_env_logger = "0.4" -reqwest = {version = "0.11.9", features = ["json"] } +reqwest = {version = "0.11.18", features = ["json"] } serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" serde_repr = "0.1" diff --git a/sdk/lib/socks5-listener/Cargo.lock b/sdk/lib/socks5-listener/Cargo.lock deleted file mode 100644 index 3fc8b34151..0000000000 --- a/sdk/lib/socks5-listener/Cargo.lock +++ /dev/null @@ -1,5331 +0,0 @@ -# This file is automatically @generated by Cargo. -# It is not intended for manual editing. -version = 3 - -[[package]] -name = "adler" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" - -[[package]] -name = "aead" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0" -dependencies = [ - "crypto-common", - "generic-array 0.14.7", -] - -[[package]] -name = "aes" -version = "0.7.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e8b47f52ea9bae42228d07ec09eb676433d7c4ed1ebdf0f1d1c29ed446f1ab8" -dependencies = [ - "cfg-if", - "cipher 0.3.0", - "cpufeatures", - "ctr 0.8.0", - "opaque-debug 0.3.0", -] - -[[package]] -name = "aes" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "433cfd6710c9986c576a25ca913c39d66a6474107b406f34f91d4a8923395241" -dependencies = [ - "cfg-if", - "cipher 0.4.4", - "cpufeatures", -] - -[[package]] -name = "ahash" -version = "0.7.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47" -dependencies = [ - "getrandom 0.2.9", - "once_cell", - "version_check", -] - -[[package]] -name = "aho-corasick" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67fc08ce920c31afb70f013dcce1bfc3a3195de6a228474e45e1f145b36f8d04" -dependencies = [ - "memchr", -] - -[[package]] -name = "android_log-sys" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "27f0fc03f560e1aebde41c2398b691cb98b5ea5996a6184a7a67bbbb77448969" - -[[package]] -name = "android_logger" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fa490e751f3878eb9accb9f18988eca52c2337ce000a8bf31ef50d4c723ca9e" -dependencies = [ - "android_log-sys", - "env_logger 0.10.0", - "log", - "once_cell", -] - -[[package]] -name = "anstream" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ca84f3628370c59db74ee214b3263d58f9aadd9b4fe7e711fd87dc452b7f163" -dependencies = [ - "anstyle", - "anstyle-parse", - "anstyle-query", - "anstyle-wincon", - "colorchoice", - "is-terminal", - "utf8parse", -] - -[[package]] -name = "anstyle" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41ed9a86bf92ae6580e0a31281f65a1b1d867c0cc68d5346e2ae128dddfa6a7d" - -[[package]] -name = "anstyle-parse" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e765fd216e48e067936442276d1d57399e37bce53c264d6fefbe298080cb57ee" -dependencies = [ - "utf8parse", -] - -[[package]] -name = "anstyle-query" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ca11d4be1bab0c8bc8734a9aa7bf4ee8316d462a08c6ac5052f888fef5b494b" -dependencies = [ - "windows-sys 0.48.0", -] - -[[package]] -name = "anstyle-wincon" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "180abfa45703aebe0093f79badacc01b8fd4ea2e35118747e5811127f926e188" -dependencies = [ - "anstyle", - "windows-sys 0.48.0", -] - -[[package]] -name = "anyhow" -version = "1.0.71" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c7d0618f0e0b7e8ff11427422b64564d5fb0be1940354bfe2e0529b18a9d9b8" - -[[package]] -name = "arrayref" -version = "0.3.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b4930d2cb77ce62f89ee5d5289b4ac049559b1c45539271f5ed4fdc7db34545" - -[[package]] -name = "arrayvec" -version = "0.7.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8da52d66c7071e2e3fa2a1e5c6d088fec47b593032b254f5e980de8ea54454d6" - -[[package]] -name = "async-trait" -version = "0.1.68" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9ccdd8f2a161be9bd5c023df56f1b2a0bd1d83872ae53b71a84a12c9bf6e842" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.15", -] - -[[package]] -name = "atoi" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "616896e05fc0e2649463a93a15183c6a16bf03413a7af88ef1285ddedfa9cda5" -dependencies = [ - "num-traits", -] - -[[package]] -name = "atoi" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7c57d12312ff59c811c0643f4d80830505833c9ffaebd193d819392b265be8e" -dependencies = [ - "num-traits", -] - -[[package]] -name = "atty" -version = "0.2.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" -dependencies = [ - "hermit-abi 0.1.19", - "libc", - "winapi", -] - -[[package]] -name = "autocfg" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" - -[[package]] -name = "base16ct" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "349a06037c7bf932dd7e7d1f653678b2038b9ad46a74102f1fc7bd7872678cce" - -[[package]] -name = "base64" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" - -[[package]] -name = "base64" -version = "0.21.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4a4ddaa51a5bc52a6948f74c06d20aaaddb71924eab79b8c97a8c556e942d6a" - -[[package]] -name = "base64ct" -version = "1.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" - -[[package]] -name = "bip32" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "873faa4363bfc54c36a48321da034c92a0645a363eed34d948683ffc1706e37f" -dependencies = [ - "bs58", - "hmac 0.11.0", - "k256", - "once_cell", - "pbkdf2", - "rand_core 0.6.4", - "ripemd160", - "sha2 0.9.9", - "subtle 2.4.1", - "zeroize", -] - -[[package]] -name = "bip39" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93f2635620bf0b9d4576eb7bb9a38a55df78bd1205d26fa994b25911a69f212f" -dependencies = [ - "bitcoin_hashes", - "rand 0.8.5", - "rand_core 0.6.4", - "serde", - "unicode-normalization", - "zeroize", -] - -[[package]] -name = "bitcoin_hashes" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90064b8dee6815a6470d60bad07bbbaee885c0e12d04177138fa3291a01b7bc4" - -[[package]] -name = "bitflags" -version = "1.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" - -[[package]] -name = "bitvec" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c" -dependencies = [ - "funty", - "radium", - "tap", - "wyz", -] - -[[package]] -name = "blake2" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94cb07b0da6a73955f8fb85d24c466778e70cda767a568229b104f0264089330" -dependencies = [ - "byte-tools", - "crypto-mac 0.7.0", - "digest 0.8.1", - "opaque-debug 0.2.3", -] - -[[package]] -name = "blake3" -version = "1.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42ae2468a89544a466886840aa467a25b766499f4f04bf7d9fcd10ecee9fccef" -dependencies = [ - "arrayref", - "arrayvec", - "cc", - "cfg-if", - "constant_time_eq", - "digest 0.10.6", -] - -[[package]] -name = "block-buffer" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" -dependencies = [ - "block-padding", - "generic-array 0.14.7", -] - -[[package]] -name = "block-buffer" -version = "0.10.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" -dependencies = [ - "generic-array 0.14.7", -] - -[[package]] -name = "block-padding" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d696c370c750c948ada61c69a0ee2cbbb9c50b1019ddb86d9317157a99c2cae" - -[[package]] -name = "bls12_381" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54757888b09a69be70b5ec303e382a74227392086ba808cb01eeca29233a2397" -dependencies = [ - "digest 0.9.0", - "ff 0.10.1", - "group 0.10.0", - "pairing 0.20.0", - "rand_core 0.6.4", - "subtle 2.4.1", -] - -[[package]] -name = "bls12_381" -version = "0.6.0" -source = "git+https://github.com/jstuczyn/bls12_381?branch=gt-serialisation#10fb6f700bfda17c8475af3bfd31e3fec15f2278" -dependencies = [ - "digest 0.9.0", - "ff 0.11.1", - "group 0.11.0", - "pairing 0.21.0", - "rand_core 0.6.4", - "subtle 2.4.1", - "zeroize", -] - -[[package]] -name = "bs58" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "771fe0050b883fcc3ea2359b1a96bcfbc090b7116eae7c3c512c7a083fdf23d3" -dependencies = [ - "sha2 0.9.9", -] - -[[package]] -name = "bumpalo" -version = "3.12.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c6ed94e98ecff0c12dd1b04c15ec0d7d9458ca8fe806cea6f12954efe74c63b" - -[[package]] -name = "byte-tools" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3b5ca7a04898ad4bcd41c90c5285445ff5b791899bb1b0abdd2a2aa791211d7" - -[[package]] -name = "byteorder" -version = "1.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" - -[[package]] -name = "bytes" -version = "1.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be" - -[[package]] -name = "cc" -version = "1.0.79" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f" -dependencies = [ - "jobserver", -] - -[[package]] -name = "cesu8" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" - -[[package]] -name = "cfg-if" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" - -[[package]] -name = "chacha" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ddf3c081b5fba1e5615640aae998e0fbd10c24cbd897ee39ed754a77601a4862" -dependencies = [ - "byteorder", - "keystream", -] - -[[package]] -name = "chacha20" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3613f74bd2eac03dad61bd53dbe620703d4371614fe0bc3b9f04dd36fe4e818" -dependencies = [ - "cfg-if", - "cipher 0.4.4", - "cpufeatures", -] - -[[package]] -name = "chacha20poly1305" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10cd79432192d1c0f4e1a0fef9527696cc039165d729fb41b3f4f4f354c2dc35" -dependencies = [ - "aead", - "chacha20", - "cipher 0.4.4", - "poly1305", - "zeroize", -] - -[[package]] -name = "cipher" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ee52072ec15386f770805afd189a01c8841be8696bed250fa2f13c4c0d6dfb7" -dependencies = [ - "generic-array 0.14.7", -] - -[[package]] -name = "cipher" -version = "0.4.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" -dependencies = [ - "crypto-common", - "inout", - "zeroize", -] - -[[package]] -name = "clap" -version = "4.2.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34d21f9bf1b425d2968943631ec91202fe5e837264063503708b83013f8fc938" -dependencies = [ - "clap_builder", - "clap_derive", - "once_cell", -] - -[[package]] -name = "clap_builder" -version = "4.2.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "914c8c79fb560f238ef6429439a30023c862f7a28e688c58f7203f12b29970bd" -dependencies = [ - "anstream", - "anstyle", - "bitflags", - "clap_lex", - "strsim", -] - -[[package]] -name = "clap_complete" -version = "4.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36774babb166352bb4f7b9cb16f781ffa3439d2a8f12cd31bea85a38c888fea3" -dependencies = [ - "clap", -] - -[[package]] -name = "clap_complete_fig" -version = "4.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3af28956330989baa428ed4d3471b853715d445c62de21b67292e22cf8a41fa" -dependencies = [ - "clap", - "clap_complete", -] - -[[package]] -name = "clap_derive" -version = "4.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f9644cd56d6b87dbe899ef8b053e331c0637664e9e21a33dfcdc36093f5c5c4" -dependencies = [ - "heck", - "proc-macro2", - "quote", - "syn 2.0.15", -] - -[[package]] -name = "clap_lex" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a2dd5a6fe8c6e3502f568a6353e5273bbb15193ad9a89e457b9970798efbea1" - -[[package]] -name = "colorchoice" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" - -[[package]] -name = "colored" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3616f750b84d8f0de8a58bda93e08e2a81ad3f523089b05f1dffecab48c6cbd" -dependencies = [ - "atty", - "lazy_static", - "winapi", -] - -[[package]] -name = "combine" -version = "4.6.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35ed6e9d84f0b51a7f52daf1c7d71dd136fd7a3f41a8462b8cdb8c78d920fad4" -dependencies = [ - "bytes", - "memchr", -] - -[[package]] -name = "const-oid" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4c78c047431fee22c1a7bb92e00ad095a02a983affe4d8a72e2a2c62c1b94f3" - -[[package]] -name = "constant_time_eq" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13418e745008f7349ec7e449155f419a61b92b58a99cc3616942b926825ec76b" - -[[package]] -name = "core-foundation" -version = "0.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "194a7a9e6de53fa55116934067c844d9d749312f75c6f6d0980e8c252f8c2146" -dependencies = [ - "core-foundation-sys", - "libc", -] - -[[package]] -name = "core-foundation-sys" -version = "0.8.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa" - -[[package]] -name = "cosmos-sdk-proto" -version = "0.12.3" -source = "git+https://github.com/neacsu/cosmos-rust?branch=neacsu/feegrant_support#f63ded63ec13e753ebe8bdafe9dc503df265d67d" -dependencies = [ - "prost", - "prost-types", - "tendermint-proto", -] - -[[package]] -name = "cosmrs" -version = "0.7.1" -source = "git+https://github.com/neacsu/cosmos-rust?branch=neacsu/feegrant_support#f63ded63ec13e753ebe8bdafe9dc503df265d67d" -dependencies = [ - "bip32", - "cosmos-sdk-proto", - "ecdsa", - "eyre", - "getrandom 0.2.9", - "k256", - "prost", - "prost-types", - "rand_core 0.6.4", - "serde", - "serde_json", - "subtle-encoding", - "tendermint", - "tendermint-rpc", - "thiserror", -] - -[[package]] -name = "cosmwasm-crypto" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "970d1d705862179b5d7a233ae36f02f21c4ec1b8075fe60c77fc5b43471331fa" -dependencies = [ - "digest 0.9.0", - "ed25519-zebra", - "k256", - "rand_core 0.6.4", - "thiserror", -] - -[[package]] -name = "cosmwasm-derive" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b36e527620a2a3e00e46b6e731ab6c9b68d11069c986f7d7be8eba79ef081a4" -dependencies = [ - "syn 1.0.109", -] - -[[package]] -name = "cosmwasm-std" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "875994993c2082a6fcd406937bf0fca21c349e4a624f3810253a14fa83a3a195" -dependencies = [ - "base64 0.13.1", - "cosmwasm-crypto", - "cosmwasm-derive", - "forward_ref", - "schemars", - "serde", - "serde-json-wasm", - "thiserror", - "uint", -] - -[[package]] -name = "cpufeatures" -version = "0.2.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e4c1eaa2012c47becbbad2ab175484c2a84d1185b566fb2cc5b8707343dfe58" -dependencies = [ - "libc", -] - -[[package]] -name = "crc" -version = "2.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49fc9a695bca7f35f5f4c15cddc84415f66a74ea78eef08e90c5024f2b540e23" -dependencies = [ - "crc-catalog 1.1.1", -] - -[[package]] -name = "crc" -version = "3.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86ec7a15cbe22e59248fc7eadb1907dab5ba09372595da4d73dd805ed4417dfe" -dependencies = [ - "crc-catalog 2.2.0", -] - -[[package]] -name = "crc-catalog" -version = "1.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ccaeedb56da03b09f598226e25e80088cb4cd25f316e6e4df7d695f0feeb1403" - -[[package]] -name = "crc-catalog" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9cace84e55f07e7301bae1c519df89cdad8cc3cd868413d3fdbdeca9ff3db484" - -[[package]] -name = "crc32fast" -version = "1.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" -dependencies = [ - "cfg-if", -] - -[[package]] -name = "crossbeam-channel" -version = "0.5.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a33c2bf77f2df06183c3aa30d1e96c0695a313d4f9c453cc3762a6db39f99200" -dependencies = [ - "cfg-if", - "crossbeam-utils", -] - -[[package]] -name = "crossbeam-deque" -version = "0.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce6fd6f855243022dcecf8702fef0c297d4338e226845fe067f6341ad9fa0cef" -dependencies = [ - "cfg-if", - "crossbeam-epoch", - "crossbeam-utils", -] - -[[package]] -name = "crossbeam-epoch" -version = "0.9.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46bd5f3f85273295a9d14aedfb86f6aadbff6d8f5295c4a9edb08e819dcf5695" -dependencies = [ - "autocfg", - "cfg-if", - "crossbeam-utils", - "memoffset", - "scopeguard", -] - -[[package]] -name = "crossbeam-queue" -version = "0.3.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1cfb3ea8a53f37c40dea2c7bedcbd88bdfae54f5e2175d6ecaff1c988353add" -dependencies = [ - "cfg-if", - "crossbeam-utils", -] - -[[package]] -name = "crossbeam-utils" -version = "0.8.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c063cd8cc95f5c377ed0d4b49a4b21f632396ff690e8470c29b3359b346984b" -dependencies = [ - "cfg-if", -] - -[[package]] -name = "crunchy" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" - -[[package]] -name = "crypto-bigint" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03c6a1d5fa1de37e071642dfa44ec552ca5b299adb128fab16138e24b548fd21" -dependencies = [ - "generic-array 0.14.7", - "rand_core 0.6.4", - "subtle 2.4.1", - "zeroize", -] - -[[package]] -name = "crypto-common" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" -dependencies = [ - "generic-array 0.14.7", - "rand_core 0.6.4", - "typenum", -] - -[[package]] -name = "crypto-mac" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4434400df11d95d556bac068ddfedd482915eb18fe8bea89bc80b6e4b1c179e5" -dependencies = [ - "generic-array 0.12.4", - "subtle 1.0.0", -] - -[[package]] -name = "crypto-mac" -version = "0.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1d1a86f49236c215f271d40892d5fc950490551400b02ef360692c29815c714" -dependencies = [ - "generic-array 0.14.7", - "subtle 2.4.1", -] - -[[package]] -name = "ct-logs" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1a816186fa68d9e426e3cb4ae4dff1fcd8e4a2c34b781bf7a822574a0d0aac8" -dependencies = [ - "sct 0.6.1", -] - -[[package]] -name = "ctor" -version = "0.1.26" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d2301688392eb071b0bf1a37be05c469d3cc4dbbd95df672fe28ab021e6a096" -dependencies = [ - "quote", - "syn 1.0.109", -] - -[[package]] -name = "ctr" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "049bb91fb4aaf0e3c7efa6cd5ef877dbbbd15b39dad06d9948de4ec8a75761ea" -dependencies = [ - "cipher 0.3.0", -] - -[[package]] -name = "ctr" -version = "0.9.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0369ee1ad671834580515889b80f2ea915f23b8be8d0daa4bbaf2ac5c7590835" -dependencies = [ - "cipher 0.4.4", -] - -[[package]] -name = "curve25519-dalek" -version = "3.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b9fdf9972b2bd6af2d913799d9ebc165ea4d2e65878e329d9c6b372c4491b61" -dependencies = [ - "byteorder", - "digest 0.9.0", - "rand_core 0.5.1", - "subtle 2.4.1", - "zeroize", -] - -[[package]] -name = "cw-storage-plus" -version = "0.13.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "648b1507290bbc03a8d88463d7cd9b04b1fa0155e5eef366c4fa052b9caaac7a" -dependencies = [ - "cosmwasm-std", - "schemars", - "serde", -] - -[[package]] -name = "cw-utils" -version = "0.13.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9dbaecb78c8e8abfd6b4258c7f4fbeb5c49a5e45ee4d910d3240ee8e1d714e1b" -dependencies = [ - "cosmwasm-std", - "schemars", - "serde", - "thiserror", -] - -[[package]] -name = "cw2" -version = "0.13.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04cf4639517490dd36b333bbd6c4fbd92e325fd0acf4683b41753bc5eb63bfc1" -dependencies = [ - "cosmwasm-std", - "cw-storage-plus", - "schemars", - "serde", -] - -[[package]] -name = "cw3" -version = "0.13.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe19462a7f644ba60c19d3443cb90d00c50d9b6b3b0a3a7fca93df8261af979b" -dependencies = [ - "cosmwasm-std", - "cw-utils", - "schemars", - "serde", -] - -[[package]] -name = "cw4" -version = "0.13.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0acc3549d5ce11c6901b3a676f2e2628684722197054d97cd0101ea174ed5cbd" -dependencies = [ - "cosmwasm-std", - "cw-storage-plus", - "schemars", - "serde", -] - -[[package]] -name = "dashmap" -version = "5.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "907076dfda823b0b36d2a1bb5f90c96660a5bbcd7729e10727f07858f22c4edc" -dependencies = [ - "cfg-if", - "hashbrown 0.12.3", - "lock_api", - "once_cell", - "parking_lot_core 0.9.7", -] - -[[package]] -name = "der" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6919815d73839e7ad218de758883aae3a257ba6759ce7a9992501efbb53d705c" -dependencies = [ - "const-oid", -] - -[[package]] -name = "digest" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3d0c8c8752312f9713efd397ff63acb9f85585afbf179282e720e7704954dd5" -dependencies = [ - "generic-array 0.12.4", -] - -[[package]] -name = "digest" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" -dependencies = [ - "generic-array 0.14.7", -] - -[[package]] -name = "digest" -version = "0.10.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8168378f4e5023e7218c89c891c0fd8ecdb5e5e4f18cb78f38cf245dd021e76f" -dependencies = [ - "block-buffer 0.10.4", - "crypto-common", - "subtle 2.4.1", -] - -[[package]] -name = "dirs" -version = "4.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca3aa72a6f96ea37bbc5aa912f6788242832f75369bdfdadcb0e38423f100059" -dependencies = [ - "dirs-sys 0.3.7", -] - -[[package]] -name = "dirs" -version = "5.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44c45a9d03d6676652bcb5e724c7e988de1acad23a711b5217ab9cbecbec2225" -dependencies = [ - "dirs-sys 0.4.1", -] - -[[package]] -name = "dirs-sys" -version = "0.3.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b1d1d91c932ef41c0f2663aa8b0ca0342d444d842c06914aa0a7e352d0bada6" -dependencies = [ - "libc", - "redox_users", - "winapi", -] - -[[package]] -name = "dirs-sys" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "520f05a5cbd335fae5a99ff7a6ab8627577660ee5cfd6a94a6a929b52ff0321c" -dependencies = [ - "libc", - "option-ext", - "redox_users", - "windows-sys 0.48.0", -] - -[[package]] -name = "dotenv" -version = "0.15.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77c90badedccf4105eca100756a0b1289e191f6fcbdadd3cee1d2f614f97da8f" - -[[package]] -name = "dotenvy" -version = "0.15.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b" - -[[package]] -name = "dyn-clone" -version = "1.0.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68b0cf012f1230e43cd00ebb729c6bb58707ecfa8ad08b52ef3a4ccd2697fc30" - -[[package]] -name = "ecdsa" -version = "0.13.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0d69ae62e0ce582d56380743515fefaf1a8c70cec685d9677636d7e30ae9dc9" -dependencies = [ - "der", - "elliptic-curve", - "rfc6979", - "signature", -] - -[[package]] -name = "ed25519" -version = "1.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91cff35c70bba8a626e3185d8cd48cc11b5437e1a5bcd15b9b5fa3c64b6dfee7" -dependencies = [ - "signature", -] - -[[package]] -name = "ed25519-dalek" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c762bae6dcaf24c4c84667b8579785430908723d5c889f469d76a41d59cc7a9d" -dependencies = [ - "curve25519-dalek", - "ed25519", - "rand 0.7.3", - "serde", - "sha2 0.9.9", - "zeroize", -] - -[[package]] -name = "ed25519-zebra" -version = "3.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c24f403d068ad0b359e577a77f92392118be3f3c927538f2bb544a5ecd828c6" -dependencies = [ - "curve25519-dalek", - "hashbrown 0.12.3", - "hex", - "rand_core 0.6.4", - "serde", - "sha2 0.9.9", - "zeroize", -] - -[[package]] -name = "either" -version = "1.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91" - -[[package]] -name = "elliptic-curve" -version = "0.11.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25b477563c2bfed38a3b7a60964c49e058b2510ad3f12ba3483fd8f62c2306d6" -dependencies = [ - "base16ct", - "crypto-bigint", - "der", - "ff 0.11.1", - "generic-array 0.14.7", - "group 0.11.0", - "rand_core 0.6.4", - "sec1", - "subtle 2.4.1", - "zeroize", -] - -[[package]] -name = "encoding_rs" -version = "0.8.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "071a31f4ee85403370b58aca746f01041ede6f0da2730960ad001edc2b71b394" -dependencies = [ - "cfg-if", -] - -[[package]] -name = "enum-iterator" -version = "1.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45a0ac4aeb3a18f92eaf09c6bb9b3ac30ff61ca95514fc58cbead1c9a6bf5401" -dependencies = [ - "enum-iterator-derive", -] - -[[package]] -name = "enum-iterator-derive" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eecf8589574ce9b895052fa12d69af7a233f99e6107f5cb8dd1044f2a17bfdcb" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.15", -] - -[[package]] -name = "env_logger" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44533bbbb3bb3c1fa17d9f2e4e38bbbaf8396ba82193c4cb1b6445d711445d36" -dependencies = [ - "atty", - "humantime 1.3.0", - "log", - "regex", - "termcolor", -] - -[[package]] -name = "env_logger" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85cdab6a89accf66733ad5a1693a4dcced6aeff64602b634530dd73c1f3ee9f0" -dependencies = [ - "log", - "regex", -] - -[[package]] -name = "errno" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4bcfec3a70f97c962c307b2d2c56e358cf1d00b558d74262b5f929ee8cc7e73a" -dependencies = [ - "errno-dragonfly", - "libc", - "windows-sys 0.48.0", -] - -[[package]] -name = "errno-dragonfly" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" -dependencies = [ - "cc", - "libc", -] - -[[package]] -name = "event-listener" -version = "2.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" - -[[package]] -name = "ext-trait" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d772df1c1a777963712fb68e014235e80863d6a91a85c4e06ba2d16243a310e5" -dependencies = [ - "ext-trait-proc_macros", -] - -[[package]] -name = "ext-trait-proc_macros" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ab7934152eaf26aa5aa9f7371408ad5af4c31357073c9e84c3b9d7f11ad639a" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.109", -] - -[[package]] -name = "extension-traits" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a296e5a895621edf9fa8329c83aa1cb69a964643e36cf54d8d7a69b789089537" -dependencies = [ - "ext-trait", -] - -[[package]] -name = "eyre" -version = "0.6.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c2b6b5a29c02cdc822728b7d7b8ae1bab3e3b05d44522770ddd49722eeac7eb" -dependencies = [ - "indenter", - "once_cell", -] - -[[package]] -name = "fastrand" -version = "1.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e51093e27b0797c359783294ca4f0a911c270184cb10f85783b118614a1501be" -dependencies = [ - "instant", -] - -[[package]] -name = "ff" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0f40b2dcd8bc322217a5f6559ae5f9e9d1de202a2ecee2e9eafcbece7562a4f" -dependencies = [ - "rand_core 0.6.4", - "subtle 2.4.1", -] - -[[package]] -name = "ff" -version = "0.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "131655483be284720a17d74ff97592b8e76576dc25563148601df2d7c9080924" -dependencies = [ - "rand_core 0.6.4", - "subtle 2.4.1", -] - -[[package]] -name = "flate2" -version = "1.0.26" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b9429470923de8e8cbd4d2dc513535400b4b3fef0319fb5c4e1f520a7bef743" -dependencies = [ - "crc32fast", - "miniz_oxide", -] - -[[package]] -name = "flex-error" -version = "0.4.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c606d892c9de11507fa0dcffc116434f94e105d0bbdc4e405b61519464c49d7b" -dependencies = [ - "eyre", - "paste", -] - -[[package]] -name = "flume" -version = "0.10.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1657b4441c3403d9f7b3409e47575237dac27b1b5726df654a6ecbf92f0f7577" -dependencies = [ - "futures-core", - "futures-sink", - "pin-project", - "spin 0.9.8", -] - -[[package]] -name = "fnv" -version = "1.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" - -[[package]] -name = "foreign-types" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" -dependencies = [ - "foreign-types-shared", -] - -[[package]] -name = "foreign-types-shared" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" - -[[package]] -name = "form_urlencoded" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9c384f161156f5260c24a097c56119f9be8c798586aecc13afbcbe7b7e26bf8" -dependencies = [ - "percent-encoding", -] - -[[package]] -name = "forward_ref" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8cbd1169bd7b4a0a20d92b9af7a7e0422888bd38a6f5ec29c1fd8c1558a272e" - -[[package]] -name = "funty" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" - -[[package]] -name = "futures" -version = "0.3.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23342abe12aba583913b2e62f22225ff9c950774065e4bfb61a19cd9770fec40" -dependencies = [ - "futures-channel", - "futures-core", - "futures-executor", - "futures-io", - "futures-sink", - "futures-task", - "futures-util", -] - -[[package]] -name = "futures-channel" -version = "0.3.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "955518d47e09b25bbebc7a18df10b81f0c766eaf4c4f1cccef2fca5f2a4fb5f2" -dependencies = [ - "futures-core", - "futures-sink", -] - -[[package]] -name = "futures-core" -version = "0.3.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4bca583b7e26f571124fe5b7561d49cb2868d79116cfa0eefce955557c6fee8c" - -[[package]] -name = "futures-executor" -version = "0.3.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ccecee823288125bd88b4d7f565c9e58e41858e47ab72e8ea2d64e93624386e0" -dependencies = [ - "futures-core", - "futures-task", - "futures-util", -] - -[[package]] -name = "futures-intrusive" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a604f7a68fbf8103337523b1fadc8ade7361ee3f112f7c680ad179651616aed5" -dependencies = [ - "futures-core", - "lock_api", - "parking_lot", -] - -[[package]] -name = "futures-io" -version = "0.3.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fff74096e71ed47f8e023204cfd0aa1289cd54ae5430a9523be060cdb849964" - -[[package]] -name = "futures-macro" -version = "0.3.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.15", -] - -[[package]] -name = "futures-sink" -version = "0.3.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f43be4fe21a13b9781a69afa4985b0f6ee0e1afab2c6f454a8cf30e2b2237b6e" - -[[package]] -name = "futures-task" -version = "0.3.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76d3d132be6c0e6aa1534069c705a74a5997a356c0dc2f86a47765e5617c5b65" - -[[package]] -name = "futures-util" -version = "0.3.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26b01e40b772d54cf6c6d721c1d1abd0647a0106a12ecaa1c186273392a69533" -dependencies = [ - "futures-channel", - "futures-core", - "futures-io", - "futures-macro", - "futures-sink", - "futures-task", - "memchr", - "pin-project-lite", - "pin-utils", - "slab", -] - -[[package]] -name = "generic-array" -version = "0.12.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffdf9f34f1447443d37393cc6c2b8313aebddcd96906caf34e54c68d8e57d7bd" -dependencies = [ - "typenum", -] - -[[package]] -name = "generic-array" -version = "0.14.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" -dependencies = [ - "serde", - "typenum", - "version_check", -] - -[[package]] -name = "getrandom" -version = "0.1.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce" -dependencies = [ - "cfg-if", - "js-sys", - "libc", - "wasi 0.9.0+wasi-snapshot-preview1", - "wasm-bindgen", -] - -[[package]] -name = "getrandom" -version = "0.2.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c85e1d9ab2eadba7e5040d4e09cbd6d072b76a557ad64e797c2cb9d4da21d7e4" -dependencies = [ - "cfg-if", - "js-sys", - "libc", - "wasi 0.11.0+wasi-snapshot-preview1", - "wasm-bindgen", -] - -[[package]] -name = "getset" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e45727250e75cc04ff2846a66397da8ef2b3db8e40e0cef4df67950a07621eb9" -dependencies = [ - "proc-macro-error", - "proc-macro2", - "quote", - "syn 1.0.109", -] - -[[package]] -name = "ghost" -version = "0.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e77ac7b51b8e6313251737fcef4b1c01a2ea102bde68415b62c0ee9268fec357" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.15", -] - -[[package]] -name = "git2" -version = "0.14.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0155506aab710a86160ddb504a480d2964d7ab5b9e62419be69e0032bc5931c" -dependencies = [ - "bitflags", - "libc", - "libgit2-sys", - "log", - "url", -] - -[[package]] -name = "gloo-timers" -version = "0.2.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b995a66bb87bebce9a0f4a95aed01daca4872c050bfcb21653361c03bc35e5c" -dependencies = [ - "futures-channel", - "futures-core", - "js-sys", - "wasm-bindgen", -] - -[[package]] -name = "group" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c363a5301b8f153d80747126a04b3c82073b9fe3130571a9d170cacdeaf7912" -dependencies = [ - "byteorder", - "ff 0.10.1", - "rand_core 0.6.4", - "subtle 2.4.1", -] - -[[package]] -name = "group" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc5ac374b108929de78460075f3dc439fa66df9d8fc77e8f12caa5165fcf0c89" -dependencies = [ - "byteorder", - "ff 0.11.1", - "rand_core 0.6.4", - "subtle 2.4.1", -] - -[[package]] -name = "h2" -version = "0.3.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17f8a914c2987b688368b5138aa05321db91f4090cf26118185672ad588bce21" -dependencies = [ - "bytes", - "fnv", - "futures-core", - "futures-sink", - "futures-util", - "http", - "indexmap", - "slab", - "tokio", - "tokio-util", - "tracing", -] - -[[package]] -name = "handlebars" -version = "3.5.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4498fc115fa7d34de968184e473529abb40eeb6be8bc5f7faba3d08c316cb3e3" -dependencies = [ - "log", - "pest", - "pest_derive", - "quick-error 2.0.1", - "serde", - "serde_json", -] - -[[package]] -name = "hashbrown" -version = "0.11.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e" -dependencies = [ - "ahash", -] - -[[package]] -name = "hashbrown" -version = "0.12.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" -dependencies = [ - "ahash", -] - -[[package]] -name = "hashlink" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7249a3129cbc1ffccd74857f81464a323a152173cdb134e0fd81bc803b29facf" -dependencies = [ - "hashbrown 0.11.2", -] - -[[package]] -name = "hashlink" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69fe1fcf8b4278d860ad0548329f892a3631fb63f82574df68275f34cdbe0ffa" -dependencies = [ - "hashbrown 0.12.3", -] - -[[package]] -name = "headers" -version = "0.3.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3e372db8e5c0d213e0cd0b9be18be2aca3d44cf2fe30a9d46a65581cd454584" -dependencies = [ - "base64 0.13.1", - "bitflags", - "bytes", - "headers-core", - "http", - "httpdate", - "mime", - "sha1", -] - -[[package]] -name = "headers-core" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7f66481bfee273957b1f20485a4ff3362987f85b2c236580d81b4eb7a326429" -dependencies = [ - "http", -] - -[[package]] -name = "heck" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" -dependencies = [ - "unicode-segmentation", -] - -[[package]] -name = "hermit-abi" -version = "0.1.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" -dependencies = [ - "libc", -] - -[[package]] -name = "hermit-abi" -version = "0.2.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee512640fe35acbfb4bb779db6f0d80704c2cacfa2e39b601ef3e3f47d1ae4c7" -dependencies = [ - "libc", -] - -[[package]] -name = "hermit-abi" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fed44880c466736ef9a5c5b5facefb5ed0785676d0c02d612db14e54f0d84286" - -[[package]] -name = "hex" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" - -[[package]] -name = "hex-literal" -version = "0.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ebdb29d2ea9ed0083cd8cece49bbd968021bd99b0849edb4a9a7ee0fdf6a4e0" - -[[package]] -name = "hkdf" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01706d578d5c281058480e673ae4086a9f4710d8df1ad80a5b03e39ece5f886b" -dependencies = [ - "digest 0.9.0", - "hmac 0.11.0", -] - -[[package]] -name = "hkdf" -version = "0.12.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "791a029f6b9fc27657f6f188ec6e5e43f6911f6f878e0dc5501396e09809d437" -dependencies = [ - "hmac 0.12.1", -] - -[[package]] -name = "hmac" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a2a2320eb7ec0ebe8da8f744d7812d9fc4cb4d09344ac01898dbcb6a20ae69b" -dependencies = [ - "crypto-mac 0.11.1", - "digest 0.9.0", -] - -[[package]] -name = "hmac" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" -dependencies = [ - "digest 0.10.6", -] - -[[package]] -name = "http" -version = "0.2.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd6effc99afb63425aff9b05836f029929e345a6148a14b7ecd5ab67af944482" -dependencies = [ - "bytes", - "fnv", - "itoa", -] - -[[package]] -name = "http-body" -version = "0.4.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1" -dependencies = [ - "bytes", - "http", - "pin-project-lite", -] - -[[package]] -name = "httparse" -version = "1.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" - -[[package]] -name = "httpdate" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421" - -[[package]] -name = "humantime" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df004cfca50ef23c36850aaaa59ad52cc70d0e90243c3c7737a4dd32dc7a3c4f" -dependencies = [ - "quick-error 1.2.3", -] - -[[package]] -name = "humantime" -version = "2.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" - -[[package]] -name = "humantime-serde" -version = "1.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57a3db5ea5923d99402c94e9feb261dc5ee9b4efa158b0315f788cf549cc200c" -dependencies = [ - "humantime 2.1.0", - "serde", -] - -[[package]] -name = "hyper" -version = "0.14.26" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab302d72a6f11a3b910431ff93aae7e773078c769f0a3ef15fb9ec692ed147d4" -dependencies = [ - "bytes", - "futures-channel", - "futures-core", - "futures-util", - "h2", - "http", - "http-body", - "httparse", - "httpdate", - "itoa", - "pin-project-lite", - "socket2", - "tokio", - "tower-service", - "tracing", - "want", -] - -[[package]] -name = "hyper-proxy" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca815a891b24fdfb243fa3239c86154392b0953ee584aa1a2a1f66d20cbe75cc" -dependencies = [ - "bytes", - "futures", - "headers", - "http", - "hyper", - "hyper-rustls", - "rustls-native-certs", - "tokio", - "tokio-rustls 0.22.0", - "tower-service", - "webpki 0.21.4", -] - -[[package]] -name = "hyper-rustls" -version = "0.22.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f9f7a97316d44c0af9b0301e65010573a853a9fc97046d7331d7f6bc0fd5a64" -dependencies = [ - "ct-logs", - "futures-util", - "hyper", - "log", - "rustls 0.19.1", - "rustls-native-certs", - "tokio", - "tokio-rustls 0.22.0", - "webpki 0.21.4", - "webpki-roots 0.21.1", -] - -[[package]] -name = "hyper-tls" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" -dependencies = [ - "bytes", - "hyper", - "native-tls", - "tokio", - "tokio-native-tls", -] - -[[package]] -name = "idna" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e14ddfc70884202db2244c223200c204c2bda1bc6e0998d11b5e024d657209e6" -dependencies = [ - "unicode-bidi", - "unicode-normalization", -] - -[[package]] -name = "indenter" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683" - -[[package]] -name = "indexmap" -version = "1.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" -dependencies = [ - "autocfg", - "hashbrown 0.12.3", - "serde", -] - -[[package]] -name = "inout" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5" -dependencies = [ - "generic-array 0.14.7", -] - -[[package]] -name = "input_buffer" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f97967975f448f1a7ddb12b0bc41069d09ed6a1c161a92687e057325db35d413" -dependencies = [ - "bytes", -] - -[[package]] -name = "instant" -version = "0.1.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" -dependencies = [ - "cfg-if", - "js-sys", - "wasm-bindgen", - "web-sys", -] - -[[package]] -name = "inventory" -version = "0.1.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0eb5160c60ba1e809707918ee329adb99d222888155835c6feedba19f6c3fd4" -dependencies = [ - "ctor", - "ghost", - "inventory-impl", -] - -[[package]] -name = "inventory-impl" -version = "0.1.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e41b53715c6f0c4be49510bb82dee2c1e51c8586d885abe65396e82ed518548" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.109", -] - -[[package]] -name = "io-lifetimes" -version = "1.0.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c66c74d2ae7e79a5a8f7ac924adbe38ee42a859c6539ad869eb51f0b52dc220" -dependencies = [ - "hermit-abi 0.3.1", - "libc", - "windows-sys 0.48.0", -] - -[[package]] -name = "ipnet" -version = "2.7.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12b6ee2129af8d4fb011108c73d99a1b83a85977f23b82460c0ae2e25bb4b57f" - -[[package]] -name = "is-terminal" -version = "0.4.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adcf93614601c8129ddf72e2d5633df827ba6551541c6d8c59520a371475be1f" -dependencies = [ - "hermit-abi 0.3.1", - "io-lifetimes", - "rustix", - "windows-sys 0.48.0", -] - -[[package]] -name = "itertools" -version = "0.10.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" -dependencies = [ - "either", -] - -[[package]] -name = "itoa" -version = "1.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "453ad9f582a441959e5f0d088b02ce04cfe8d51a8eaf077f12ac6d3e94164ca6" - -[[package]] -name = "jni" -version = "0.21.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a87aa2bb7d2af34197c04845522473242e1aa17c12f4935d5856491a7fb8c97" -dependencies = [ - "cesu8", - "cfg-if", - "combine", - "jni-sys", - "log", - "thiserror", - "walkdir", - "windows-sys 0.45.0", -] - -[[package]] -name = "jni-sys" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" - -[[package]] -name = "jobserver" -version = "0.1.26" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "936cfd212a0155903bcbc060e316fb6cc7cbf2e1907329391ebadc1fe0ce77c2" -dependencies = [ - "libc", -] - -[[package]] -name = "js-sys" -version = "0.3.60" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49409df3e3bf0856b916e2ceaca09ee28e6871cf7d9ce97a692cacfdb2a25a47" -dependencies = [ - "wasm-bindgen", -] - -[[package]] -name = "k256" -version = "0.10.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19c3a5e0a0b8450278feda242592512e09f61c72e018b8cd5c859482802daf2d" -dependencies = [ - "cfg-if", - "ecdsa", - "elliptic-curve", - "sec1", - "sha2 0.9.9", - "sha3", -] - -[[package]] -name = "keccak" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f6d5ed8676d904364de097082f4e7d240b571b67989ced0240f08b7f966f940" -dependencies = [ - "cpufeatures", -] - -[[package]] -name = "keystream" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c33070833c9ee02266356de0c43f723152bd38bd96ddf52c82b3af10c9138b28" - -[[package]] -name = "lazy_static" -version = "1.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" - -[[package]] -name = "libc" -version = "0.2.144" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b00cc1c228a6782d0f076e7b232802e0c5689d41bb5df366f2a6b6621cfdfe1" - -[[package]] -name = "libgit2-sys" -version = "0.13.5+1.4.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51e5ea06c26926f1002dd553fded6cfcdc9784c1f60feeb58368b4d9b07b6dba" -dependencies = [ - "cc", - "libc", - "libz-sys", - "pkg-config", -] - -[[package]] -name = "libm" -version = "0.2.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "348108ab3fba42ec82ff6e9564fc4ca0247bdccdc68dd8af9764bbc79c3c8ffb" - -[[package]] -name = "libsqlite3-sys" -version = "0.24.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "898745e570c7d0453cc1fbc4a701eb6c662ed54e8fec8b7d14be137ebeeb9d14" -dependencies = [ - "cc", - "pkg-config", - "vcpkg", -] - -[[package]] -name = "libz-sys" -version = "1.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56ee889ecc9568871456d42f603d6a0ce59ff328d291063a45cbdf0036baf6db" -dependencies = [ - "cc", - "libc", - "pkg-config", - "vcpkg", -] - -[[package]] -name = "linux-raw-sys" -version = "0.3.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ece97ea872ece730aed82664c424eb4c8291e1ff2480247ccf7409044bc6479f" - -[[package]] -name = "lioness" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ae926706ba42c425c9457121178330d75e273df2e82e28b758faf3de3a9acb9" -dependencies = [ - "arrayref", - "blake2", - "chacha", - "keystream", -] - -[[package]] -name = "lock_api" -version = "0.4.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "435011366fe56583b16cf956f9df0095b405b82d76425bc8981c0e22e60ec4df" -dependencies = [ - "autocfg", - "scopeguard", -] - -[[package]] -name = "log" -version = "0.4.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" -dependencies = [ - "cfg-if", -] - -[[package]] -name = "macro_rules_attribute" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea1bd89c00ca5cf70d924bdaa5baea9eabf4709489ce9e71f2f559ed04edd862" -dependencies = [ - "macro_rules_attribute-proc_macro", - "paste", -] - -[[package]] -name = "macro_rules_attribute-proc_macro" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8dd856d451cc0da70e2ef2ce95a18e39a93b7558bedf10201ad28503f918568" - -[[package]] -name = "memchr" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" - -[[package]] -name = "memoffset" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d61c719bcfbcf5d62b3a09efa6088de8c54bc0bfcd3ea7ae39fcc186108b8de1" -dependencies = [ - "autocfg", -] - -[[package]] -name = "mime" -version = "0.3.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" - -[[package]] -name = "minimal-lexical" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" - -[[package]] -name = "miniz_oxide" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7" -dependencies = [ - "adler", -] - -[[package]] -name = "mio" -version = "0.8.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b9d9a46eff5b4ff64b45a9e316a6d1e0bc719ef429cbec4dc630684212bfdf9" -dependencies = [ - "libc", - "log", - "wasi 0.11.0+wasi-snapshot-preview1", - "windows-sys 0.45.0", -] - -[[package]] -name = "native-tls" -version = "0.2.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07226173c32f2926027b63cce4bcd8076c3552846cbe7925f3aaffeac0a3b92e" -dependencies = [ - "lazy_static", - "libc", - "log", - "openssl", - "openssl-probe", - "openssl-sys", - "schannel", - "security-framework", - "security-framework-sys", - "tempfile", -] - -[[package]] -name = "nom" -version = "7.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" -dependencies = [ - "memchr", - "minimal-lexical", -] - -[[package]] -name = "num-derive" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "876a53fff98e03a936a674b29568b0e605f06b29372c2489ff4de23f1949743d" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.109", -] - -[[package]] -name = "num-traits" -version = "0.2.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" -dependencies = [ - "autocfg", - "libm", -] - -[[package]] -name = "num_cpus" -version = "1.15.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fac9e2da13b5eb447a6ce3d392f23a29d8694bff781bf03a16cd9ac8697593b" -dependencies = [ - "hermit-abi 0.2.6", - "libc", -] - -[[package]] -name = "nym-api-requests" -version = "0.1.0" -dependencies = [ - "bs58", - "cosmrs", - "cosmwasm-std", - "getset", - "nym-coconut-interface", - "nym-mixnet-contract-common", - "schemars", - "serde", -] - -[[package]] -name = "nym-bandwidth-controller" -version = "0.1.0" -dependencies = [ - "bip39", - "nym-coconut-interface", - "nym-credential-storage", - "nym-credentials", - "nym-crypto", - "nym-network-defaults", - "nym-validator-client", - "rand 0.7.3", - "thiserror", - "url", -] - -[[package]] -name = "nym-bin-common" -version = "0.5.0" -dependencies = [ - "atty", - "clap", - "clap_complete", - "clap_complete_fig", - "log", - "pretty_env_logger", - "semver 0.11.0", - "serde", - "vergen", -] - -[[package]] -name = "nym-client-core" -version = "1.1.14" -dependencies = [ - "async-trait", - "dashmap", - "dirs 4.0.0", - "futures", - "gloo-timers", - "humantime-serde", - "log", - "nym-bandwidth-controller", - "nym-config", - "nym-credential-storage", - "nym-crypto", - "nym-gateway-client", - "nym-gateway-requests", - "nym-network-defaults", - "nym-nonexhaustive-delayqueue", - "nym-pemstore", - "nym-sphinx", - "nym-task", - "nym-topology", - "nym-validator-client", - "rand 0.7.3", - "serde", - "serde_json", - "sqlx 0.6.3", - "tap", - "thiserror", - "time", - "tokio", - "tokio-stream", - "tokio-tungstenite", - "tungstenite", - "url", - "wasm-bindgen", - "wasm-bindgen-futures", - "wasm-timer", - "wasm-utils", - "zeroize", -] - -[[package]] -name = "nym-coconut" -version = "0.5.0" -dependencies = [ - "bls12_381 0.6.0", - "bs58", - "digest 0.9.0", - "ff 0.11.1", - "getrandom 0.2.9", - "group 0.11.0", - "itertools", - "nym-dkg", - "nym-pemstore", - "rand 0.8.5", - "serde", - "serde_derive", - "sha2 0.9.9", - "thiserror", -] - -[[package]] -name = "nym-coconut-bandwidth-contract-common" -version = "0.1.0" -dependencies = [ - "cosmwasm-std", - "nym-multisig-contract-common", - "schemars", - "serde", -] - -[[package]] -name = "nym-coconut-dkg-common" -version = "0.1.0" -dependencies = [ - "cosmwasm-std", - "cw-utils", - "nym-contracts-common", - "nym-multisig-contract-common", - "schemars", - "serde", -] - -[[package]] -name = "nym-coconut-interface" -version = "0.1.0" -dependencies = [ - "bs58", - "getset", - "nym-coconut", - "serde", - "thiserror", -] - -[[package]] -name = "nym-config" -version = "0.1.0" -dependencies = [ - "dirs 5.0.1", - "handlebars", - "log", - "nym-network-defaults", - "serde", - "toml 0.7.4", - "url", -] - -[[package]] -name = "nym-contracts-common" -version = "0.4.0" -dependencies = [ - "bs58", - "cosmwasm-std", - "schemars", - "serde", - "thiserror", -] - -[[package]] -name = "nym-credential-storage" -version = "0.1.0" -dependencies = [ - "async-trait", - "log", - "sqlx 0.5.13", - "thiserror", - "tokio", -] - -[[package]] -name = "nym-credentials" -version = "0.1.0" -dependencies = [ - "bls12_381 0.5.0", - "cosmrs", - "nym-api-requests", - "nym-coconut-interface", - "nym-crypto", - "nym-validator-client", - "thiserror", -] - -[[package]] -name = "nym-crypto" -version = "0.3.0" -dependencies = [ - "aes 0.8.2", - "blake3", - "bs58", - "cipher 0.4.4", - "ctr 0.9.2", - "digest 0.10.6", - "ed25519-dalek", - "generic-array 0.14.7", - "hkdf 0.12.3", - "hmac 0.12.1", - "nym-pemstore", - "nym-sphinx-types", - "rand 0.7.3", - "subtle-encoding", - "thiserror", - "x25519-dalek", - "zeroize", -] - -[[package]] -name = "nym-dkg" -version = "0.1.0" -dependencies = [ - "bitvec", - "bls12_381 0.6.0", - "bs58", - "ff 0.11.1", - "group 0.11.0", - "lazy_static", - "nym-pemstore", - "rand 0.8.5", - "rand_chacha 0.3.1", - "rand_core 0.6.4", - "serde", - "serde_derive", - "sha2 0.9.9", - "thiserror", - "zeroize", -] - -[[package]] -name = "nym-gateway-client" -version = "0.1.0" -dependencies = [ - "futures", - "getrandom 0.2.9", - "log", - "nym-bandwidth-controller", - "nym-coconut-interface", - "nym-credential-storage", - "nym-crypto", - "nym-gateway-requests", - "nym-network-defaults", - "nym-pemstore", - "nym-sphinx", - "nym-task", - "nym-validator-client", - "rand 0.7.3", - "serde", - "thiserror", - "tokio", - "tokio-stream", - "tokio-tungstenite", - "tungstenite", - "url", - "wasm-bindgen", - "wasm-bindgen-futures", - "wasm-timer", - "wasm-utils", -] - -[[package]] -name = "nym-gateway-requests" -version = "0.1.0" -dependencies = [ - "bs58", - "futures", - "generic-array 0.14.7", - "log", - "nym-coconut-interface", - "nym-credentials", - "nym-crypto", - "nym-pemstore", - "nym-sphinx", - "rand 0.7.3", - "serde", - "serde_json", - "thiserror", - "tungstenite", - "zeroize", -] - -[[package]] -name = "nym-group-contract-common" -version = "0.1.0" -dependencies = [ - "cw4", - "schemars", - "serde", -] - -[[package]] -name = "nym-mixnet-contract-common" -version = "0.5.0" -dependencies = [ - "bs58", - "cosmwasm-std", - "humantime-serde", - "log", - "nym-contracts-common", - "schemars", - "serde", - "serde-json-wasm", - "serde_repr", - "thiserror", - "time", -] - -[[package]] -name = "nym-multisig-contract-common" -version = "0.1.0" -dependencies = [ - "cosmwasm-std", - "cw-utils", - "cw3", - "cw4", - "schemars", - "serde", - "thiserror", -] - -[[package]] -name = "nym-name-service-common" -version = "0.1.0" -dependencies = [ - "cosmwasm-std", - "schemars", - "serde", -] - -[[package]] -name = "nym-network-defaults" -version = "0.1.0" -dependencies = [ - "cfg-if", - "dotenvy", - "hex-literal", - "once_cell", - "serde", - "thiserror", - "url", -] - -[[package]] -name = "nym-nonexhaustive-delayqueue" -version = "0.1.0" -dependencies = [ - "futures-core", - "slab", - "tokio", - "tokio-stream", - "tokio-util", - "wasm-timer", -] - -[[package]] -name = "nym-ordered-buffer" -version = "0.1.0" -dependencies = [ - "log", - "thiserror", -] - -[[package]] -name = "nym-outfox" -version = "0.1.0" -dependencies = [ - "blake3", - "chacha20", - "chacha20poly1305", - "curve25519-dalek", - "getrandom 0.2.9", - "rayon", - "sphinx-packet 0.1.0 (git+https://github.com/nymtech/sphinx.git)", - "thiserror", - "zeroize", -] - -[[package]] -name = "nym-pemstore" -version = "0.2.0" -dependencies = [ - "pem", -] - -[[package]] -name = "nym-service-provider-directory-common" -version = "0.1.0" -dependencies = [ - "cosmwasm-std", - "schemars", - "serde", -] - -[[package]] -name = "nym-service-providers-common" -version = "0.1.0" -dependencies = [ - "async-trait", - "log", - "nym-bin-common", - "nym-sphinx-anonymous-replies", - "serde", - "serde_json", - "thiserror", -] - -[[package]] -name = "nym-socks5-client-core" -version = "0.1.0" -dependencies = [ - "dirs 4.0.0", - "futures", - "log", - "nym-bandwidth-controller", - "nym-client-core", - "nym-config", - "nym-credential-storage", - "nym-network-defaults", - "nym-service-providers-common", - "nym-socks5-proxy-helpers", - "nym-socks5-requests", - "nym-sphinx", - "nym-task", - "nym-validator-client", - "pin-project", - "rand 0.7.3", - "serde", - "tap", - "thiserror", - "tokio", -] - -[[package]] -name = "nym-socks5-listener" -version = "0.1.0" -dependencies = [ - "android_logger", - "anyhow", - "futures", - "jni", - "lazy_static", - "log", - "nym-client-core", - "nym-config", - "nym-credential-storage", - "nym-crypto", - "nym-socks5-client-core", - "openssl", - "rand 0.7.3", - "safer-ffi", - "tokio", -] - -[[package]] -name = "nym-socks5-proxy-helpers" -version = "0.1.0" -dependencies = [ - "bytes", - "futures", - "log", - "nym-ordered-buffer", - "nym-socks5-requests", - "nym-task", - "tokio", - "tokio-util", -] - -[[package]] -name = "nym-socks5-requests" -version = "0.1.0" -dependencies = [ - "nym-service-providers-common", - "nym-sphinx-addressing", - "serde", - "serde_json", - "thiserror", -] - -[[package]] -name = "nym-sphinx" -version = "0.1.0" -dependencies = [ - "log", - "nym-crypto", - "nym-outfox", - "nym-sphinx-acknowledgements", - "nym-sphinx-addressing", - "nym-sphinx-anonymous-replies", - "nym-sphinx-chunking", - "nym-sphinx-cover", - "nym-sphinx-forwarding", - "nym-sphinx-framing", - "nym-sphinx-params", - "nym-sphinx-routing", - "nym-sphinx-types", - "nym-topology", - "rand 0.7.3", - "rand_distr", - "thiserror", - "tokio", -] - -[[package]] -name = "nym-sphinx-acknowledgements" -version = "0.1.0" -dependencies = [ - "nym-crypto", - "nym-pemstore", - "nym-sphinx-addressing", - "nym-sphinx-params", - "nym-sphinx-types", - "nym-topology", - "rand 0.7.3", - "thiserror", - "zeroize", -] - -[[package]] -name = "nym-sphinx-addressing" -version = "0.1.0" -dependencies = [ - "nym-crypto", - "nym-sphinx-types", - "serde", - "thiserror", -] - -[[package]] -name = "nym-sphinx-anonymous-replies" -version = "0.1.0" -dependencies = [ - "bs58", - "nym-crypto", - "nym-sphinx-addressing", - "nym-sphinx-params", - "nym-sphinx-types", - "nym-topology", - "rand 0.7.3", - "serde", - "thiserror", - "wasm-bindgen", -] - -[[package]] -name = "nym-sphinx-chunking" -version = "0.1.0" -dependencies = [ - "log", - "nym-sphinx-addressing", - "nym-sphinx-params", - "nym-sphinx-types", - "rand 0.7.3", - "thiserror", -] - -[[package]] -name = "nym-sphinx-cover" -version = "0.1.0" -dependencies = [ - "nym-crypto", - "nym-sphinx-acknowledgements", - "nym-sphinx-addressing", - "nym-sphinx-chunking", - "nym-sphinx-forwarding", - "nym-sphinx-params", - "nym-sphinx-types", - "nym-topology", - "rand 0.7.3", - "thiserror", -] - -[[package]] -name = "nym-sphinx-forwarding" -version = "0.1.0" -dependencies = [ - "nym-outfox", - "nym-sphinx-addressing", - "nym-sphinx-params", - "nym-sphinx-types", -] - -[[package]] -name = "nym-sphinx-framing" -version = "0.1.0" -dependencies = [ - "bytes", - "nym-sphinx-params", - "nym-sphinx-types", - "thiserror", - "tokio-util", -] - -[[package]] -name = "nym-sphinx-params" -version = "0.1.0" -dependencies = [ - "nym-crypto", - "nym-sphinx-types", - "serde", - "thiserror", -] - -[[package]] -name = "nym-sphinx-routing" -version = "0.1.0" -dependencies = [ - "nym-sphinx-addressing", - "nym-sphinx-types", - "thiserror", -] - -[[package]] -name = "nym-sphinx-types" -version = "0.2.0" -dependencies = [ - "sphinx-packet 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "nym-task" -version = "0.1.0" -dependencies = [ - "futures", - "log", - "thiserror", - "tokio", - "wasm-bindgen", - "wasm-bindgen-futures", -] - -[[package]] -name = "nym-topology" -version = "0.1.0" -dependencies = [ - "async-trait", - "bs58", - "log", - "nym-bin-common", - "nym-crypto", - "nym-mixnet-contract-common", - "nym-sphinx-addressing", - "nym-sphinx-routing", - "nym-sphinx-types", - "rand 0.7.3", - "thiserror", -] - -[[package]] -name = "nym-validator-client" -version = "0.1.0" -dependencies = [ - "async-trait", - "base64 0.13.1", - "bip39", - "colored", - "cosmrs", - "cosmwasm-std", - "cw3", - "cw4", - "eyre", - "flate2", - "futures", - "itertools", - "log", - "nym-api-requests", - "nym-coconut-bandwidth-contract-common", - "nym-coconut-dkg-common", - "nym-coconut-interface", - "nym-config", - "nym-contracts-common", - "nym-group-contract-common", - "nym-mixnet-contract-common", - "nym-multisig-contract-common", - "nym-name-service-common", - "nym-network-defaults", - "nym-service-provider-directory-common", - "nym-vesting-contract", - "nym-vesting-contract-common", - "prost", - "reqwest", - "serde", - "serde_json", - "sha2 0.9.9", - "thiserror", - "tokio", - "url", - "zeroize", -] - -[[package]] -name = "nym-vesting-contract" -version = "1.4.0" -dependencies = [ - "cosmwasm-derive", - "cosmwasm-std", - "cw-storage-plus", - "cw2", - "nym-contracts-common", - "nym-mixnet-contract-common", - "nym-vesting-contract-common", - "schemars", - "semver 1.0.17", - "serde", - "thiserror", - "vergen", -] - -[[package]] -name = "nym-vesting-contract-common" -version = "0.6.0" -dependencies = [ - "cosmwasm-std", - "nym-contracts-common", - "nym-mixnet-contract-common", - "schemars", - "serde", -] - -[[package]] -name = "once_cell" -version = "1.17.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3" - -[[package]] -name = "opaque-debug" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2839e79665f131bdb5782e51f2c6c9599c133c6098982a54c794358bf432529c" - -[[package]] -name = "opaque-debug" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" - -[[package]] -name = "openssl" -version = "0.10.52" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01b8574602df80f7b85fdfc5392fa884a4e3b3f4f35402c070ab34c3d3f78d56" -dependencies = [ - "bitflags", - "cfg-if", - "foreign-types", - "libc", - "once_cell", - "openssl-macros", - "openssl-sys", -] - -[[package]] -name = "openssl-macros" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.15", -] - -[[package]] -name = "openssl-probe" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" - -[[package]] -name = "openssl-src" -version = "111.25.3+1.1.1t" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "924757a6a226bf60da5f7dd0311a34d2b52283dd82ddeb103208ddc66362f80c" -dependencies = [ - "cc", -] - -[[package]] -name = "openssl-sys" -version = "0.9.87" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e17f59264b2809d77ae94f0e1ebabc434773f370d6ca667bd223ea10e06cc7e" -dependencies = [ - "cc", - "libc", - "openssl-src", - "pkg-config", - "vcpkg", -] - -[[package]] -name = "option-ext" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" - -[[package]] -name = "pairing" -version = "0.20.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7de9d09263c9966e8196fe0380c9dbbc7ea114b5cf371ba29004bc1f9c6db7f3" -dependencies = [ - "group 0.10.0", -] - -[[package]] -name = "pairing" -version = "0.21.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2e415e349a3006dd7d9482cdab1c980a845bed1377777d768cb693a44540b42" -dependencies = [ - "group 0.11.0", -] - -[[package]] -name = "parking_lot" -version = "0.11.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99" -dependencies = [ - "instant", - "lock_api", - "parking_lot_core 0.8.6", -] - -[[package]] -name = "parking_lot_core" -version = "0.8.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60a2cfe6f0ad2bfc16aefa463b497d5c7a5ecd44a23efa72aa342d90177356dc" -dependencies = [ - "cfg-if", - "instant", - "libc", - "redox_syscall 0.2.16", - "smallvec", - "winapi", -] - -[[package]] -name = "parking_lot_core" -version = "0.9.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9069cbb9f99e3a5083476ccb29ceb1de18b9118cafa53e90c9551235de2b9521" -dependencies = [ - "cfg-if", - "libc", - "redox_syscall 0.2.16", - "smallvec", - "windows-sys 0.45.0", -] - -[[package]] -name = "paste" -version = "1.0.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f746c4065a8fa3fe23974dd82f15431cc8d40779821001404d10d2e79ca7d79" - -[[package]] -name = "pbkdf2" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f05894bce6a1ba4be299d0c5f29563e08af2bc18bb7d48313113bed71e904739" -dependencies = [ - "crypto-mac 0.11.1", -] - -[[package]] -name = "peg" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07c0b841ea54f523f7aa556956fbd293bcbe06f2e67d2eb732b7278aaf1d166a" -dependencies = [ - "peg-macros", - "peg-runtime", -] - -[[package]] -name = "peg-macros" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5aa52829b8decbef693af90202711348ab001456803ba2a98eb4ec8fb70844c" -dependencies = [ - "peg-runtime", - "proc-macro2", - "quote", -] - -[[package]] -name = "peg-runtime" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c719dcf55f09a3a7e764c6649ab594c18a177e3599c467983cdf644bfc0a4088" - -[[package]] -name = "pem" -version = "0.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd56cbd21fea48d0c440b41cd69c589faacade08c992d9a54e471b79d0fd13eb" -dependencies = [ - "base64 0.13.1", - "once_cell", - "regex", -] - -[[package]] -name = "percent-encoding" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e" - -[[package]] -name = "pest" -version = "2.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e68e84bfb01f0507134eac1e9b410a12ba379d064eab48c50ba4ce329a527b70" -dependencies = [ - "thiserror", - "ucd-trie", -] - -[[package]] -name = "pest_derive" -version = "2.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b79d4c71c865a25a4322296122e3924d30bc8ee0834c8bfc8b95f7f054afbfb" -dependencies = [ - "pest", - "pest_generator", -] - -[[package]] -name = "pest_generator" -version = "2.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c435bf1076437b851ebc8edc3a18442796b30f1728ffea6262d59bbe28b077e" -dependencies = [ - "pest", - "pest_meta", - "proc-macro2", - "quote", - "syn 2.0.15", -] - -[[package]] -name = "pest_meta" -version = "2.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "745a452f8eb71e39ffd8ee32b3c5f51d03845f99786fa9b68db6ff509c505411" -dependencies = [ - "once_cell", - "pest", - "sha2 0.10.6", -] - -[[package]] -name = "pin-project" -version = "1.0.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad29a609b6bcd67fee905812e544992d216af9d755757c05ed2d0e15a74c6ecc" -dependencies = [ - "pin-project-internal", -] - -[[package]] -name = "pin-project-internal" -version = "1.0.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "069bdb1e05adc7a8990dce9cc75370895fbe4e3d58b9b73bf1aee56359344a55" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.109", -] - -[[package]] -name = "pin-project-lite" -version = "0.2.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" - -[[package]] -name = "pin-utils" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" - -[[package]] -name = "pkcs8" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7cabda3fb821068a9a4fab19a683eac3af12edf0f34b94a8be53c4972b8149d0" -dependencies = [ - "der", - "spki", - "zeroize", -] - -[[package]] -name = "pkg-config" -version = "0.3.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964" - -[[package]] -name = "poly1305" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8159bd90725d2df49889a078b54f4f79e87f1f8a8444194cdca81d38f5393abf" -dependencies = [ - "cpufeatures", - "opaque-debug 0.3.0", - "universal-hash", -] - -[[package]] -name = "ppv-lite86" -version = "0.2.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" - -[[package]] -name = "pretty_env_logger" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "926d36b9553851b8b0005f1275891b392ee4d2d833852c417ed025477350fb9d" -dependencies = [ - "env_logger 0.7.1", - "log", -] - -[[package]] -name = "prettyplease" -version = "0.1.25" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c8646e95016a7a6c4adea95bafa8a16baab64b583356217f2c85db4a39d9a86" -dependencies = [ - "proc-macro2", - "syn 1.0.109", -] - -[[package]] -name = "proc-macro-error" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" -dependencies = [ - "proc-macro-error-attr", - "proc-macro2", - "quote", - "syn 1.0.109", - "version_check", -] - -[[package]] -name = "proc-macro-error-attr" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" -dependencies = [ - "proc-macro2", - "quote", - "version_check", -] - -[[package]] -name = "proc-macro2" -version = "1.0.56" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b63bdb0cd06f1f4dedf69b254734f9b45af66e4a031e42a7480257d9898b435" -dependencies = [ - "unicode-ident", -] - -[[package]] -name = "prost" -version = "0.10.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "71adf41db68aa0daaefc69bb30bcd68ded9b9abaad5d1fbb6304c4fb390e083e" -dependencies = [ - "bytes", - "prost-derive", -] - -[[package]] -name = "prost-derive" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b670f45da57fb8542ebdbb6105a925fe571b67f9e7ed9f47a06a84e72b4e7cc" -dependencies = [ - "anyhow", - "itertools", - "proc-macro2", - "quote", - "syn 1.0.109", -] - -[[package]] -name = "prost-types" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d0a014229361011dc8e69c8a1ec6c2e8d0f2af7c91e3ea3f5b2170298461e68" -dependencies = [ - "bytes", - "prost", -] - -[[package]] -name = "quick-error" -version = "1.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" - -[[package]] -name = "quick-error" -version = "2.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3" - -[[package]] -name = "quote" -version = "1.0.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f4f29d145265ec1c483c7c654450edde0bfe043d3938d6972630663356d9500" -dependencies = [ - "proc-macro2", -] - -[[package]] -name = "radium" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" - -[[package]] -name = "rand" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" -dependencies = [ - "getrandom 0.1.16", - "libc", - "rand_chacha 0.2.2", - "rand_core 0.5.1", - "rand_hc", -] - -[[package]] -name = "rand" -version = "0.8.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" -dependencies = [ - "libc", - "rand_chacha 0.3.1", - "rand_core 0.6.4", -] - -[[package]] -name = "rand_chacha" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402" -dependencies = [ - "ppv-lite86", - "rand_core 0.5.1", -] - -[[package]] -name = "rand_chacha" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" -dependencies = [ - "ppv-lite86", - "rand_core 0.6.4", -] - -[[package]] -name = "rand_core" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" -dependencies = [ - "getrandom 0.1.16", -] - -[[package]] -name = "rand_core" -version = "0.6.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" -dependencies = [ - "getrandom 0.2.9", -] - -[[package]] -name = "rand_distr" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9e9532ada3929fb8b2e9dbe28d1e06c9b2cc65813f074fcb6bd5fbefeff9d56" -dependencies = [ - "num-traits", - "rand 0.7.3", -] - -[[package]] -name = "rand_hc" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" -dependencies = [ - "rand_core 0.5.1", -] - -[[package]] -name = "rayon" -version = "1.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d2df5196e37bcc87abebc0053e20787d73847bb33134a69841207dd0a47f03b" -dependencies = [ - "either", - "rayon-core", -] - -[[package]] -name = "rayon-core" -version = "1.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b8f95bd6966f5c87776639160a66bd8ab9895d9d4ab01ddba9fc60661aebe8d" -dependencies = [ - "crossbeam-channel", - "crossbeam-deque", - "crossbeam-utils", - "num_cpus", -] - -[[package]] -name = "redox_syscall" -version = "0.2.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" -dependencies = [ - "bitflags", -] - -[[package]] -name = "redox_syscall" -version = "0.3.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29" -dependencies = [ - "bitflags", -] - -[[package]] -name = "redox_users" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b" -dependencies = [ - "getrandom 0.2.9", - "redox_syscall 0.2.16", - "thiserror", -] - -[[package]] -name = "regex" -version = "1.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af83e617f331cc6ae2da5443c602dfa5af81e517212d9d611a5b3ba1777b5370" -dependencies = [ - "aho-corasick", - "memchr", - "regex-syntax", -] - -[[package]] -name = "regex-syntax" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5996294f19bd3aae0453a862ad728f60e6600695733dd5df01da90c54363a3c" - -[[package]] -name = "reqwest" -version = "0.11.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13293b639a097af28fc8a90f22add145a9c954e49d77da06263d58cf44d5fb91" -dependencies = [ - "base64 0.21.0", - "bytes", - "encoding_rs", - "futures-core", - "futures-util", - "h2", - "http", - "http-body", - "hyper", - "hyper-tls", - "ipnet", - "js-sys", - "log", - "mime", - "native-tls", - "once_cell", - "percent-encoding", - "pin-project-lite", - "serde", - "serde_json", - "serde_urlencoded", - "tokio", - "tokio-native-tls", - "tower-service", - "url", - "wasm-bindgen", - "wasm-bindgen-futures", - "web-sys", - "winreg", -] - -[[package]] -name = "rfc6979" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96ef608575f6392792f9ecf7890c00086591d29a83910939d430753f7c050525" -dependencies = [ - "crypto-bigint", - "hmac 0.11.0", - "zeroize", -] - -[[package]] -name = "ring" -version = "0.16.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc" -dependencies = [ - "cc", - "libc", - "once_cell", - "spin 0.5.2", - "untrusted", - "web-sys", - "winapi", -] - -[[package]] -name = "ripemd160" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2eca4ecc81b7f313189bf73ce724400a07da2a6dac19588b03c8bd76a2dcc251" -dependencies = [ - "block-buffer 0.9.0", - "digest 0.9.0", - "opaque-debug 0.3.0", -] - -[[package]] -name = "rustc_version" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" -dependencies = [ - "semver 1.0.17", -] - -[[package]] -name = "rustix" -version = "0.37.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "acf8729d8542766f1b2cf77eb034d52f40d375bb8b615d0b147089946e16613d" -dependencies = [ - "bitflags", - "errno", - "io-lifetimes", - "libc", - "linux-raw-sys", - "windows-sys 0.48.0", -] - -[[package]] -name = "rustls" -version = "0.19.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35edb675feee39aec9c99fa5ff985081995a06d594114ae14cbe797ad7b7a6d7" -dependencies = [ - "base64 0.13.1", - "log", - "ring", - "sct 0.6.1", - "webpki 0.21.4", -] - -[[package]] -name = "rustls" -version = "0.20.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fff78fc74d175294f4e83b28343315ffcfb114b156f0185e9741cb5570f50e2f" -dependencies = [ - "log", - "ring", - "sct 0.7.0", - "webpki 0.22.0", -] - -[[package]] -name = "rustls-native-certs" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a07b7c1885bd8ed3831c289b7870b13ef46fe0e856d288c30d9cc17d75a2092" -dependencies = [ - "openssl-probe", - "rustls 0.19.1", - "schannel", - "security-framework", -] - -[[package]] -name = "rustls-pemfile" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d194b56d58803a43635bdc398cd17e383d6f71f9182b9a192c127ca42494a59b" -dependencies = [ - "base64 0.21.0", -] - -[[package]] -name = "rustversion" -version = "1.0.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f3208ce4d8448b3f3e7d168a73f5e0c43a61e32930de3bceeccedb388b6bf06" - -[[package]] -name = "ryu" -version = "1.0.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f91339c0467de62360649f8d3e185ca8de4224ff281f66000de5eb2a77a79041" - -[[package]] -name = "safer-ffi" -version = "0.1.0-rc1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7270b08a2af50909969cfc28a239cfdda9ab1b3df74749b265c46956183e5bf7" -dependencies = [ - "inventory", - "libc", - "macro_rules_attribute", - "paste", - "safer_ffi-proc_macros", - "scopeguard", - "uninit", - "unwind_safe", - "with_builtin_macros", -] - -[[package]] -name = "safer_ffi-proc_macros" -version = "0.1.0-rc1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1438fb328c52f5148ea02a317603efd802f2cd6eb9e721adfd51b94bb55d9ddb" -dependencies = [ - "macro_rules_attribute", - "prettyplease", - "proc-macro2", - "quote", - "syn 1.0.109", -] - -[[package]] -name = "same-file" -version = "1.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" -dependencies = [ - "winapi-util", -] - -[[package]] -name = "schannel" -version = "0.1.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "713cfb06c7059f3588fb8044c0fad1d09e3c01d225e25b9220dbfdcf16dbb1b3" -dependencies = [ - "windows-sys 0.42.0", -] - -[[package]] -name = "schemars" -version = "0.8.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02c613288622e5f0c3fdc5dbd4db1c5fbe752746b1d1a56a0630b78fd00de44f" -dependencies = [ - "dyn-clone", - "indexmap", - "schemars_derive", - "serde", - "serde_json", -] - -[[package]] -name = "schemars_derive" -version = "0.8.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "109da1e6b197438deb6db99952990c7f959572794b80ff93707d55a232545e7c" -dependencies = [ - "proc-macro2", - "quote", - "serde_derive_internals", - "syn 1.0.109", -] - -[[package]] -name = "scopeguard" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" - -[[package]] -name = "sct" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b362b83898e0e69f38515b82ee15aa80636befe47c3b6d3d89a911e78fc228ce" -dependencies = [ - "ring", - "untrusted", -] - -[[package]] -name = "sct" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d53dcdb7c9f8158937a7981b48accfd39a43af418591a5d008c7b22b5e1b7ca4" -dependencies = [ - "ring", - "untrusted", -] - -[[package]] -name = "sec1" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08da66b8b0965a5555b6bd6639e68ccba85e1e2506f5fbb089e93f8a04e1a2d1" -dependencies = [ - "der", - "generic-array 0.14.7", - "pkcs8", - "subtle 2.4.1", - "zeroize", -] - -[[package]] -name = "security-framework" -version = "2.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a332be01508d814fed64bf28f798a146d73792121129962fdf335bb3c49a4254" -dependencies = [ - "bitflags", - "core-foundation", - "core-foundation-sys", - "libc", - "security-framework-sys", -] - -[[package]] -name = "security-framework-sys" -version = "2.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31c9bb296072e961fcbd8853511dd39c2d8be2deb1e17c6860b1d30732b323b4" -dependencies = [ - "core-foundation-sys", - "libc", -] - -[[package]] -name = "semver" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f301af10236f6df4160f7c3f04eec6dbc70ace82d23326abad5edee88801c6b6" -dependencies = [ - "semver-parser", -] - -[[package]] -name = "semver" -version = "1.0.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bebd363326d05ec3e2f532ab7660680f3b02130d780c299bca73469d521bc0ed" - -[[package]] -name = "semver-parser" -version = "0.10.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00b0bef5b7f9e0df16536d3961cfb6e84331c065b4066afb39768d0e319411f7" -dependencies = [ - "pest", -] - -[[package]] -name = "serde" -version = "1.0.163" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2113ab51b87a539ae008b5c6c02dc020ffa39afd2d83cffcb3f4eb2722cebec2" -dependencies = [ - "serde_derive", -] - -[[package]] -name = "serde-json-wasm" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "479b4dbc401ca13ee8ce902851b834893251404c4f3c65370a49e047a6be09a5" -dependencies = [ - "serde", -] - -[[package]] -name = "serde_bytes" -version = "0.11.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "416bda436f9aab92e02c8e10d49a15ddd339cea90b6e340fe51ed97abb548294" -dependencies = [ - "serde", -] - -[[package]] -name = "serde_derive" -version = "1.0.163" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c805777e3930c8883389c602315a24224bcc738b63905ef87cd1420353ea93e" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.15", -] - -[[package]] -name = "serde_derive_internals" -version = "0.26.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85bf8229e7920a9f636479437026331ce11aa132b4dde37d121944a44d6e5f3c" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.109", -] - -[[package]] -name = "serde_json" -version = "1.0.96" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "057d394a50403bcac12672b2b18fb387ab6d289d957dab67dd201875391e52f1" -dependencies = [ - "itoa", - "ryu", - "serde", -] - -[[package]] -name = "serde_repr" -version = "0.1.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bcec881020c684085e55a25f7fd888954d56609ef363479dc5a1305eb0d40cab" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.15", -] - -[[package]] -name = "serde_spanned" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93107647184f6027e3b7dcb2e11034cf95ffa1e3a682c67951963ac69c1c007d" -dependencies = [ - "serde", -] - -[[package]] -name = "serde_urlencoded" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" -dependencies = [ - "form_urlencoded", - "itoa", - "ryu", - "serde", -] - -[[package]] -name = "sha-1" -version = "0.9.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99cd6713db3cf16b6c84e06321e049a9b9f699826e16096d23bbcc44d15d51a6" -dependencies = [ - "block-buffer 0.9.0", - "cfg-if", - "cpufeatures", - "digest 0.9.0", - "opaque-debug 0.3.0", -] - -[[package]] -name = "sha1" -version = "0.10.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f04293dc80c3993519f2d7f6f511707ee7094fe0c6d3406feb330cdb3540eba3" -dependencies = [ - "cfg-if", - "cpufeatures", - "digest 0.10.6", -] - -[[package]] -name = "sha2" -version = "0.9.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d58a1e1bf39749807d89cf2d98ac2dfa0ff1cb3faa38fbb64dd88ac8013d800" -dependencies = [ - "block-buffer 0.9.0", - "cfg-if", - "cpufeatures", - "digest 0.9.0", - "opaque-debug 0.3.0", -] - -[[package]] -name = "sha2" -version = "0.10.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82e6b795fe2e3b1e845bafcb27aa35405c4d47cdfc92af5fc8d3002f76cebdc0" -dependencies = [ - "cfg-if", - "cpufeatures", - "digest 0.10.6", -] - -[[package]] -name = "sha3" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f81199417d4e5de3f04b1e871023acea7389672c4135918f05aa9cbf2f2fa809" -dependencies = [ - "block-buffer 0.9.0", - "digest 0.9.0", - "keccak", - "opaque-debug 0.3.0", -] - -[[package]] -name = "signal-hook-registry" -version = "1.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1" -dependencies = [ - "libc", -] - -[[package]] -name = "signature" -version = "1.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02658e48d89f2bec991f9a78e69cfa4c316f8d6a6c4ec12fae1aeb263d486788" -dependencies = [ - "digest 0.9.0", - "rand_core 0.6.4", -] - -[[package]] -name = "slab" -version = "0.4.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6528351c9bc8ab22353f9d776db39a20288e8d6c37ef8cfe3317cf875eecfc2d" -dependencies = [ - "autocfg", -] - -[[package]] -name = "smallvec" -version = "1.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0" - -[[package]] -name = "socket2" -version = "0.4.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64a4a911eed85daf18834cfaa86a79b7d266ff93ff5ba14005426219480ed662" -dependencies = [ - "libc", - "winapi", -] - -[[package]] -name = "sphinx-packet" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc43eda802856ee82a7555c7b75ceb9e07451741c7a2f5f23d036020e01189d4" -dependencies = [ - "aes 0.7.5", - "arrayref", - "blake2", - "bs58", - "byteorder", - "chacha", - "curve25519-dalek", - "digest 0.9.0", - "hkdf 0.11.0", - "hmac 0.11.0", - "lioness", - "log", - "rand 0.7.3", - "rand_distr", - "sha2 0.9.9", - "subtle 2.4.1", -] - -[[package]] -name = "sphinx-packet" -version = "0.1.0" -source = "git+https://github.com/nymtech/sphinx.git#ca107d94360cdf8bbfbdb12fe5320ed74f80e40c" -dependencies = [ - "aes 0.7.5", - "arrayref", - "blake2", - "bs58", - "byteorder", - "chacha", - "curve25519-dalek", - "digest 0.9.0", - "hkdf 0.11.0", - "hmac 0.11.0", - "lioness", - "log", - "rand 0.7.3", - "rand_distr", - "sha2 0.9.9", - "subtle 2.4.1", -] - -[[package]] -name = "spin" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" - -[[package]] -name = "spin" -version = "0.9.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" -dependencies = [ - "lock_api", -] - -[[package]] -name = "spki" -version = "0.5.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44d01ac02a6ccf3e07db148d2be087da624fea0221a16152ed01f0496a6b0a27" -dependencies = [ - "base64ct", - "der", -] - -[[package]] -name = "sqlformat" -version = "0.1.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4b7922be017ee70900be125523f38bdd644f4f06a1b16e8fa5a8ee8c34bffd4" -dependencies = [ - "itertools", - "nom", - "unicode_categories", -] - -[[package]] -name = "sqlformat" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c12bc9199d1db8234678b7051747c07f517cdcf019262d1847b94ec8b1aee3e" -dependencies = [ - "itertools", - "nom", - "unicode_categories", -] - -[[package]] -name = "sqlx" -version = "0.5.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "551873805652ba0d912fec5bbb0f8b4cdd96baf8e2ebf5970e5671092966019b" -dependencies = [ - "sqlx-core 0.5.13", - "sqlx-macros 0.5.13", -] - -[[package]] -name = "sqlx" -version = "0.6.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8de3b03a925878ed54a954f621e64bf55a3c1bd29652d0d1a17830405350188" -dependencies = [ - "sqlx-core 0.6.3", - "sqlx-macros 0.6.3", -] - -[[package]] -name = "sqlx-core" -version = "0.5.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e48c61941ccf5ddcada342cd59e3e5173b007c509e1e8e990dafc830294d9dc5" -dependencies = [ - "ahash", - "atoi 0.4.0", - "bitflags", - "byteorder", - "bytes", - "crc 2.1.0", - "crossbeam-queue", - "either", - "event-listener", - "flume", - "futures-channel", - "futures-core", - "futures-executor", - "futures-intrusive", - "futures-util", - "hashlink 0.7.0", - "hex", - "indexmap", - "itoa", - "libc", - "libsqlite3-sys", - "log", - "memchr", - "once_cell", - "paste", - "percent-encoding", - "rustls 0.19.1", - "sha2 0.10.6", - "smallvec", - "sqlformat 0.1.8", - "sqlx-rt 0.5.13", - "stringprep", - "thiserror", - "tokio-stream", - "url", - "webpki 0.21.4", - "webpki-roots 0.21.1", -] - -[[package]] -name = "sqlx-core" -version = "0.6.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa8241483a83a3f33aa5fff7e7d9def398ff9990b2752b6c6112b83c6d246029" -dependencies = [ - "ahash", - "atoi 1.0.0", - "bitflags", - "byteorder", - "bytes", - "crc 3.0.1", - "crossbeam-queue", - "dotenvy", - "either", - "event-listener", - "flume", - "futures-channel", - "futures-core", - "futures-executor", - "futures-intrusive", - "futures-util", - "hashlink 0.8.1", - "hex", - "indexmap", - "itoa", - "libc", - "libsqlite3-sys", - "log", - "memchr", - "once_cell", - "paste", - "percent-encoding", - "rustls 0.20.8", - "rustls-pemfile", - "sha2 0.10.6", - "smallvec", - "sqlformat 0.2.1", - "sqlx-rt 0.6.3", - "stringprep", - "thiserror", - "tokio-stream", - "url", - "webpki-roots 0.22.6", -] - -[[package]] -name = "sqlx-macros" -version = "0.5.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc0fba2b0cae21fc00fe6046f8baa4c7fcb49e379f0f592b04696607f69ed2e1" -dependencies = [ - "dotenv", - "either", - "heck", - "once_cell", - "proc-macro2", - "quote", - "sha2 0.10.6", - "sqlx-core 0.5.13", - "sqlx-rt 0.5.13", - "syn 1.0.109", - "url", -] - -[[package]] -name = "sqlx-macros" -version = "0.6.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9966e64ae989e7e575b19d7265cb79d7fc3cbbdf179835cb0d716f294c2049c9" -dependencies = [ - "dotenvy", - "either", - "heck", - "once_cell", - "proc-macro2", - "quote", - "sha2 0.10.6", - "sqlx-core 0.6.3", - "sqlx-rt 0.6.3", - "syn 1.0.109", - "url", -] - -[[package]] -name = "sqlx-rt" -version = "0.5.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4db708cd3e459078f85f39f96a00960bd841f66ee2a669e90bf36907f5a79aae" -dependencies = [ - "once_cell", - "tokio", - "tokio-rustls 0.22.0", -] - -[[package]] -name = "sqlx-rt" -version = "0.6.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "804d3f245f894e61b1e6263c84b23ca675d96753b5abfd5cc8597d86806e8024" -dependencies = [ - "once_cell", - "tokio", - "tokio-rustls 0.23.4", -] - -[[package]] -name = "static_assertions" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" - -[[package]] -name = "stringprep" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ee348cb74b87454fff4b551cbf727025810a004f88aeacae7f85b87f4e9a1c1" -dependencies = [ - "unicode-bidi", - "unicode-normalization", -] - -[[package]] -name = "strsim" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" - -[[package]] -name = "subtle" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d67a5a62ba6e01cb2192ff309324cb4875d0c451d55fe2319433abe7a05a8ee" - -[[package]] -name = "subtle" -version = "2.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601" - -[[package]] -name = "subtle-encoding" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7dcb1ed7b8330c5eed5441052651dd7a12c75e2ed88f2ec024ae1fa3a5e59945" -dependencies = [ - "zeroize", -] - -[[package]] -name = "syn" -version = "1.0.109" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - -[[package]] -name = "syn" -version = "2.0.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a34fcf3e8b60f57e6a14301a2e916d323af98b0ea63c599441eec8558660c822" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - -[[package]] -name = "tap" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" - -[[package]] -name = "tempfile" -version = "3.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9fbec84f381d5795b08656e4912bec604d162bff9291d6189a78f4c8ab87998" -dependencies = [ - "cfg-if", - "fastrand", - "redox_syscall 0.3.5", - "rustix", - "windows-sys 0.45.0", -] - -[[package]] -name = "tendermint" -version = "0.23.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ca881fa4dedd2b46334f13be7fbc8cc1549ba4be5a833fe4e73d1a1baaf7949" -dependencies = [ - "async-trait", - "bytes", - "ed25519", - "ed25519-dalek", - "flex-error", - "futures", - "k256", - "num-traits", - "once_cell", - "prost", - "prost-types", - "ripemd160", - "serde", - "serde_bytes", - "serde_json", - "serde_repr", - "sha2 0.9.9", - "signature", - "subtle 2.4.1", - "subtle-encoding", - "tendermint-proto", - "time", - "zeroize", -] - -[[package]] -name = "tendermint-config" -version = "0.23.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6c56ee93f4e9b7e7daba86d171f44572e91b741084384d0ae00df7991873dfd" -dependencies = [ - "flex-error", - "serde", - "serde_json", - "tendermint", - "toml 0.5.11", - "url", -] - -[[package]] -name = "tendermint-proto" -version = "0.23.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b71f925d74903f4abbdc4af0110635a307b3cb05b175fdff4a7247c14a4d0874" -dependencies = [ - "bytes", - "flex-error", - "num-derive", - "num-traits", - "prost", - "prost-types", - "serde", - "serde_bytes", - "subtle-encoding", - "time", -] - -[[package]] -name = "tendermint-rpc" -version = "0.23.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a13e63f57ee05a1e927887191c76d1b139de9fa40c180b9f8727ee44377242a6" -dependencies = [ - "async-trait", - "bytes", - "flex-error", - "futures", - "getrandom 0.2.9", - "http", - "hyper", - "hyper-proxy", - "hyper-rustls", - "peg", - "pin-project", - "serde", - "serde_bytes", - "serde_json", - "subtle-encoding", - "tendermint", - "tendermint-config", - "tendermint-proto", - "thiserror", - "time", - "tokio", - "tracing", - "url", - "uuid", - "walkdir", -] - -[[package]] -name = "termcolor" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be55cf8942feac5c765c2c993422806843c9a9a45d4d5c407ad6dd2ea95eb9b6" -dependencies = [ - "winapi-util", -] - -[[package]] -name = "thiserror" -version = "1.0.40" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "978c9a314bd8dc99be594bc3c175faaa9794be04a5a5e153caba6915336cebac" -dependencies = [ - "thiserror-impl", -] - -[[package]] -name = "thiserror-impl" -version = "1.0.40" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9456a42c5b0d803c8cd86e73dd7cc9edd429499f37a3550d286d5e86720569f" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.15", -] - -[[package]] -name = "time" -version = "0.3.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f3403384eaacbca9923fa06940178ac13e4edb725486d70e8e15881d0c836cc" -dependencies = [ - "itoa", - "js-sys", - "serde", - "time-core", - "time-macros", -] - -[[package]] -name = "time-core" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7300fbefb4dadc1af235a9cef3737cea692a9d97e1b9cbcd4ebdae6f8868e6fb" - -[[package]] -name = "time-macros" -version = "0.2.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "372950940a5f07bf38dbe211d7283c9e6d7327df53794992d293e534c733d09b" -dependencies = [ - "time-core", -] - -[[package]] -name = "tinyvec" -version = "1.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" -dependencies = [ - "tinyvec_macros", -] - -[[package]] -name = "tinyvec_macros" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" - -[[package]] -name = "tokio" -version = "1.28.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0aa32867d44e6f2ce3385e89dceb990188b8bb0fb25b0cf576647a6f98ac5105" -dependencies = [ - "autocfg", - "bytes", - "libc", - "mio", - "num_cpus", - "pin-project-lite", - "signal-hook-registry", - "socket2", - "tokio-macros", - "windows-sys 0.48.0", -] - -[[package]] -name = "tokio-macros" -version = "2.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.15", -] - -[[package]] -name = "tokio-native-tls" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" -dependencies = [ - "native-tls", - "tokio", -] - -[[package]] -name = "tokio-rustls" -version = "0.22.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc6844de72e57df1980054b38be3a9f4702aba4858be64dd700181a8a6d0e1b6" -dependencies = [ - "rustls 0.19.1", - "tokio", - "webpki 0.21.4", -] - -[[package]] -name = "tokio-rustls" -version = "0.23.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c43ee83903113e03984cb9e5cebe6c04a5116269e900e3ddba8f068a62adda59" -dependencies = [ - "rustls 0.20.8", - "tokio", - "webpki 0.22.0", -] - -[[package]] -name = "tokio-stream" -version = "0.1.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "397c988d37662c7dda6d2208364a706264bf3d6138b11d436cbac0ad38832842" -dependencies = [ - "futures-core", - "pin-project-lite", - "tokio", - "tokio-util", -] - -[[package]] -name = "tokio-tungstenite" -version = "0.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e96bb520beab540ab664bd5a9cfeaa1fcd846fa68c830b42e2c8963071251d2" -dependencies = [ - "futures-util", - "log", - "pin-project", - "tokio", - "tungstenite", -] - -[[package]] -name = "tokio-util" -version = "0.7.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "806fe8c2c87eccc8b3267cbae29ed3ab2d0bd37fca70ab622e46aaa9375ddb7d" -dependencies = [ - "bytes", - "futures-core", - "futures-sink", - "pin-project-lite", - "slab", - "tokio", - "tracing", -] - -[[package]] -name = "toml" -version = "0.5.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234" -dependencies = [ - "serde", -] - -[[package]] -name = "toml" -version = "0.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6135d499e69981f9ff0ef2167955a5333c35e36f6937d382974566b3d5b94ec" -dependencies = [ - "serde", - "serde_spanned", - "toml_datetime", - "toml_edit", -] - -[[package]] -name = "toml_datetime" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a76a9312f5ba4c2dec6b9161fdf25d87ad8a09256ccea5a556fef03c706a10f" -dependencies = [ - "serde", -] - -[[package]] -name = "toml_edit" -version = "0.19.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2380d56e8670370eee6566b0bfd4265f65b3f432e8c6d85623f728d4fa31f739" -dependencies = [ - "indexmap", - "serde", - "serde_spanned", - "toml_datetime", - "winnow", -] - -[[package]] -name = "tower-service" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" - -[[package]] -name = "tracing" -version = "0.1.37" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8" -dependencies = [ - "cfg-if", - "pin-project-lite", - "tracing-core", -] - -[[package]] -name = "tracing-core" -version = "0.1.30" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24eb03ba0eab1fd845050058ce5e616558e8f8d8fca633e6b163fe25c797213a" -dependencies = [ - "once_cell", -] - -[[package]] -name = "try-lock" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3528ecfd12c466c6f163363caf2d02a71161dd5e1cc6ae7b34207ea2d42d81ed" - -[[package]] -name = "tungstenite" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fe8dada8c1a3aeca77d6b51a4f1314e0f4b8e438b7b1b71e3ddaca8080e4093" -dependencies = [ - "base64 0.13.1", - "byteorder", - "bytes", - "http", - "httparse", - "input_buffer", - "log", - "rand 0.8.5", - "sha-1", - "thiserror", - "url", - "utf-8", -] - -[[package]] -name = "typenum" -version = "1.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba" - -[[package]] -name = "ucd-trie" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e79c4d996edb816c91e4308506774452e55e95c3c9de07b6729e17e15a5ef81" - -[[package]] -name = "uint" -version = "0.9.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76f64bba2c53b04fcab63c01a7d7427eadc821e3bc48c34dc9ba29c501164b52" -dependencies = [ - "byteorder", - "crunchy", - "hex", - "static_assertions", -] - -[[package]] -name = "unicode-bidi" -version = "0.3.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460" - -[[package]] -name = "unicode-ident" -version = "1.0.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5464a87b239f13a63a501f2701565754bae92d243d4bb7eb12f6d57d2269bf4" - -[[package]] -name = "unicode-normalization" -version = "0.1.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921" -dependencies = [ - "tinyvec", -] - -[[package]] -name = "unicode-segmentation" -version = "1.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36" - -[[package]] -name = "unicode_categories" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39ec24b3121d976906ece63c9daad25b85969647682eee313cb5779fdd69e14e" - -[[package]] -name = "uninit" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e130f2ed46ca5d8ec13c7ff95836827f92f5f5f37fd2b2bf16f33c408d98bb6" -dependencies = [ - "extension-traits", -] - -[[package]] -name = "universal-hash" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d3160b73c9a19f7e2939a2fdad446c57c1bbbbf4d919d3213ff1267a580d8b5" -dependencies = [ - "crypto-common", - "subtle 2.4.1", -] - -[[package]] -name = "untrusted" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" - -[[package]] -name = "unwind_safe" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0976c77def3f1f75c4ef892a292c31c0bbe9e3d0702c63044d7c76db298171a3" - -[[package]] -name = "url" -version = "2.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d68c799ae75762b8c3fe375feb6600ef5602c883c5d21eb51c09f22b83c4643" -dependencies = [ - "form_urlencoded", - "idna", - "percent-encoding", - "serde", -] - -[[package]] -name = "utf-8" -version = "0.7.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" - -[[package]] -name = "utf8parse" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" - -[[package]] -name = "uuid" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc5cf98d8186244414c848017f0e2676b3fcb46807f6668a97dfe67359a3c4b7" - -[[package]] -name = "vcpkg" -version = "0.2.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" - -[[package]] -name = "vergen" -version = "7.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "447f9238a4553957277b3ee09d80babeae0811f1b3baefb093de1c0448437a37" -dependencies = [ - "anyhow", - "cfg-if", - "enum-iterator", - "getset", - "git2", - "rustc_version", - "rustversion", - "thiserror", - "time", -] - -[[package]] -name = "version_check" -version = "0.9.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" - -[[package]] -name = "walkdir" -version = "2.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36df944cda56c7d8d8b7496af378e6b16de9284591917d307c9b4d313c44e698" -dependencies = [ - "same-file", - "winapi-util", -] - -[[package]] -name = "want" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ce8a968cb1cd110d136ff8b819a556d6fb6d919363c61534f6860c7eb172ba0" -dependencies = [ - "log", - "try-lock", -] - -[[package]] -name = "wasi" -version = "0.9.0+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" - -[[package]] -name = "wasi" -version = "0.11.0+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" - -[[package]] -name = "wasm-bindgen" -version = "0.2.83" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eaf9f5aceeec8be17c128b2e93e031fb8a4d469bb9c4ae2d7dc1888b26887268" -dependencies = [ - "cfg-if", - "wasm-bindgen-macro", -] - -[[package]] -name = "wasm-bindgen-backend" -version = "0.2.83" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c8ffb332579b0557b52d268b91feab8df3615f265d5270fec2a8c95b17c1142" -dependencies = [ - "bumpalo", - "log", - "once_cell", - "proc-macro2", - "quote", - "syn 1.0.109", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-futures" -version = "0.4.33" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23639446165ca5a5de86ae1d8896b737ae80319560fbaa4c2887b7da6e7ebd7d" -dependencies = [ - "cfg-if", - "js-sys", - "wasm-bindgen", - "web-sys", -] - -[[package]] -name = "wasm-bindgen-macro" -version = "0.2.83" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "052be0f94026e6cbc75cdefc9bae13fd6052cdcaf532fa6c45e7ae33a1e6c810" -dependencies = [ - "quote", - "wasm-bindgen-macro-support", -] - -[[package]] -name = "wasm-bindgen-macro-support" -version = "0.2.83" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07bc0c051dc5f23e307b13285f9d75df86bfdf816c5721e573dec1f9b8aa193c" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.109", - "wasm-bindgen-backend", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-shared" -version = "0.2.83" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c38c045535d93ec4f0b4defec448e4291638ee608530863b1e2ba115d4fff7f" - -[[package]] -name = "wasm-timer" -version = "0.2.5" -source = "git+https://github.com/mmsinclair/wasm-timer?rev=b9d1a54ad514c2f230a026afe0dde341e98cd7b6#b9d1a54ad514c2f230a026afe0dde341e98cd7b6" -dependencies = [ - "futures", - "js-sys", - "parking_lot", - "pin-utils", - "wasm-bindgen", - "wasm-bindgen-futures", - "web-sys", -] - -[[package]] -name = "wasm-utils" -version = "0.1.0" -dependencies = [ - "futures", - "getrandom 0.2.9", - "js-sys", - "tungstenite", - "wasm-bindgen", - "wasm-bindgen-futures", - "web-sys", -] - -[[package]] -name = "web-sys" -version = "0.3.60" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bcda906d8be16e728fd5adc5b729afad4e444e106ab28cd1c7256e54fa61510f" -dependencies = [ - "js-sys", - "wasm-bindgen", -] - -[[package]] -name = "webpki" -version = "0.21.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8e38c0608262c46d4a56202ebabdeb094cef7e560ca7a226c6bf055188aa4ea" -dependencies = [ - "ring", - "untrusted", -] - -[[package]] -name = "webpki" -version = "0.22.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f095d78192e208183081cc07bc5515ef55216397af48b873e5edcd72637fa1bd" -dependencies = [ - "ring", - "untrusted", -] - -[[package]] -name = "webpki-roots" -version = "0.21.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aabe153544e473b775453675851ecc86863d2a81d786d741f6b76778f2a48940" -dependencies = [ - "webpki 0.21.4", -] - -[[package]] -name = "webpki-roots" -version = "0.22.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6c71e40d7d2c34a5106301fb632274ca37242cd0c9d3e64dbece371a40a2d87" -dependencies = [ - "webpki 0.22.0", -] - -[[package]] -name = "winapi" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" -dependencies = [ - "winapi-i686-pc-windows-gnu", - "winapi-x86_64-pc-windows-gnu", -] - -[[package]] -name = "winapi-i686-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" - -[[package]] -name = "winapi-util" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" -dependencies = [ - "winapi", -] - -[[package]] -name = "winapi-x86_64-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" - -[[package]] -name = "windows-sys" -version = "0.42.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" -dependencies = [ - "windows_aarch64_gnullvm 0.42.2", - "windows_aarch64_msvc 0.42.2", - "windows_i686_gnu 0.42.2", - "windows_i686_msvc 0.42.2", - "windows_x86_64_gnu 0.42.2", - "windows_x86_64_gnullvm 0.42.2", - "windows_x86_64_msvc 0.42.2", -] - -[[package]] -name = "windows-sys" -version = "0.45.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" -dependencies = [ - "windows-targets 0.42.2", -] - -[[package]] -name = "windows-sys" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" -dependencies = [ - "windows-targets 0.48.0", -] - -[[package]] -name = "windows-targets" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" -dependencies = [ - "windows_aarch64_gnullvm 0.42.2", - "windows_aarch64_msvc 0.42.2", - "windows_i686_gnu 0.42.2", - "windows_i686_msvc 0.42.2", - "windows_x86_64_gnu 0.42.2", - "windows_x86_64_gnullvm 0.42.2", - "windows_x86_64_msvc 0.42.2", -] - -[[package]] -name = "windows-targets" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b1eb6f0cd7c80c79759c929114ef071b87354ce476d9d94271031c0497adfd5" -dependencies = [ - "windows_aarch64_gnullvm 0.48.0", - "windows_aarch64_msvc 0.48.0", - "windows_i686_gnu 0.48.0", - "windows_i686_msvc 0.48.0", - "windows_x86_64_gnu 0.48.0", - "windows_x86_64_gnullvm 0.48.0", - "windows_x86_64_msvc 0.48.0", -] - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3" - -[[package]] -name = "windows_i686_gnu" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" - -[[package]] -name = "windows_i686_gnu" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241" - -[[package]] -name = "windows_i686_msvc" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" - -[[package]] -name = "windows_i686_msvc" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" - -[[package]] -name = "winnow" -version = "0.4.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61de7bac303dc551fe038e2b3cef0f571087a47571ea6e79a87692ac99b99699" -dependencies = [ - "memchr", -] - -[[package]] -name = "winreg" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80d0f4e272c85def139476380b12f9ac60926689dd2e01d4923222f40580869d" -dependencies = [ - "winapi", -] - -[[package]] -name = "with_builtin_macros" -version = "0.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a59d55032495429b87f9d69954c6c8602e4d3f3e0a747a12dea6b0b23de685da" -dependencies = [ - "with_builtin_macros-proc_macros", -] - -[[package]] -name = "with_builtin_macros-proc_macros" -version = "0.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15bd7679c15e22924f53aee34d4e448c45b674feb6129689af88593e129f8f42" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.109", -] - -[[package]] -name = "wyz" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed" -dependencies = [ - "tap", -] - -[[package]] -name = "x25519-dalek" -version = "1.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a0c105152107e3b96f6a00a65e86ce82d9b125230e1c4302940eca58ff71f4f" -dependencies = [ - "curve25519-dalek", - "rand_core 0.5.1", - "zeroize", -] - -[[package]] -name = "zeroize" -version = "1.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a0956f1ba7c7909bfb66c2e9e4124ab6f6482560f6628b5aaeba39207c9aad9" -dependencies = [ - "zeroize_derive", -] - -[[package]] -name = "zeroize_derive" -version = "1.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.15", -] diff --git a/sdk/lib/socks5-listener/Cargo.toml b/sdk/lib/socks5-listener/Cargo.toml index 793b9bd7dc..6960745920 100644 --- a/sdk/lib/socks5-listener/Cargo.toml +++ b/sdk/lib/socks5-listener/Cargo.toml @@ -26,7 +26,7 @@ nym-config-common = { path = "../../../common/config", package = "nym-config" } nym-credential-storage = { path = "../../../common/credential-storage" } nym-crypto = { path = "../../../common/crypto" } nym-socks5-client-core = { path = "../../../common/socks5-client-core", default-features = false } -openssl = { version = "0.10", features = ["vendored"] } +openssl = { version = "^0.10.55", features = ["vendored"] } serde = { workspace = true } tokio = { workspace = true, features = ["sync", "time"] } log = "0.4.17" diff --git a/sdk/rust/nym-sdk/Cargo.toml b/sdk/rust/nym-sdk/Cargo.toml index 7aaced99aa..97751d4e72 100644 --- a/sdk/rust/nym-sdk/Cargo.toml +++ b/sdk/rust/nym-sdk/Cargo.toml @@ -31,7 +31,7 @@ toml = "0.5.10" [dev-dependencies] dotenvy = { workspace = true } pretty_env_logger = "0.4.0" -reqwest = { version = "0.11", features = ["json", "socks"] } +reqwest = { workspace = true, features = ["json", "socks"] } thiserror = { workspace = true } tokio = { version = "1", features = ["full"] } nym-bin-common = { path = "../../../common/bin-common" } diff --git a/service-providers/network-requester/Cargo.toml b/service-providers/network-requester/Cargo.toml index d1d7367d85..6fa166705d 100644 --- a/service-providers/network-requester/Cargo.toml +++ b/service-providers/network-requester/Cargo.toml @@ -24,7 +24,7 @@ pretty_env_logger = "0.4.0" publicsuffix = "1.5" # Can't update this until bip updates to support newer idna version rand = "0.7.3" regex = "1.8.4" -reqwest = { version = "0.11.11", features = ["json"] } +reqwest = { workspace = true, features = ["json"] } serde = { workspace = true, features = ["derive"] } serde_json = { workspace = true } sqlx = { version = "0.6.1", features = ["runtime-tokio-rustls", "chrono"]} From 204dba1a66c78872e704930c6bb8cd25456344bb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C4=99drzej=20Stuczy=C5=84ski?= Date: Mon, 14 Aug 2023 12:56:46 +0100 Subject: [PATCH 149/361] revert the update of tauri dependencies (#3765) --- nym-wallet/Cargo.lock | 143 +++++++++--------------------------------- 1 file changed, 28 insertions(+), 115 deletions(-) diff --git a/nym-wallet/Cargo.lock b/nym-wallet/Cargo.lock index c549a1a2e7..bc7f8ae20f 100644 --- a/nym-wallet/Cargo.lock +++ b/nym-wallet/Cargo.lock @@ -106,21 +106,6 @@ dependencies = [ "alloc-no-stdlib", ] -[[package]] -name = "android-tzdata" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" - -[[package]] -name = "android_system_properties" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" -dependencies = [ - "libc", -] - [[package]] name = "anstream" version = "0.3.2" @@ -565,13 +550,12 @@ checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" [[package]] name = "cfb" -version = "0.7.3" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d38f2da7a0a2c4ccf0065be06397cc26a81f4e528be095826eee9d4adbb8c60f" +checksum = "74f89d248799e3f15f91b70917f65381062a01bb8e222700ea0e5a7ff9785f9c" dependencies = [ "byteorder", - "fnv", - "uuid 1.4.1", + "uuid 0.8.2", ] [[package]] @@ -609,19 +593,6 @@ dependencies = [ "keystream", ] -[[package]] -name = "chrono" -version = "0.4.26" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec837a71355b28f6556dbd569b37b3f363091c0bd4b2e735674521b4c5fd9bc5" -dependencies = [ - "android-tzdata", - "iana-time-zone", - "num-traits", - "serde", - "winapi", -] - [[package]] name = "cipher" version = "0.3.0" @@ -1206,9 +1177,9 @@ dependencies = [ [[package]] name = "darling" -version = "0.20.3" +version = "0.13.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0209d94da627ab5605dcccf08bb18afa5009cfbef48d8a8b7d7bdbc79be25c5e" +checksum = "a01d95850c592940db9b8194bc39f4bc0e89dee5c4265e4b1807c34a9aba453c" dependencies = [ "darling_core", "darling_macro", @@ -1216,27 +1187,27 @@ dependencies = [ [[package]] name = "darling_core" -version = "0.20.3" +version = "0.13.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "177e3443818124b357d8e76f53be906d60937f0d3a90773a664fa63fa253e621" +checksum = "859d65a907b6852c9361e3185c862aae7fafd2887876799fa55f5f99dc40d610" dependencies = [ "fnv", "ident_case", "proc-macro2", "quote", "strsim", - "syn 2.0.28", + "syn 1.0.109", ] [[package]] name = "darling_macro" -version = "0.20.3" +version = "0.13.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "836a9bbc7ad63342d6d6e7b815ccab164bc77a2d95d84bc3117a8c0d5c98e2d5" +checksum = "9c972679f83bdf9c42bd905396b6c3588a843a17f0f16dfcfa3e2c5d57441835" dependencies = [ "darling_core", "quote", - "syn 2.0.28", + "syn 1.0.109", ] [[package]] @@ -1264,9 +1235,6 @@ name = "deranged" version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7684a49fb1af197853ef7b2ee694bc1f5b4179556f1e5710e1760c5db6f5e929" -dependencies = [ - "serde", -] [[package]] name = "derivative" @@ -2550,29 +2518,6 @@ dependencies = [ "tokio-native-tls", ] -[[package]] -name = "iana-time-zone" -version = "0.1.57" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fad5b825842d2b38bd206f3e81d6957625fd7f0a361e345c30e01a0ae2dd613" -dependencies = [ - "android_system_properties", - "core-foundation-sys", - "iana-time-zone-haiku", - "js-sys", - "wasm-bindgen", - "windows 0.48.0", -] - -[[package]] -name = "iana-time-zone-haiku" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" -dependencies = [ - "cc", -] - [[package]] name = "ico" version = "0.2.0" @@ -2654,14 +2599,13 @@ checksum = "d5477fe2230a79769d8dc68e0eabf5437907c0457a5614a9e8dddb67f65eb65d" dependencies = [ "equivalent", "hashbrown 0.14.0", - "serde", ] [[package]] name = "infer" -version = "0.12.0" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a898e4b7951673fce96614ce5751d13c40fc5674bc2d759288e46c3ab62598b3" +checksum = "20b2b533137b9cad970793453d4f921c2e91312a6d88b1085c07bc15fc51bb3b" dependencies = [ "cfb", ] @@ -2791,19 +2735,7 @@ checksum = "eb3fa5a61630976fc4c353c70297f2e93f1930e3ccee574d59d618ccbd5154ce" dependencies = [ "serde", "serde_json", - "treediff 3.0.2", -] - -[[package]] -name = "json-patch" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f54898088ccb91df1b492cc80029a6fdf1c48ca0db7c6822a8babad69c94658" -dependencies = [ - "serde", - "serde_json", - "thiserror", - "treediff 4.0.2", + "treediff", ] [[package]] @@ -4871,31 +4803,24 @@ dependencies = [ [[package]] name = "serde_with" -version = "3.2.0" +version = "1.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1402f54f9a3b9e2efe71c1cea24e648acce55887983553eeb858cf3115acfd49" +checksum = "678b5a069e50bf00ecd22d0cd8ddf7c236f68581b03db652061ed5eb13a312ff" dependencies = [ - "base64 0.21.2", - "chrono", - "hex", - "indexmap 1.9.3", - "indexmap 2.0.0", "serde", - "serde_json", "serde_with_macros", - "time", ] [[package]] name = "serde_with_macros" -version = "3.2.0" +version = "1.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9197f1ad0e3c173a0222d3c4404fb04c3afe87e962bcb327af73e8301fa203c7" +checksum = "e182d6ec6f05393cc0e5ed1bf81ad6db3a8feedf8ee515ecdd369809bcce8082" dependencies = [ "darling", "proc-macro2", "quote", - "syn 2.0.28", + "syn 1.0.109", ] [[package]] @@ -5403,7 +5328,7 @@ dependencies = [ "anyhow", "cargo_toml", "heck 0.4.1", - "json-patch 0.2.7", + "json-patch", "semver 1.0.18", "serde_json", "tauri-utils", @@ -5419,7 +5344,7 @@ dependencies = [ "base64 0.13.1", "brotli", "ico", - "json-patch 0.2.7", + "json-patch", "plist", "png", "proc-macro2", @@ -5452,9 +5377,9 @@ dependencies = [ [[package]] name = "tauri-runtime" -version = "0.12.2" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc36898ad4acb6c381878acf903c320a36cf29b68b74f6e791d6045b6557128c" +checksum = "c507d954d08ac8705d235bc70ec6975b9054fb95ff7823af72dbb04186596f3b" dependencies = [ "gtk", "http", @@ -5465,7 +5390,6 @@ dependencies = [ "serde_json", "tauri-utils", "thiserror", - "url", "uuid 1.4.1", "webview2-com", "windows 0.39.0", @@ -5473,9 +5397,9 @@ dependencies = [ [[package]] name = "tauri-runtime-wry" -version = "0.12.3" +version = "0.12.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2ebc22bc5566ba33310744fadd86709fa591ed163491b165855474523ac1aab" +checksum = "36b1c5764a41a13176a4599b5b7bd0881bea7d94dfe45e1e755f789b98317e30" dependencies = [ "cocoa", "gtk", @@ -5484,7 +5408,6 @@ dependencies = [ "raw-window-handle", "tauri-runtime", "tauri-utils", - "url", "uuid 1.4.1", "webkit2gtk", "webview2-com", @@ -5494,18 +5417,17 @@ dependencies = [ [[package]] name = "tauri-utils" -version = "1.4.0" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03fc02bb6072bb397e1d473c6f76c953cda48b4a2d0cce605df284aa74a12e84" +checksum = "5abbc109a6eb45127956ffcc26ef0e875d160150ac16cfa45d26a6b2871686f1" dependencies = [ "brotli", "ctor", - "dunce", "glob", "heck 0.4.1", "html5ever", "infer", - "json-patch 1.0.0", + "json-patch", "kuchiki", "memchr", "phf 0.10.1", @@ -5918,15 +5840,6 @@ dependencies = [ "serde_json", ] -[[package]] -name = "treediff" -version = "4.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52984d277bdf2a751072b5df30ec0377febdb02f7696d64c2d7d54630bac4303" -dependencies = [ - "serde_json", -] - [[package]] name = "try-lock" version = "0.2.4" From 0c24f393009c8b3068272e140a75c12bf39d6e7f Mon Sep 17 00:00:00 2001 From: Pierre Dommerc Date: Mon, 14 Aug 2023 14:00:07 +0200 Subject: [PATCH 150/361] chore: remove nym-connect/mobile (#3766) --- .github/workflows/connect-mobile.yml | 72 - .github/workflows/typescript-lint.yml | 4 - nym-connect/mobile/.babelrc | 15 - nym-connect/mobile/.env.sample | 1 - nym-connect/mobile/.eslintrc | 8 - nym-connect/mobile/.nvmrc | 1 - nym-connect/mobile/.prettierrc | 7 - nym-connect/mobile/.storybook/main.js | 73 - .../mobile/.storybook/mocks/tauri/app.js | 8 - .../.storybook/mocks/tauri/clipboard.js | 8 - .../mobile/.storybook/mocks/tauri/event.js | 8 - .../mobile/.storybook/mocks/tauri/index.js | 14 - .../.storybook/mocks/tauri/notification.js | 10 - .../mobile/.storybook/mocks/tauri/window.js | 10 - .../mobile/.storybook/preview-fonts.js | 3 - nym-connect/mobile/.storybook/preview.js | 26 - nym-connect/mobile/CHANGELOG.md | 98 - nym-connect/mobile/README.md | 71 - nym-connect/mobile/dist/.gitkeep | 0 nym-connect/mobile/package.json.bak | 119 - nym-connect/mobile/public/favicon.ico | Bin 5430 -> 0 bytes nym-connect/mobile/public/favicon.png | Bin 1312 -> 0 bytes nym-connect/mobile/public/index.html | 11 - nym-connect/mobile/src-tauri/.gitignore | 5 - nym-connect/mobile/src-tauri/Cargo.lock | 7748 ----------------- nym-connect/mobile/src-tauri/Cargo.toml | 79 - .../gen/android/nym_connect/.editorconfig | 12 - .../gen/android/nym_connect/.gitignore | 16 - .../gen/android/nym_connect/app/.gitignore | 1 - .../android/nym_connect/app/build.gradle.kts | 112 - .../nym_connect/app/proguard-rules.pro | 21 - .../app/src/main/AndroidManifest.xml | 24 - .../net/nymtech/nym_connect/MainActivity.kt | 3 - .../nymtech/nym_connect/generated/.gitkeep | 0 .../drawable-v24/ic_launcher_foreground.xml | 30 - .../res/drawable/ic_launcher_background.xml | 170 - .../app/src/main/res/layout/activity_main.xml | 18 - .../res/mipmap-anydpi-v26/ic_launcher.xml | 5 - .../mipmap-anydpi-v26/ic_launcher_round.xml | 5 - .../src/main/res/mipmap-hdpi/ic_launcher.png | Bin 3357 -> 0 bytes .../mipmap-hdpi/ic_launcher_foreground.png | Bin 5427 -> 0 bytes .../res/mipmap-hdpi/ic_launcher_round.png | Bin 5381 -> 0 bytes .../src/main/res/mipmap-mdpi/ic_launcher.png | Bin 1757 -> 0 bytes .../mipmap-mdpi/ic_launcher_foreground.png | Bin 2753 -> 0 bytes .../res/mipmap-mdpi/ic_launcher_round.png | Bin 2775 -> 0 bytes .../src/main/res/mipmap-xhdpi/ic_launcher.png | Bin 4226 -> 0 bytes .../mipmap-xhdpi/ic_launcher_foreground.png | Bin 8196 -> 0 bytes .../res/mipmap-xhdpi/ic_launcher_round.png | Bin 7277 -> 0 bytes .../main/res/mipmap-xxhdpi/ic_launcher.png | Bin 7664 -> 0 bytes .../mipmap-xxhdpi/ic_launcher_foreground.png | Bin 15273 -> 0 bytes .../res/mipmap-xxhdpi/ic_launcher_round.png | Bin 12925 -> 0 bytes .../main/res/mipmap-xxxhdpi/ic_launcher.png | Bin 11075 -> 0 bytes .../mipmap-xxxhdpi/ic_launcher_foreground.png | Bin 24100 -> 0 bytes .../res/mipmap-xxxhdpi/ic_launcher_round.png | Bin 18743 -> 0 bytes .../app/src/main/res/values-night/themes.xml | 19 - .../app/src/main/res/values/colors.xml | 9 - .../res/values/ic_launcher_background.xml | 4 - .../app/src/main/res/values/strings.xml | 3 - .../app/src/main/res/values/themes.xml | 19 - .../gen/android/nym_connect/build.gradle.kts | 25 - .../nym_connect/buildSrc/build.gradle.kts | 23 - .../nymtech/nym_connect/kotlin/BuildTask.kt | 54 - .../nymtech/nym_connect/kotlin/RustPlugin.kt | 59 - .../gen/android/nym_connect/gradle.properties | 23 - .../gradle/wrapper/gradle-wrapper.jar | Bin 59203 -> 0 bytes .../gradle/wrapper/gradle-wrapper.properties | 6 - .../src-tauri/gen/android/nym_connect/gradlew | 185 - .../gen/android/nym_connect/gradlew.bat | 89 - .../gen/android/nym_connect/settings.gradle | 1 - .../mobile/src-tauri/icons/128x128.png | Bin 11741 -> 0 bytes .../mobile/src-tauri/icons/128x128@2x.png | Bin 25778 -> 0 bytes nym-connect/mobile/src-tauri/icons/32x32.png | Bin 2394 -> 0 bytes .../src-tauri/icons/Square107x107Logo.png | Bin 9407 -> 0 bytes .../src-tauri/icons/Square142x142Logo.png | Bin 12553 -> 0 bytes .../src-tauri/icons/Square150x150Logo.png | Bin 13324 -> 0 bytes .../src-tauri/icons/Square284x284Logo.png | Bin 28970 -> 0 bytes .../src-tauri/icons/Square30x30Logo.png | Bin 2212 -> 0 bytes .../src-tauri/icons/Square310x310Logo.png | Bin 31861 -> 0 bytes .../src-tauri/icons/Square44x44Logo.png | Bin 3527 -> 0 bytes .../src-tauri/icons/Square71x71Logo.png | Bin 5694 -> 0 bytes .../src-tauri/icons/Square89x89Logo.png | Bin 7511 -> 0 bytes .../mobile/src-tauri/icons/StoreLogo.png | Bin 3996 -> 0 bytes nym-connect/mobile/src-tauri/icons/icon.icns | Bin 318732 -> 0 bytes nym-connect/mobile/src-tauri/icons/icon.ico | Bin 40889 -> 0 bytes nym-connect/mobile/src-tauri/icons/icon.png | Bin 61553 -> 0 bytes .../mobile/src-tauri/icons/tray_icon.ico | Bin 38265 -> 0 bytes .../mobile/src-tauri/icons/tray_icon.png | Bin 56946 -> 0 bytes nym-connect/mobile/src-tauri/src/build.rs | 3 - .../mobile/src-tauri/src/config/mod.rs | 160 - nym-connect/mobile/src-tauri/src/error.rs | 86 - nym-connect/mobile/src-tauri/src/events.rs | 71 - nym-connect/mobile/src-tauri/src/lib.rs | 88 - nym-connect/mobile/src-tauri/src/logging.rs | 147 - nym-connect/mobile/src-tauri/src/main.rs | 10 - nym-connect/mobile/src-tauri/src/mobile.rs | 4 - .../mobile/src-tauri/src/models/mod.rs | 116 - .../src/operations/connection/connect.rs | 68 - .../src/operations/connection/disconnect.rs | 20 - .../src/operations/connection/health_check.rs | 26 - .../src/operations/connection/mod.rs | 4 - .../src/operations/connection/status.rs | 44 - .../src-tauri/src/operations/directory/mod.rs | 92 - .../mobile/src-tauri/src/operations/export.rs | 91 - .../src-tauri/src/operations/help/log.rs | 31 - .../src-tauri/src/operations/help/mod.rs | 2 - .../src-tauri/src/operations/help/storage.rs | 20 - .../mobile/src-tauri/src/operations/http.rs | 37 - .../mobile/src-tauri/src/operations/mod.rs | 5 - nym-connect/mobile/src-tauri/src/state.rs | 262 - nym-connect/mobile/src-tauri/src/tasks.rs | 205 - nym-connect/mobile/src-tauri/tauri.conf.json | 74 - .../mobile/src/assets/help-step-four.png | Bin 8530 -> 0 bytes .../src/assets/help-step-one-legacy.png | Bin 14403 -> 0 bytes .../mobile/src/assets/help-step-one.png | Bin 36448 -> 0 bytes .../mobile/src/assets/help-step-three.png | Bin 18990 -> 0 bytes .../src/assets/help-step-two-legacy.png | Bin 35631 -> 0 bytes .../mobile/src/assets/help-step-two.png | Bin 18353 -> 0 bytes .../mobile/src/components/AppVersion.tsx | 15 - .../mobile/src/components/AppWindowFrame.tsx | 48 - .../src/components/ConnectionButton.tsx | 168 - .../mobile/src/components/ConnectionStats.tsx | 47 - .../src/components/ConnectionStatus.tsx | 134 - .../src/components/ConntectionTimer.tsx | 28 - .../mobile/src/components/CopyToClipboard.tsx | 69 - .../mobile/src/components/CustomTitleBar.tsx | 67 - nym-connect/mobile/src/components/Error.tsx | 17 - .../mobile/src/components/ErrorFallback.tsx | 17 - .../src/components/ExperimentalWarning.tsx | 13 - .../Growth/content/assets/matrix.webp | Bin 1170192 -> 0 bytes .../mobile/src/components/HelpPage.tsx | 35 - .../mobile/src/components/HelpPageActions.tsx | 20 - .../mobile/src/components/HelpPageImage.tsx | 5 - .../src/components/HelpPageStepIndicator.tsx | 14 - .../mobile/src/components/InfoModal.tsx | 69 - .../src/components/IpAddressAndPort.tsx | 55 - .../src/components/IpAddressAndPortModal.tsx | 47 - .../mobile/src/components/NeedHelp.tsx | 16 - .../components/PowerButton/PowerButton.tsx | 149 - .../components/PowerButton/power-button.css | 72 - .../src/components/ServiceProviderPopup.tsx | 37 - .../ServiceProviderSelector.stories.tsx | 342 - .../components/ServiceProviderSelector.tsx | 147 - .../components/ServiceSelector.stories.tsx | 342 - .../mobile/src/components/ServiceSelector.tsx | 179 - .../mobile/src/components/TooltipInfo.tsx | 29 - nym-connect/mobile/src/config.ts | 3 - nym-connect/mobile/src/context/main.tsx | 217 - nym-connect/mobile/src/context/mocks/main.tsx | 33 - ...OpenSans-Italic-VariableFont_wdth,wght.ttf | Bin 580468 -> 0 bytes .../fonts/OpenSans-VariableFont_wdth,wght.ttf | Bin 529672 -> 0 bytes nym-connect/mobile/src/fonts/fonts.css | 6 - nym-connect/mobile/src/hooks/events.ts | 68 - nym-connect/mobile/src/index.tsx | 30 - .../mobile/src/layouts/ConnectionLayout.tsx | 24 - .../mobile/src/pages/connection/Connected.tsx | 82 - .../src/pages/connection/Disconnected.tsx | 55 - .../mobile/src/pages/connection/index.tsx | 92 - nym-connect/mobile/src/pages/menu/Apps.tsx | 39 - nym-connect/mobile/src/pages/menu/Guide.tsx | 47 - nym-connect/mobile/src/pages/menu/index.tsx | 35 - .../pages/menu/settings/GatewaySettings.tsx | 78 - .../menu/settings/ServiceProviderSettings.tsx | 101 - .../mobile/src/pages/menu/settings/index.tsx | 26 - nym-connect/mobile/src/routes/index.tsx | 25 - .../mobile/src/stories/AppFlow.stories.tsx | 116 - .../src/stories/AppWindowFrame.stories.tsx | 19 - .../src/stories/ConnectedLayout.stories.tsx | 42 - .../src/stories/ConnectionButton.stories.tsx | 25 - .../src/stories/ConnectionStats.stories.tsx | 27 - .../src/stories/ConnectionStatus.stories.tsx | 26 - .../src/stories/DefaultLayout.stories.tsx | 41 - .../src/stories/Introduction.stories.mdx | 7 - .../src/stories/IpAddressAndPort.stories.tsx | 14 - .../src/stories/PowerButton.stories.tsx | 29 - nym-connect/mobile/src/theme/index.tsx | 29 - nym-connect/mobile/src/theme/mui-theme.d.ts | 90 - nym-connect/mobile/src/theme/theme.tsx | 284 - nym-connect/mobile/src/types/connection.ts | 8 - nym-connect/mobile/src/types/directory.ts | 14 - nym-connect/mobile/src/types/error.ts | 4 - nym-connect/mobile/src/types/event.ts | 6 - nym-connect/mobile/src/types/gateway.ts | 4 - nym-connect/mobile/src/types/index.ts | 2 - nym-connect/mobile/src/types/rust/index.ts | 1 - .../mobile/src/types/rust/stateparams.ts | 7 - .../mobile/src/types/service-provider.ts | 9 - nym-connect/mobile/src/types/storage.ts | 4 - nym-connect/mobile/src/typings/FC.d.ts | 1 - nym-connect/mobile/src/typings/jpeg.d.ts | 9 - nym-connect/mobile/src/typings/json.d.ts | 4 - nym-connect/mobile/src/typings/png.d.ts | 4 - nym-connect/mobile/src/typings/svg.d.ts | 4 - nym-connect/mobile/src/typings/webp.d.ts | 4 - nym-connect/mobile/src/typings/yaml.d.ts | 9 - nym-connect/mobile/src/utils/index.ts | 48 - nym-connect/mobile/tsconfig.eslint.json | 20 - nym-connect/mobile/tsconfig.json | 23 - nym-connect/mobile/webpack.common.js | 55 - nym-connect/mobile/webpack.dev.js | 78 - nym-connect/mobile/webpack.prod.js | 24 - 200 files changed, 15234 deletions(-) delete mode 100644 .github/workflows/connect-mobile.yml delete mode 100644 nym-connect/mobile/.babelrc delete mode 100644 nym-connect/mobile/.env.sample delete mode 100644 nym-connect/mobile/.eslintrc delete mode 100644 nym-connect/mobile/.nvmrc delete mode 100644 nym-connect/mobile/.prettierrc delete mode 100644 nym-connect/mobile/.storybook/main.js delete mode 100644 nym-connect/mobile/.storybook/mocks/tauri/app.js delete mode 100644 nym-connect/mobile/.storybook/mocks/tauri/clipboard.js delete mode 100644 nym-connect/mobile/.storybook/mocks/tauri/event.js delete mode 100644 nym-connect/mobile/.storybook/mocks/tauri/index.js delete mode 100644 nym-connect/mobile/.storybook/mocks/tauri/notification.js delete mode 100644 nym-connect/mobile/.storybook/mocks/tauri/window.js delete mode 100644 nym-connect/mobile/.storybook/preview-fonts.js delete mode 100644 nym-connect/mobile/.storybook/preview.js delete mode 100644 nym-connect/mobile/CHANGELOG.md delete mode 100644 nym-connect/mobile/README.md delete mode 100644 nym-connect/mobile/dist/.gitkeep delete mode 100644 nym-connect/mobile/package.json.bak delete mode 100644 nym-connect/mobile/public/favicon.ico delete mode 100644 nym-connect/mobile/public/favicon.png delete mode 100644 nym-connect/mobile/public/index.html delete mode 100644 nym-connect/mobile/src-tauri/.gitignore delete mode 100644 nym-connect/mobile/src-tauri/Cargo.lock delete mode 100644 nym-connect/mobile/src-tauri/Cargo.toml delete mode 100644 nym-connect/mobile/src-tauri/gen/android/nym_connect/.editorconfig delete mode 100644 nym-connect/mobile/src-tauri/gen/android/nym_connect/.gitignore delete mode 100644 nym-connect/mobile/src-tauri/gen/android/nym_connect/app/.gitignore delete mode 100644 nym-connect/mobile/src-tauri/gen/android/nym_connect/app/build.gradle.kts delete mode 100644 nym-connect/mobile/src-tauri/gen/android/nym_connect/app/proguard-rules.pro delete mode 100644 nym-connect/mobile/src-tauri/gen/android/nym_connect/app/src/main/AndroidManifest.xml delete mode 100644 nym-connect/mobile/src-tauri/gen/android/nym_connect/app/src/main/java/net/nymtech/nym_connect/MainActivity.kt delete mode 100644 nym-connect/mobile/src-tauri/gen/android/nym_connect/app/src/main/java/net/nymtech/nym_connect/generated/.gitkeep delete mode 100644 nym-connect/mobile/src-tauri/gen/android/nym_connect/app/src/main/res/drawable-v24/ic_launcher_foreground.xml delete mode 100644 nym-connect/mobile/src-tauri/gen/android/nym_connect/app/src/main/res/drawable/ic_launcher_background.xml delete mode 100644 nym-connect/mobile/src-tauri/gen/android/nym_connect/app/src/main/res/layout/activity_main.xml delete mode 100644 nym-connect/mobile/src-tauri/gen/android/nym_connect/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml delete mode 100644 nym-connect/mobile/src-tauri/gen/android/nym_connect/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml delete mode 100644 nym-connect/mobile/src-tauri/gen/android/nym_connect/app/src/main/res/mipmap-hdpi/ic_launcher.png delete mode 100644 nym-connect/mobile/src-tauri/gen/android/nym_connect/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png delete mode 100644 nym-connect/mobile/src-tauri/gen/android/nym_connect/app/src/main/res/mipmap-hdpi/ic_launcher_round.png delete mode 100644 nym-connect/mobile/src-tauri/gen/android/nym_connect/app/src/main/res/mipmap-mdpi/ic_launcher.png delete mode 100644 nym-connect/mobile/src-tauri/gen/android/nym_connect/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png delete mode 100644 nym-connect/mobile/src-tauri/gen/android/nym_connect/app/src/main/res/mipmap-mdpi/ic_launcher_round.png delete mode 100644 nym-connect/mobile/src-tauri/gen/android/nym_connect/app/src/main/res/mipmap-xhdpi/ic_launcher.png delete mode 100644 nym-connect/mobile/src-tauri/gen/android/nym_connect/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png delete mode 100644 nym-connect/mobile/src-tauri/gen/android/nym_connect/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png delete mode 100644 nym-connect/mobile/src-tauri/gen/android/nym_connect/app/src/main/res/mipmap-xxhdpi/ic_launcher.png delete mode 100644 nym-connect/mobile/src-tauri/gen/android/nym_connect/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png delete mode 100644 nym-connect/mobile/src-tauri/gen/android/nym_connect/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png delete mode 100644 nym-connect/mobile/src-tauri/gen/android/nym_connect/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png delete mode 100644 nym-connect/mobile/src-tauri/gen/android/nym_connect/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png delete mode 100644 nym-connect/mobile/src-tauri/gen/android/nym_connect/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png delete mode 100644 nym-connect/mobile/src-tauri/gen/android/nym_connect/app/src/main/res/values-night/themes.xml delete mode 100644 nym-connect/mobile/src-tauri/gen/android/nym_connect/app/src/main/res/values/colors.xml delete mode 100644 nym-connect/mobile/src-tauri/gen/android/nym_connect/app/src/main/res/values/ic_launcher_background.xml delete mode 100644 nym-connect/mobile/src-tauri/gen/android/nym_connect/app/src/main/res/values/strings.xml delete mode 100644 nym-connect/mobile/src-tauri/gen/android/nym_connect/app/src/main/res/values/themes.xml delete mode 100644 nym-connect/mobile/src-tauri/gen/android/nym_connect/build.gradle.kts delete mode 100644 nym-connect/mobile/src-tauri/gen/android/nym_connect/buildSrc/build.gradle.kts delete mode 100644 nym-connect/mobile/src-tauri/gen/android/nym_connect/buildSrc/src/main/java/net/nymtech/nym_connect/kotlin/BuildTask.kt delete mode 100644 nym-connect/mobile/src-tauri/gen/android/nym_connect/buildSrc/src/main/java/net/nymtech/nym_connect/kotlin/RustPlugin.kt delete mode 100644 nym-connect/mobile/src-tauri/gen/android/nym_connect/gradle.properties delete mode 100644 nym-connect/mobile/src-tauri/gen/android/nym_connect/gradle/wrapper/gradle-wrapper.jar delete mode 100644 nym-connect/mobile/src-tauri/gen/android/nym_connect/gradle/wrapper/gradle-wrapper.properties delete mode 100755 nym-connect/mobile/src-tauri/gen/android/nym_connect/gradlew delete mode 100644 nym-connect/mobile/src-tauri/gen/android/nym_connect/gradlew.bat delete mode 100644 nym-connect/mobile/src-tauri/gen/android/nym_connect/settings.gradle delete mode 100644 nym-connect/mobile/src-tauri/icons/128x128.png delete mode 100644 nym-connect/mobile/src-tauri/icons/128x128@2x.png delete mode 100644 nym-connect/mobile/src-tauri/icons/32x32.png delete mode 100644 nym-connect/mobile/src-tauri/icons/Square107x107Logo.png delete mode 100644 nym-connect/mobile/src-tauri/icons/Square142x142Logo.png delete mode 100644 nym-connect/mobile/src-tauri/icons/Square150x150Logo.png delete mode 100644 nym-connect/mobile/src-tauri/icons/Square284x284Logo.png delete mode 100644 nym-connect/mobile/src-tauri/icons/Square30x30Logo.png delete mode 100644 nym-connect/mobile/src-tauri/icons/Square310x310Logo.png delete mode 100644 nym-connect/mobile/src-tauri/icons/Square44x44Logo.png delete mode 100644 nym-connect/mobile/src-tauri/icons/Square71x71Logo.png delete mode 100644 nym-connect/mobile/src-tauri/icons/Square89x89Logo.png delete mode 100644 nym-connect/mobile/src-tauri/icons/StoreLogo.png delete mode 100644 nym-connect/mobile/src-tauri/icons/icon.icns delete mode 100644 nym-connect/mobile/src-tauri/icons/icon.ico delete mode 100644 nym-connect/mobile/src-tauri/icons/icon.png delete mode 100644 nym-connect/mobile/src-tauri/icons/tray_icon.ico delete mode 100644 nym-connect/mobile/src-tauri/icons/tray_icon.png delete mode 100644 nym-connect/mobile/src-tauri/src/build.rs delete mode 100644 nym-connect/mobile/src-tauri/src/config/mod.rs delete mode 100644 nym-connect/mobile/src-tauri/src/error.rs delete mode 100644 nym-connect/mobile/src-tauri/src/events.rs delete mode 100644 nym-connect/mobile/src-tauri/src/lib.rs delete mode 100644 nym-connect/mobile/src-tauri/src/logging.rs delete mode 100644 nym-connect/mobile/src-tauri/src/main.rs delete mode 100644 nym-connect/mobile/src-tauri/src/mobile.rs delete mode 100644 nym-connect/mobile/src-tauri/src/models/mod.rs delete mode 100644 nym-connect/mobile/src-tauri/src/operations/connection/connect.rs delete mode 100644 nym-connect/mobile/src-tauri/src/operations/connection/disconnect.rs delete mode 100644 nym-connect/mobile/src-tauri/src/operations/connection/health_check.rs delete mode 100644 nym-connect/mobile/src-tauri/src/operations/connection/mod.rs delete mode 100644 nym-connect/mobile/src-tauri/src/operations/connection/status.rs delete mode 100644 nym-connect/mobile/src-tauri/src/operations/directory/mod.rs delete mode 100644 nym-connect/mobile/src-tauri/src/operations/export.rs delete mode 100644 nym-connect/mobile/src-tauri/src/operations/help/log.rs delete mode 100644 nym-connect/mobile/src-tauri/src/operations/help/mod.rs delete mode 100644 nym-connect/mobile/src-tauri/src/operations/help/storage.rs delete mode 100644 nym-connect/mobile/src-tauri/src/operations/http.rs delete mode 100644 nym-connect/mobile/src-tauri/src/operations/mod.rs delete mode 100644 nym-connect/mobile/src-tauri/src/state.rs delete mode 100644 nym-connect/mobile/src-tauri/src/tasks.rs delete mode 100644 nym-connect/mobile/src-tauri/tauri.conf.json delete mode 100644 nym-connect/mobile/src/assets/help-step-four.png delete mode 100644 nym-connect/mobile/src/assets/help-step-one-legacy.png delete mode 100644 nym-connect/mobile/src/assets/help-step-one.png delete mode 100644 nym-connect/mobile/src/assets/help-step-three.png delete mode 100644 nym-connect/mobile/src/assets/help-step-two-legacy.png delete mode 100644 nym-connect/mobile/src/assets/help-step-two.png delete mode 100644 nym-connect/mobile/src/components/AppVersion.tsx delete mode 100644 nym-connect/mobile/src/components/AppWindowFrame.tsx delete mode 100644 nym-connect/mobile/src/components/ConnectionButton.tsx delete mode 100644 nym-connect/mobile/src/components/ConnectionStats.tsx delete mode 100644 nym-connect/mobile/src/components/ConnectionStatus.tsx delete mode 100644 nym-connect/mobile/src/components/ConntectionTimer.tsx delete mode 100644 nym-connect/mobile/src/components/CopyToClipboard.tsx delete mode 100644 nym-connect/mobile/src/components/CustomTitleBar.tsx delete mode 100644 nym-connect/mobile/src/components/Error.tsx delete mode 100644 nym-connect/mobile/src/components/ErrorFallback.tsx delete mode 100644 nym-connect/mobile/src/components/ExperimentalWarning.tsx delete mode 100644 nym-connect/mobile/src/components/Growth/content/assets/matrix.webp delete mode 100644 nym-connect/mobile/src/components/HelpPage.tsx delete mode 100644 nym-connect/mobile/src/components/HelpPageActions.tsx delete mode 100644 nym-connect/mobile/src/components/HelpPageImage.tsx delete mode 100644 nym-connect/mobile/src/components/HelpPageStepIndicator.tsx delete mode 100644 nym-connect/mobile/src/components/InfoModal.tsx delete mode 100644 nym-connect/mobile/src/components/IpAddressAndPort.tsx delete mode 100644 nym-connect/mobile/src/components/IpAddressAndPortModal.tsx delete mode 100644 nym-connect/mobile/src/components/NeedHelp.tsx delete mode 100644 nym-connect/mobile/src/components/PowerButton/PowerButton.tsx delete mode 100644 nym-connect/mobile/src/components/PowerButton/power-button.css delete mode 100644 nym-connect/mobile/src/components/ServiceProviderPopup.tsx delete mode 100644 nym-connect/mobile/src/components/ServiceProviderSelector.stories.tsx delete mode 100644 nym-connect/mobile/src/components/ServiceProviderSelector.tsx delete mode 100644 nym-connect/mobile/src/components/ServiceSelector.stories.tsx delete mode 100644 nym-connect/mobile/src/components/ServiceSelector.tsx delete mode 100644 nym-connect/mobile/src/components/TooltipInfo.tsx delete mode 100644 nym-connect/mobile/src/config.ts delete mode 100644 nym-connect/mobile/src/context/main.tsx delete mode 100644 nym-connect/mobile/src/context/mocks/main.tsx delete mode 100644 nym-connect/mobile/src/fonts/OpenSans-Italic-VariableFont_wdth,wght.ttf delete mode 100644 nym-connect/mobile/src/fonts/OpenSans-VariableFont_wdth,wght.ttf delete mode 100644 nym-connect/mobile/src/fonts/fonts.css delete mode 100644 nym-connect/mobile/src/hooks/events.ts delete mode 100644 nym-connect/mobile/src/index.tsx delete mode 100644 nym-connect/mobile/src/layouts/ConnectionLayout.tsx delete mode 100644 nym-connect/mobile/src/pages/connection/Connected.tsx delete mode 100644 nym-connect/mobile/src/pages/connection/Disconnected.tsx delete mode 100644 nym-connect/mobile/src/pages/connection/index.tsx delete mode 100644 nym-connect/mobile/src/pages/menu/Apps.tsx delete mode 100644 nym-connect/mobile/src/pages/menu/Guide.tsx delete mode 100644 nym-connect/mobile/src/pages/menu/index.tsx delete mode 100644 nym-connect/mobile/src/pages/menu/settings/GatewaySettings.tsx delete mode 100644 nym-connect/mobile/src/pages/menu/settings/ServiceProviderSettings.tsx delete mode 100644 nym-connect/mobile/src/pages/menu/settings/index.tsx delete mode 100644 nym-connect/mobile/src/routes/index.tsx delete mode 100644 nym-connect/mobile/src/stories/AppFlow.stories.tsx delete mode 100644 nym-connect/mobile/src/stories/AppWindowFrame.stories.tsx delete mode 100644 nym-connect/mobile/src/stories/ConnectedLayout.stories.tsx delete mode 100644 nym-connect/mobile/src/stories/ConnectionButton.stories.tsx delete mode 100644 nym-connect/mobile/src/stories/ConnectionStats.stories.tsx delete mode 100644 nym-connect/mobile/src/stories/ConnectionStatus.stories.tsx delete mode 100644 nym-connect/mobile/src/stories/DefaultLayout.stories.tsx delete mode 100644 nym-connect/mobile/src/stories/Introduction.stories.mdx delete mode 100644 nym-connect/mobile/src/stories/IpAddressAndPort.stories.tsx delete mode 100644 nym-connect/mobile/src/stories/PowerButton.stories.tsx delete mode 100644 nym-connect/mobile/src/theme/index.tsx delete mode 100644 nym-connect/mobile/src/theme/mui-theme.d.ts delete mode 100644 nym-connect/mobile/src/theme/theme.tsx delete mode 100644 nym-connect/mobile/src/types/connection.ts delete mode 100644 nym-connect/mobile/src/types/directory.ts delete mode 100644 nym-connect/mobile/src/types/error.ts delete mode 100644 nym-connect/mobile/src/types/event.ts delete mode 100644 nym-connect/mobile/src/types/gateway.ts delete mode 100644 nym-connect/mobile/src/types/index.ts delete mode 100644 nym-connect/mobile/src/types/rust/index.ts delete mode 100644 nym-connect/mobile/src/types/rust/stateparams.ts delete mode 100644 nym-connect/mobile/src/types/service-provider.ts delete mode 100644 nym-connect/mobile/src/types/storage.ts delete mode 100644 nym-connect/mobile/src/typings/FC.d.ts delete mode 100644 nym-connect/mobile/src/typings/jpeg.d.ts delete mode 100644 nym-connect/mobile/src/typings/json.d.ts delete mode 100644 nym-connect/mobile/src/typings/png.d.ts delete mode 100644 nym-connect/mobile/src/typings/svg.d.ts delete mode 100644 nym-connect/mobile/src/typings/webp.d.ts delete mode 100644 nym-connect/mobile/src/typings/yaml.d.ts delete mode 100644 nym-connect/mobile/src/utils/index.ts delete mode 100644 nym-connect/mobile/tsconfig.eslint.json delete mode 100644 nym-connect/mobile/tsconfig.json delete mode 100644 nym-connect/mobile/webpack.common.js delete mode 100644 nym-connect/mobile/webpack.dev.js delete mode 100644 nym-connect/mobile/webpack.prod.js diff --git a/.github/workflows/connect-mobile.yml b/.github/workflows/connect-mobile.yml deleted file mode 100644 index 05f0add6e2..0000000000 --- a/.github/workflows/connect-mobile.yml +++ /dev/null @@ -1,72 +0,0 @@ -name: Nym Connect - mobile (Rust) - -on: - push: - paths: - - "nym-connect/mobile/src-tauri/**" - - "nym-connect/mobile/src-tauri/Cargo.toml" - - "!nym-connect/mobile/src-tauri/gen/**" - - "clients/client-core/**" - - "clients/socks5/**" - - "common/**" - - "gateway/gateway-requests/**" - - "contracts/vesting/**" - - "nym-api/nym-api-requests/**" - pull_request: - paths: - - "nym-connect/mobile/src-tauri/**" - - "nym-connect/mobile/src-tauri/Cargo.toml" - - "!nym-connect/mobile/src-tauri/gen/**" - - "clients/client-core/**" - - "clients/socks5/**" - - "common/**" - - "gateway/gateway-requests/**" - - "contracts/vesting/**" - - "nym-api/nym-api-requests/**" - -jobs: - build: - #runs-on: [self-hosted, custom-linux] - runs-on: ubuntu-22.04 - #env: - #RUSTC_WRAPPER: /home/ubuntu/.cargo/bin/sccache - #defaults: - #run: - #working-directory: nym-connect/mobile/src-tauri/ - steps: - - name: Install Dependencies (Linux) - run: | - sudo apt-get update - sudo apt-get -y install \ - libwebkit2gtk-4.1-dev \ - build-essential \ - curl \ - wget \ - libssl-dev \ - libgtk-3-dev \ - squashfs-tools \ - libayatana-appindicator3-dev \ - librsvg2-dev \ - libsoup-3.0-dev \ - libjavascriptcoregtk-4.1-dev - - - name: Checkout - uses: actions/checkout@v3 - - - name: Install Rust toolchain - uses: dtolnay/rust-toolchain@master - with: - toolchain: stable - components: clippy, rustfmt - - - name: Check formatting - run: cargo fmt --manifest-path nym-connect/mobile/src-tauri/Cargo.toml -- --check - - - name: Build all binaries - run: cargo build --manifest-path nym-connect/mobile/src-tauri/Cargo.toml - - - name: Run all tests - run: cargo test --manifest-path nym-connect/mobile/src-tauri/Cargo.toml - - - name: Clippy - run: cargo clippy --manifest-path nym-connect/mobile/src-tauri/Cargo.toml --all-targets -- -D warnings diff --git a/.github/workflows/typescript-lint.yml b/.github/workflows/typescript-lint.yml index dcaa905f5d..a8318364a4 100644 --- a/.github/workflows/typescript-lint.yml +++ b/.github/workflows/typescript-lint.yml @@ -7,8 +7,6 @@ on: - "sdk/typescript/**" - "nym-connect/desktop/src/**" - "nym-connect/desktop/package.json" - - "nym-connect/mobile/src/**" - - "nym-connect/mobile/package.json" - "nym-wallet/src/**" - "nym-wallet/package.json" - "explorer/**" @@ -18,8 +16,6 @@ on: - "sdk/typescript/**" - "nym-connect/desktop/src/**" - "nym-connect/desktop/package.json" - - "nym-connect/mobile/src/**" - - "nym-connect/mobile/package.json" - "nym-wallet/src/**" - "nym-wallet/package.json" - "explorer/**" diff --git a/nym-connect/mobile/.babelrc b/nym-connect/mobile/.babelrc deleted file mode 100644 index 7a840ad24e..0000000000 --- a/nym-connect/mobile/.babelrc +++ /dev/null @@ -1,15 +0,0 @@ -{ - "presets": [ - [ - "@babel/preset-env", - { - "targets": { - "esmodules": true - } - } - ], - "@babel/preset-react", - "@babel/preset-typescript" - ], - "plugins": ["@babel/plugin-transform-async-to-generator"] -} diff --git a/nym-connect/mobile/.env.sample b/nym-connect/mobile/.env.sample deleted file mode 100644 index cc3febee19..0000000000 --- a/nym-connect/mobile/.env.sample +++ /dev/null @@ -1 +0,0 @@ -ADMIN_ADDRESS= \ No newline at end of file diff --git a/nym-connect/mobile/.eslintrc b/nym-connect/mobile/.eslintrc deleted file mode 100644 index 3ea1281af1..0000000000 --- a/nym-connect/mobile/.eslintrc +++ /dev/null @@ -1,8 +0,0 @@ -{ - "extends": [ - "@nymproject/eslint-config-react-typescript" - ], - "parserOptions": { - "project": "./tsconfig.eslint.json" - } -} diff --git a/nym-connect/mobile/.nvmrc b/nym-connect/mobile/.nvmrc deleted file mode 100644 index 25bf17fc5a..0000000000 --- a/nym-connect/mobile/.nvmrc +++ /dev/null @@ -1 +0,0 @@ -18 \ No newline at end of file diff --git a/nym-connect/mobile/.prettierrc b/nym-connect/mobile/.prettierrc deleted file mode 100644 index 1bc34d0c3f..0000000000 --- a/nym-connect/mobile/.prettierrc +++ /dev/null @@ -1,7 +0,0 @@ -{ - "trailingComma": "all", - "singleQuote": true, - "printWidth": 120, - "tabWidth": 2, - "semi": true -} diff --git a/nym-connect/mobile/.storybook/main.js b/nym-connect/mobile/.storybook/main.js deleted file mode 100644 index 344c27778e..0000000000 --- a/nym-connect/mobile/.storybook/main.js +++ /dev/null @@ -1,73 +0,0 @@ -/* eslint-disable no-param-reassign */ -const TsconfigPathsPlugin = require('tsconfig-paths-webpack-plugin'); -const ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin'); - -module.exports = { - stories: ['../src/**/*.stories.mdx', '../src/**/*.stories.@(js|jsx|ts|tsx)'], - addons: ['@storybook/addon-links', '@storybook/addon-essentials', '@storybook/addon-interactions'], - framework: '@storybook/react', - core: { - builder: 'webpack5', - }, - typescript: { reactDocgen: false }, - // webpackFinal: async (config, { configType }) => { - // // `configType` has a value of 'DEVELOPMENT' or 'PRODUCTION' - // // You can change the configuration based on that. - // // 'PRODUCTION' is used when building the static version of storybook. - webpackFinal: async (config) => { - config.module.rules.forEach((rule) => { - // look for SVG import rule and replace - // NOTE: the rule before modification is /\.(svg|ico|jpg|jpeg|png|apng|gif|eot|otf|webp|ttf|woff|woff2|cur|ani|pdf)(\?.*)?$/ - if (rule.test?.toString().includes('svg')) { - rule.test = /\.(ico|jpg|jpeg|png|apng|gif|eot|otf|webp|ttf|woff|woff2|cur|ani|pdf)(\?.*)?$/; - } - }); - - // handle asset loading with this - config.module.rules.unshift({ - test: /\.svg(\?.*)?$/i, - issuer: /\.[jt]sx?$/, - use: ['@svgr/webpack'], - }); - - config.module.rules.unshift({ - test: /\.ya?ml$/, - type: 'json', - use: [ - { - loader: 'yaml-loader', - options: { - asJSON: true, - }, - }, - ], - }); - - config.resolve.extensions = ['.tsx', '.ts', '.js']; - config.resolve.plugins = [new TsconfigPathsPlugin()]; - - config.plugins.push( - new ForkTsCheckerWebpackPlugin({ - typescript: { - mode: 'write-references', - diagnosticOptions: { - semantic: true, - syntactic: true, - }, - }, - }), - ); - - if (!config.resolve.alias) { - config.resolve.alias = {}; - } - - config.resolve.alias['@tauri-apps/api'] = `${__dirname}/mocks/tauri`; - - // Return the altered config - return config; - }, - features: { - emotionAlias: false, - }, -}; diff --git a/nym-connect/mobile/.storybook/mocks/tauri/app.js b/nym-connect/mobile/.storybook/mocks/tauri/app.js deleted file mode 100644 index 83d80d7037..0000000000 --- a/nym-connect/mobile/.storybook/mocks/tauri/app.js +++ /dev/null @@ -1,8 +0,0 @@ -/** - * This is a mock for Tauri's API package (@tauri-apps/api/app), to prevent stories from being excluded, because they either use - * or import dependencies that use Tauri. - */ - -module.exports = { - getVersion: () => undefined, -}; diff --git a/nym-connect/mobile/.storybook/mocks/tauri/clipboard.js b/nym-connect/mobile/.storybook/mocks/tauri/clipboard.js deleted file mode 100644 index a34847db86..0000000000 --- a/nym-connect/mobile/.storybook/mocks/tauri/clipboard.js +++ /dev/null @@ -1,8 +0,0 @@ -/** - * This is a mock for Tauri's API package (@tauri-apps/api/clipboard), to prevent stories from being excluded, because they either use - * or import dependencies that use Tauri. - */ - -module.exports = { - writeText: async () => undefined, -} diff --git a/nym-connect/mobile/.storybook/mocks/tauri/event.js b/nym-connect/mobile/.storybook/mocks/tauri/event.js deleted file mode 100644 index a100d30337..0000000000 --- a/nym-connect/mobile/.storybook/mocks/tauri/event.js +++ /dev/null @@ -1,8 +0,0 @@ -/** - * This is a mock for Tauri's API package (@tauri-apps/api/clipboard), to prevent stories from being excluded, because they either use - * or import dependencies that use Tauri. - */ - -module.exports = { - listen: async () => undefined, -} diff --git a/nym-connect/mobile/.storybook/mocks/tauri/index.js b/nym-connect/mobile/.storybook/mocks/tauri/index.js deleted file mode 100644 index c411c3bf18..0000000000 --- a/nym-connect/mobile/.storybook/mocks/tauri/index.js +++ /dev/null @@ -1,14 +0,0 @@ -/** - * This is a mock for Tauri's API package (@tauri-apps/api), to prevent stories from being excluded, because they either use - * or import dependencies that use Tauri. - */ -module.exports = { - invoke: (operation, args) => { - console.error( - `Tauri cannot be used in Storybook. The operation requested was "${operation}". You can add mock responses to "nym_connect/.storybook/mocks/tauri.js" if you need. The default response is "void".`, - ); - return new Promise((resolve, reject) => { - reject(new Error(`Tauri operation ${operation} not available in storybook.`)); - }); - }, -}; diff --git a/nym-connect/mobile/.storybook/mocks/tauri/notification.js b/nym-connect/mobile/.storybook/mocks/tauri/notification.js deleted file mode 100644 index dcbe18e574..0000000000 --- a/nym-connect/mobile/.storybook/mocks/tauri/notification.js +++ /dev/null @@ -1,10 +0,0 @@ -/** - * This is a mock for Tauri's API package (@tauri-apps/api/notification), to prevent stories from being excluded, because they either use - * or import dependencies that use Tauri. - */ - -module.exports = { - isPermissionGranted: () => undefined, - requestPermission: () => undefined, - sendNotification: () => undefined, -}; \ No newline at end of file diff --git a/nym-connect/mobile/.storybook/mocks/tauri/window.js b/nym-connect/mobile/.storybook/mocks/tauri/window.js deleted file mode 100644 index 9f0afeeebd..0000000000 --- a/nym-connect/mobile/.storybook/mocks/tauri/window.js +++ /dev/null @@ -1,10 +0,0 @@ -/** - * This is a mock for Tauri's API package (@tauri-apps/api/window), to prevent stories from being excluded, because they either use - * or import dependencies that use Tauri. - */ - -module.exports = { - appWindow: { - maximize: () => undefined, - } -} diff --git a/nym-connect/mobile/.storybook/preview-fonts.js b/nym-connect/mobile/.storybook/preview-fonts.js deleted file mode 100644 index a7989fca9a..0000000000 --- a/nym-connect/mobile/.storybook/preview-fonts.js +++ /dev/null @@ -1,3 +0,0 @@ -import '../src/fonts/fonts.css'; - -export const Fonts = ({ children }) => <>{children}; \ No newline at end of file diff --git a/nym-connect/mobile/.storybook/preview.js b/nym-connect/mobile/.storybook/preview.js deleted file mode 100644 index 9c14c3ec99..0000000000 --- a/nym-connect/mobile/.storybook/preview.js +++ /dev/null @@ -1,26 +0,0 @@ -import { NymMixnetTheme } from '../src/theme'; -import { Fonts } from './preview-fonts'; -import { MockProvider } from '../src/context/mocks/main'; -const withThemeProvider = (Story, context) => { - return ( - - - - - - - - ); -}; - -export const decorators = [withThemeProvider]; - -export const parameters = { - actions: { argTypesRegex: '^on[A-Z].*' }, - controls: { - matchers: { - color: /(background|color)$/i, - date: /Date$/, - }, - }, -}; diff --git a/nym-connect/mobile/CHANGELOG.md b/nym-connect/mobile/CHANGELOG.md deleted file mode 100644 index cddba036e0..0000000000 --- a/nym-connect/mobile/CHANGELOG.md +++ /dev/null @@ -1,98 +0,0 @@ -# Changelog - -## UNRELEASED - -## [nym-connect-v1.1.7](https://github.com/nymtech/nym/tree/nym-connect-v1.1.7) (2023-01-24) - -- Remove test and earn ([#2865]) - -[#2865]: https://github.com/nymtech/nym/issue/2865 - -## [nym-connect-v1.1.6](https://github.com/nymtech/nym/tree/nym-connect-v1.1.6) (2023-01-17) - -- part (1) show gateway status on the UI if the gateway is not live, is overloaded or is slow ([#2824]) - -[#2824]: https://github.com/nymtech/nym/pull/2824 - -## [nym-connect-v1.1.5](https://github.com/nymtech/nym/tree/nym-connect-v1.1.5) (2023-01-10) - -- get version number from tauri and display by @fmtabbara in https://github.com/nymtech/nym/pull/2684 -- Feature/nym connect experimental software text by @fmtabbara in https://github.com/nymtech/nym/pull/2692 -- NymConnect - Display service info in tooltip **1.1.5 Release** by @fmtabbara in https://github.com/nymtech/nym/pull/2704 - -## [nym-connect-v1.1.4](https://github.com/nymtech/nym/tree/nym-connect-v1.1.4) (2022-12-20) - -This release contains the new opt-in Test & Earn program, and it uses a stress-tested directory of network requesters to improve reliability. It also has some bugfixes, performance improvements, and better error handling. - -- nym-connect: send status messages from socks5 task to tauri backend by @octol in https://github.com/nymtech/nym/pull/1882 -- socks5: rework waiting in inbound.rs by @octol in https://github.com/nymtech/nym/pull/1880 -- Test&Earn by @mmsinclair in https://github.com/nymtech/nym/pull/2729 - -## [nym-connect-v1.1.3](https://github.com/nymtech/nym/tree/nym-connect-v1.1.3) (2022-12-13) - -- socks5-client: added support for socks4a. - -## [nym-connect-v1.1.2](https://github.com/nymtech/nym/tree/nym-connect-v1.1.2) (2022-12-06) - -- socks5-client: fix error with client failing and disconnecting unnecessarily. - -## [nym-connect-v1.1.1](https://github.com/nymtech/nym/tree/nym-connect-v1.1.1) (2022-11-29) - -- socks5-client: fix multiplex concurrent connections ([#1720], [#1777]) -- socks5-client: fix wait closing inbound connection until data is sent, and throttle incoming data in general ([#1772], [#1783],[#1789]) -- socks5-client: fix shutting down all background workers if anyone of them panics or errors out. This fixes an issue where the nym-connect UI was showing connected even though the socks5 tunnel was non-functional. ([#1805]) -- gateway-libs: fix decryping messages stored on the gateway between reconnects ([#1786]) - -- nymconnect: updated UI -- nymconnect: new help area -- nymconnect: listen for service errors and display on frontend - -[#1720]: https://github.com/nymtech/nym/pull/1720 -[#1772]: https://github.com/nymtech/nym/pull/1772 -[#1777]: https://github.com/nymtech/nym/pull/1777 -[#1783]: https://github.com/nymtech/nym/pull/1783 -[#1786]: https://github.com/nymtech/nym/pull/1786 -[#1789]: https://github.com/nymtech/nym/pull/1789 -[#1805]: https://github.com/nymtech/nym/pull/1805 - -## [nym-connect-v1.1.0](https://github.com/nymtech/nym/tree/nym-connect-v1.1.0) (2022-11-09) - -- nym-connect: rework of rewarding changes the directory data structures that describe the mixnet topology ([#1472]) -- clients: add testing-only support for two more extended packet sizes (8kb and 16kb). -- native-client/socks5-client: `disable_loop_cover_traffic_stream` Debug config option to disable the separate loop cover traffic stream ([#1666]) -- native-client/socks5-client: `disable_main_poisson_packet_distribution` Debug config option to make the client ignore poisson distribution in the main packet stream and ONLY send real message (and as fast as they come) ([#1664]) -- native-client/socks5-client: `use_extended_packet_size` Debug config option to make the client use 'ExtendedPacketSize' for its traffic (32kB as opposed to 2kB in 1.0.2) ([#1671]) -- network-requester: added additional Blockstream Green wallet endpoint to `example.allowed.list` ([#1611]) -- validator-client: added `query_contract_smart` and `query_contract_raw` on `NyxdClient` ([#1558]) - -[#1472]: https://github.com/nymtech/nym/pull/1472 -[#1558]: https://github.com/nymtech/nym/pull/1558 -[#1611]: https://github.com/nymtech/nym/pull/1611 -[#1664]: https://github.com/nymtech/nym/pull/1664 -[#1666]: https://github.com/nymtech/nym/pull/1666 -[#1671]: https://github.com/nymtech/nym/pull/1671 - -## [nym-connect-v1.0.2](https://github.com/nymtech/nym/tree/nym-connect-v1.0.2) (2022-08-18) - -### Changed - -- nym-connect: "load balance" the service providers by picking a random Service Provider for each Service and storing in local storage so it remains sticky for the user ([#1540]) -- nym-connect: the ServiceProviderSelector only displays the available Services, and picks a random Service Provider for Services the user has never used before ([#1540]) -- nym-connect: add `local-forage` for storing user settings ([#1540]) - -[#1540]: https://github.com/nymtech/nym/pull/1540 - -## [nym-connect-v1.0.1](https://github.com/nymtech/nym/tree/nym-connect-v1.0.1) (2022-07-22) - -### Added - -- nym-connect: initial proof-of-concept of a UI around the socks5 client was added -- nym-connect: add ability to select network requester and gateway ([#1427]) -- nym-connect: add ability to export gateway keys as JSON -- nym-connect: add auto updater - -### Changed - -- nym-connect: reuse config id instead of creating a new id on each connection - -[#1427]: https://github.com/nymtech/nym/pull/1427 diff --git a/nym-connect/mobile/README.md b/nym-connect/mobile/README.md deleted file mode 100644 index efb522dd81..0000000000 --- a/nym-connect/mobile/README.md +++ /dev/null @@ -1,71 +0,0 @@ - - -# Nym Connect - Mobile - -[Nym Connect](https://github.com/nymtech/nym/tree/develop/nym-connect) application for Mobile. - -Nym Connects sets up a SOCKS5 proxy for local applications to use. - -**NOTE**: Currently we only focus on Android, -the remaining docs apply for Android development only. - -## Installation prerequisites - Linux / Mac - -- `Yarn` -- `NodeJS >= v16` -- `Rust & cargo` -- Android development environment (JDK, SDK/NDK, AVD etc...) - -For setting up an Android development environment see -https://next--tauri.netlify.app/next/guides/getting-started/prerequisites/linux#android - -## Installation - -Inside the `mobile/nym-connect` directory, run the following commands: - -``` -yarn install -yarn prewebpack:dev -``` - -## Development - -Assuming there is a running android [emulator](https://developer.android.com/studio/run/emulator) -or a real [device](https://developer.android.com/studio/run/device) connected. -Inside the `mobile/nym-connect/src-tauri` directory, run the following command: - -``` -yarn dev:android -``` - -#### Debugging - -https://next--tauri.netlify.app/next/guides/debugging/application#mobile - -## Production - -To build the APK, run the build commands. - -``` -yarn webpack:prod -WRY_ANDROID_PACKAGE=net.nymtech.nym_connect WRY_ANDROID_LIBRARY=nym_connect cargo tauri android build --debug --apk -``` - -**NOTE**: Production build without the `--debug` flag requires a signed build. - -# Storybook - -Run storybook with: - -``` -yarn storybook -``` - -And build storybook static site with: - -``` -yarn storybook:build -``` diff --git a/nym-connect/mobile/dist/.gitkeep b/nym-connect/mobile/dist/.gitkeep deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/nym-connect/mobile/package.json.bak b/nym-connect/mobile/package.json.bak deleted file mode 100644 index 20c4aceb65..0000000000 --- a/nym-connect/mobile/package.json.bak +++ /dev/null @@ -1,119 +0,0 @@ -{ - "name": "@nym/nym-connect-mobile", - "version": "1.1.7", - "main": "index.js", - "license": "MIT", - "scripts": { - "prewebpack:dev": "yarn --cwd ../.. build", - "webpack:dev": "yarn webpack serve --config webpack.dev.js", - "webpack:dev:onlyThis": "yarn webpack serve --config webpack.dev.js", - "webpack:prod": "yarn webpack --progress --config webpack.prod.js", - "tauri": "tauri", - "tauri:dev:android": "WRY_ANDROID_PACKAGE=net.nymtech.nym_connect WRY_ANDROID_LIBRARY=nym_connect cargo tauri android dev", - "tauri:build:android": "WRY_ANDROID_PACKAGE=net.nymtech.nym_connect WRY_ANDROID_LIBRARY=nym_connect cargo tauri android build", - "dev:android": "run-p webpack:dev:onlyThis tauri:dev:android", - "prebuild": "yarn --cwd ../.. build", - "build:android": "run-s webpack:prod tauri:build:android", - "storybook": "start-storybook -p 6006", - "prestorybook:build": "yarn --cwd ../.. build", - "storybook:build": "build-storybook", - "tsc": "tsc --noEmit true", - "tsc:watch": "tsc --noEmit true --watch", - "lint": "eslint src", - "lint:fix": "eslint src --fix", - "test": "jest", - "test:watch": "jest --watch", - "clean:dist": "rm -rf dist", - "clean:node": "rm -rf node_modules", - "clean:rust": "cargo clean --manifest-path src-tauri/Cargo.toml", - "clean:android": "cd src-tauri/gen/android/nym_connect/ && gradlew clean", - "clean:": "run-p clean:node clean:rust clean:android" - }, - "dependencies": { - "@emotion/react": "^11.7.0", - "@emotion/styled": "^11.6.0", - "@hookform/resolvers": "^2.8.0", - "@mui/icons-material": "^5.2.0", - "@mui/material": "^5.2.2", - "@mui/styles": "^5.2.2", - "@mui/system": ">= 5", - "@mui/lab": "^5.0.0-alpha.72", - "@nymproject/react": "^1.0.0", - "@tauri-apps/api": "^2.0.0-alpha.0", - "@tauri-apps/tauri-forage": "^1.0.0-beta.2", - "clsx": "^1.1.1", - "luxon": "^2.3.0", - "pretty-bytes": "^6.0.0", - "react": "^18.2.0", - "react-dom": "^18.2.0", - "react-error-boundary": "^3.1.3", - "react-hook-form": "^7.14.2", - "react-markdown": "^8.0.4", - "react-router-dom": "^6.7.0", - "semver": "^6.3.0", - "yup": "^0.32.9" - }, - "devDependencies": { - "@babel/core": "^7.15.0", - "@babel/plugin-transform-async-to-generator": "^7.14.5", - "@babel/preset-env": "^7.15.0", - "@babel/preset-react": "^7.14.5", - "@babel/preset-typescript": "^7.15.0", - "@mdx-js/loader": "^2.1.5", - "@nymproject/eslint-config-react-typescript": "^1.0.0", - "@pmmmwh/react-refresh-webpack-plugin": "^0.5.4", - "@storybook/react": "^6.5.15", - "@svgr/webpack": "^6.1.1", - "@tauri-apps/cli": "^2.0.0-alpha.2", - "@testing-library/jest-dom": "^5.14.1", - "@testing-library/react": "^12.0.0", - "@types/jest": "^27.0.1", - "@types/luxon": "^2.3.2", - "@types/node": "^16.7.13", - "@types/react": "^18.0.26", - "@types/react-dom": "^18.0.10", - "@types/semver": "^7.3.8", - "@types/uuid": "^8.3.4", - "@typescript-eslint/eslint-plugin": "^5.13.0", - "@typescript-eslint/parser": "^5.13.0", - "babel-loader": "^8.3.0", - "babel-plugin-root-import": "^6.6.0", - "clean-webpack-plugin": "^4.0.0", - "css-loader": "^6.7.3", - "css-minimizer-webpack-plugin": "^3.0.2", - "dotenv-webpack": "^7.0.3", - "eslint": "^8.10.0", - "eslint-config-airbnb": "^19.0.4", - "eslint-config-airbnb-typescript": "^16.1.0", - "eslint-config-prettier": "^8.5.0", - "eslint-import-resolver-root-import": "^1.0.4", - "eslint-plugin-import": "^2.25.4", - "eslint-plugin-jest": "^26.1.1", - "eslint-plugin-jsx-a11y": "^6.5.1", - "eslint-plugin-prettier": "^4.0.0", - "eslint-plugin-react": "^7.29.2", - "eslint-plugin-react-hooks": "^4.3.0", - "eslint-plugin-storybook": "^0.5.12", - "file-loader": "^6.2.0", - "fork-ts-checker-webpack-plugin": "^7.2.1", - "html-webpack-plugin": "^5.3.2", - "jest": "^27.1.0", - "mini-css-extract-plugin": "^2.2.2", - "npm-run-all": "^4.1.5", - "prettier": "^2.8.7", - "react-refresh": "^0.10.0", - "react-refresh-typescript": "^2.0.2", - "style-loader": "^3.3.1", - "thread-loader": "^3.0.4", - "ts-jest": "^27.0.5", - "ts-loader": "^9.4.2", - "tsconfig-paths-webpack-plugin": "^3.5.2", - "typescript": "^4.6.2", - "url-loader": "^4.1.1", - "webpack": "^5.75.0", - "webpack-cli": "^4.8.0", - "webpack-dev-server": "^4.5.0", - "webpack-merge": "^5.8.0", - "yaml-loader": "^0.8.0" - } -} diff --git a/nym-connect/mobile/public/favicon.ico b/nym-connect/mobile/public/favicon.ico deleted file mode 100644 index bc951a1c6f2c406072df036cea6e73163f6a687e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5430 zcmc&%drVtp6tBjAwtraU57|Idos~Ef+d5%b>g&1$p%NeV5&G3m5tT=9hdq_kQ=B z^E=Xx-n) zt#yjjH3Lnu{O{}%;^v}6adT0w`qr*D_ca-f4$)@vo5?uW^6<56{qN!y1~PI3zTLhSUpCWL!`J&HO0On^xEC|`)_dLgKOS*tgq5y0@uh0 z;BSon*P>z|U)tMr;?xgzW(`wEUmrAoa>L74Ra^)Q3kz_+=Pvz?&8tfN#-3i>y2xH* z%bzYQhJTT4U3C?F{Nc3?^0K6+#m(f;RFu-PZ&y{)wrnQPWq+@&hnB6aEcfn%OCHAC7^bOZ*PQf7!rS;)nRo!4?-g)PewDc!POtFA4GSVmZnHXSG z;ekjcH+#8G$ME!7d?mJE!QPT}aQwl*45$+e4ZNutwI3yn` z0IjMMj9Q1yq@|(*v4ObYjL*I3bY+)zKrCMn0`9u|rwa?yAT^Yc(|F?d{Ai|c zO~d%f-~My6(qLkIjK-RGyP-s{q4yC8zBslB zd36MWpA@6U_{*W-Yffgt)Z`?MJva9l3e-w3HuhPN_(Az!C(VZGsVN%!@$4*^@)SPU zf#S3LA8`Ctn2j(qGfiX9&dh+3kZ!;Z6yF;A#`Yxl2((_XGUgu2Fc|I$97>yN+wm-++a?{@tqL^a@6^%w1fU8+AZwk%`o z`V-^h;ZuL_KrI*-ME$*L0kVPJe~6!M|H0Vc+kgFx`mdrO`Y+42`Y*-?OmY9IJ~z~c z`%jfm|5@^+|J1JbpJ~4Rr#|1&ww?ZyZ3BMB{kNu?GHI#@Tb}gaW2paLM*a7)u554& W_upg@)k0b*(@yr^>+RU-_x}JZi=y2C diff --git a/nym-connect/mobile/public/favicon.png b/nym-connect/mobile/public/favicon.png deleted file mode 100644 index fcf1e747d0d569a0ed17d42f8976f9161fd001f7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1312 zcmZ`%Yfw{H5Wa{3o2pom@(68XE!Q+bxryNw36hw1?hSb-BoIP?2vtD^OI4~dQmeMq z5nD?u228CT9d*PRwW8CJVp|-=fI?8>g@BRr3NIy43EJzQPWz)fd%kba?tW)>=IlYK zgue(Mg$Dp&kx;;qV@!N~76f9*Id$lFjOK4(AuIr>Km4XY2lu*|E07}quoDCTO9cSD zz*3e00N4%zz>pCD7}Wp}zNMx?mI46tR!dtPT0Lm;*x1ya*g{_#Fd_b!aBDFaUmJ~h}ms{U9iq$5xMumF0rmtq;c?IHRu*u&t};_Go#qd1|^!Q?A#>% zNzE-^&&)|oR6ukiJ*hl3^|V;fp;t7R1lh?61OjPkI7o;h8K9)A2;wM)vl#KA5nyOI zn7KOXu2gZ+#08g=!FMRImbp?%S2!_<&IEgVA3-w$=8dx zS6H&_b@XG^w$6uL_a8XfJb8!nQ5HG5YmYcOnso8f)mN`xjgL=o1WNzh93hhQfUiAc zW>3$|OiWDjMCwy(;oa#Bt=>E_iPFF7jHkXkq9vm8;nfdGG z%a?>`Qc{X=U|{H2b#3QE7cpi{0)v0gasLWSn#H6$%0v;7D{I#YK_WRTIsTeSU`S5L zf|5Enr5h6y`wIpKMLfrC`Ii|j)&68E~-nWi43W#I|PZzX2juK7?|1zjA)Dz}@ z5f*yXN=@5#sEXFU`x|BKWLj9^*^i94p~0<+NOXo_&K$*RKeAa-RlWzhSrAFX7MDHQ1x!&i3!2YA3E~S-XZ# zx04qX(Q={U{gi^j^939Es@9xFoDO-_}z1%i=xO#_v4NrmE<#IM;Uv z#R-M}50x?lG+-><70W^q4T}7p>f~ZPr0waD6 ogG#4B5CekN8h - - - - - Nym Connect - - -
    - - diff --git a/nym-connect/mobile/src-tauri/.gitignore b/nym-connect/mobile/src-tauri/.gitignore deleted file mode 100644 index 296ceb2535..0000000000 --- a/nym-connect/mobile/src-tauri/.gitignore +++ /dev/null @@ -1,5 +0,0 @@ -# Generated by Cargo -# will have compiled files and executables -/target/ -WixTools -/.cargo/ diff --git a/nym-connect/mobile/src-tauri/Cargo.lock b/nym-connect/mobile/src-tauri/Cargo.lock deleted file mode 100644 index c8bf74afe3..0000000000 --- a/nym-connect/mobile/src-tauri/Cargo.lock +++ /dev/null @@ -1,7748 +0,0 @@ -# This file is automatically @generated by Cargo. -# It is not intended for manual editing. -version = 3 - -[[package]] -name = "Inflector" -version = "0.11.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe438c63458706e03479442743baae6c88256498e6431708f6dfc520a26515d3" - -[[package]] -name = "adler" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" - -[[package]] -name = "aead" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c192eb8f11fc081b0fe4259ba5af04217d4e0faddd02417310a927911abd7c8" -dependencies = [ - "crypto-common", - "generic-array 0.14.7", -] - -[[package]] -name = "aes" -version = "0.7.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e8b47f52ea9bae42228d07ec09eb676433d7c4ed1ebdf0f1d1c29ed446f1ab8" -dependencies = [ - "cfg-if", - "cipher 0.3.0", - "cpufeatures", - "ctr 0.8.0", - "opaque-debug 0.3.0", -] - -[[package]] -name = "aes" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "433cfd6710c9986c576a25ca913c39d66a6474107b406f34f91d4a8923395241" -dependencies = [ - "cfg-if", - "cipher 0.4.4", - "cpufeatures", -] - -[[package]] -name = "ahash" -version = "0.7.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47" -dependencies = [ - "getrandom 0.2.10", - "once_cell", - "version_check", -] - -[[package]] -name = "aho-corasick" -version = "0.7.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc936419f96fa211c1b9166887b38e5e40b19958e5b895be7c1f93adec7071ac" -dependencies = [ - "memchr", -] - -[[package]] -name = "alloc-no-stdlib" -version = "2.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc7bb162ec39d46ab1ca8c77bf72e890535becd1751bb45f64c597edb4c8c6b3" - -[[package]] -name = "alloc-stdlib" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94fb8275041c72129eb51b7d0322c29b8387a0386127718b096429201a5d6ece" -dependencies = [ - "alloc-no-stdlib", -] - -[[package]] -name = "anyhow" -version = "1.0.70" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7de8ce5e0f9f8d88245311066a578d72b7af3e7088f32783804676302df237e4" - -[[package]] -name = "arrayref" -version = "0.3.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b4930d2cb77ce62f89ee5d5289b4ac049559b1c45539271f5ed4fdc7db34545" - -[[package]] -name = "arrayvec" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b" - -[[package]] -name = "arrayvec" -version = "0.7.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8da52d66c7071e2e3fa2a1e5c6d088fec47b593032b254f5e980de8ea54454d6" - -[[package]] -name = "async-trait" -version = "0.1.67" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86ea188f25f0255d8f92797797c97ebf5631fa88178beb1a46fdf5622c9a00e4" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.5", -] - -[[package]] -name = "atk" -version = "0.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39991bc421ddf72f70159011b323ff49b0f783cc676a7287c59453da2e2531cf" -dependencies = [ - "atk-sys", - "bitflags 1.3.2", - "glib", - "libc", -] - -[[package]] -name = "atk-sys" -version = "0.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11ad703eb64dc058024f0e57ccfa069e15a413b98dbd50a1a950e743b7f11148" -dependencies = [ - "glib-sys", - "gobject-sys", - "libc", - "system-deps", -] - -[[package]] -name = "atoi" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "616896e05fc0e2649463a93a15183c6a16bf03413a7af88ef1285ddedfa9cda5" -dependencies = [ - "num-traits", -] - -[[package]] -name = "atoi" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7c57d12312ff59c811c0643f4d80830505833c9ffaebd193d819392b265be8e" -dependencies = [ - "num-traits", -] - -[[package]] -name = "attohttpc" -version = "0.24.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d9a9bf8b79a749ee0b911b91b671cc2b6c670bdbc7e3dfd537576ddc94bb2a2" -dependencies = [ - "flate2", - "http", - "log", - "native-tls", - "serde", - "serde_json", - "serde_urlencoded", - "url", -] - -[[package]] -name = "atty" -version = "0.2.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" -dependencies = [ - "hermit-abi 0.1.19", - "libc", - "winapi", -] - -[[package]] -name = "autocfg" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" - -[[package]] -name = "base16ct" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "349a06037c7bf932dd7e7d1f653678b2038b9ad46a74102f1fc7bd7872678cce" - -[[package]] -name = "base16ct" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" - -[[package]] -name = "base64" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" - -[[package]] -name = "base64" -version = "0.21.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "604178f6c5c21f02dc555784810edfb88d34ac2c73b2eae109655649ee73ce3d" - -[[package]] -name = "base64ct" -version = "1.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" - -[[package]] -name = "bincode" -version = "1.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" -dependencies = [ - "serde", -] - -[[package]] -name = "bip32" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e141fb0f8be1c7b45887af94c88b182472b57c96b56773250ae00cd6a14a164" -dependencies = [ - "bs58 0.5.0", - "hmac 0.12.1", - "k256 0.13.1", - "once_cell", - "pbkdf2", - "rand_core 0.6.4", - "ripemd", - "sha2 0.10.6", - "subtle 2.4.1", - "zeroize", -] - -[[package]] -name = "bip39" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93f2635620bf0b9d4576eb7bb9a38a55df78bd1205d26fa994b25911a69f212f" -dependencies = [ - "bitcoin_hashes", - "rand 0.8.5", - "rand_core 0.6.4", - "serde", - "unicode-normalization", - "zeroize", -] - -[[package]] -name = "bitcoin_hashes" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90064b8dee6815a6470d60bad07bbbaee885c0e12d04177138fa3291a01b7bc4" - -[[package]] -name = "bitflags" -version = "1.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" - -[[package]] -name = "bitflags" -version = "2.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "487f1e0fcbe47deb8b0574e646def1c903389d95241dd1bbcc6ce4a715dfc0c1" - -[[package]] -name = "bitvec" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c" -dependencies = [ - "funty", - "radium", - "tap", - "wyz", -] - -[[package]] -name = "blake2" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94cb07b0da6a73955f8fb85d24c466778e70cda767a568229b104f0264089330" -dependencies = [ - "byte-tools", - "crypto-mac 0.7.0", - "digest 0.8.1", - "opaque-debug 0.2.3", -] - -[[package]] -name = "blake3" -version = "1.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42ae2468a89544a466886840aa467a25b766499f4f04bf7d9fcd10ecee9fccef" -dependencies = [ - "arrayref", - "arrayvec 0.7.2", - "cc", - "cfg-if", - "constant_time_eq", - "digest 0.10.7", -] - -[[package]] -name = "block" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d8c1fef690941d3e7788d328517591fecc684c084084702d6ff1641e993699a" - -[[package]] -name = "block-buffer" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" -dependencies = [ - "generic-array 0.14.7", -] - -[[package]] -name = "block-buffer" -version = "0.10.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" -dependencies = [ - "generic-array 0.14.7", -] - -[[package]] -name = "bls12_381" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54757888b09a69be70b5ec303e382a74227392086ba808cb01eeca29233a2397" -dependencies = [ - "digest 0.9.0", - "ff 0.10.1", - "group 0.10.0", - "pairing 0.20.0", - "rand_core 0.6.4", - "subtle 2.4.1", -] - -[[package]] -name = "bls12_381" -version = "0.6.0" -source = "git+https://github.com/jstuczyn/bls12_381?branch=gt-serialisation#10fb6f700bfda17c8475af3bfd31e3fec15f2278" -dependencies = [ - "digest 0.9.0", - "ff 0.11.1", - "group 0.11.0", - "pairing 0.21.0", - "rand_core 0.6.4", - "subtle 2.4.1", - "zeroize", -] - -[[package]] -name = "bnum" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "845141a4fade3f790628b7daaaa298a25b204fb28907eb54febe5142db6ce653" - -[[package]] -name = "brotli" -version = "3.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1a0b1dbcc8ae29329621f8d4f0d835787c1c38bb1401979b49d13b0b305ff68" -dependencies = [ - "alloc-no-stdlib", - "alloc-stdlib", - "brotli-decompressor", -] - -[[package]] -name = "brotli-decompressor" -version = "2.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b6561fd3f895a11e8f72af2cb7d22e08366bebc2b6b57f7744c4bda27034744" -dependencies = [ - "alloc-no-stdlib", - "alloc-stdlib", -] - -[[package]] -name = "bs58" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "771fe0050b883fcc3ea2359b1a96bcfbc090b7116eae7c3c512c7a083fdf23d3" - -[[package]] -name = "bs58" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f5353f36341f7451062466f0b755b96ac3a9547e4d7f6b70d603fc721a7d7896" -dependencies = [ - "sha2 0.10.6", -] - -[[package]] -name = "bstr" -version = "1.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3d4260bcc2e8fc9df1eac4919a720effeb63a3f0952f5bf4944adfa18897f09" -dependencies = [ - "memchr", - "serde", -] - -[[package]] -name = "bumpalo" -version = "3.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d261e256854913907f67ed06efbc3338dfe6179796deefc1ff763fc1aee5535" - -[[package]] -name = "byte-tools" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3b5ca7a04898ad4bcd41c90c5285445ff5b791899bb1b0abdd2a2aa791211d7" - -[[package]] -name = "bytemuck" -version = "1.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17febce684fd15d89027105661fec94afb475cb995fbc59d2865198446ba2eea" - -[[package]] -name = "byteorder" -version = "1.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" - -[[package]] -name = "bytes" -version = "1.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be" -dependencies = [ - "serde", -] - -[[package]] -name = "cairo-rs" -version = "0.16.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3125b15ec28b84c238f6f476c6034016a5f6cc0221cb514ca46c532139fc97d" -dependencies = [ - "bitflags 1.3.2", - "cairo-sys-rs", - "glib", - "libc", - "once_cell", - "thiserror", -] - -[[package]] -name = "cairo-sys-rs" -version = "0.16.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c48f4af05fabdcfa9658178e1326efa061853f040ce7d72e33af6885196f421" -dependencies = [ - "glib-sys", - "libc", - "system-deps", -] - -[[package]] -name = "cargo_toml" -version = "0.14.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2bfbc36312494041e2cdd5f06697b7e89d4b76f42773a0b5556ac290ff22acc2" -dependencies = [ - "serde", - "toml 0.5.11", -] - -[[package]] -name = "cc" -version = "1.0.79" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f" -dependencies = [ - "jobserver", -] - -[[package]] -name = "cesu8" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" - -[[package]] -name = "cfb" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d38f2da7a0a2c4ccf0065be06397cc26a81f4e528be095826eee9d4adbb8c60f" -dependencies = [ - "byteorder", - "fnv", - "uuid 1.3.0", -] - -[[package]] -name = "cfg-expr" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0357a6402b295ca3a86bc148e84df46c02e41f41fef186bda662557ef6328aa" -dependencies = [ - "smallvec", -] - -[[package]] -name = "cfg-if" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" - -[[package]] -name = "chacha" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ddf3c081b5fba1e5615640aae998e0fbd10c24cbd897ee39ed754a77601a4862" -dependencies = [ - "byteorder", - "keystream", -] - -[[package]] -name = "chacha20" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7fc89c7c5b9e7a02dfe45cd2367bae382f9ed31c61ca8debe5f827c420a2f08" -dependencies = [ - "cfg-if", - "cipher 0.4.4", - "cpufeatures", -] - -[[package]] -name = "chacha20poly1305" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10cd79432192d1c0f4e1a0fef9527696cc039165d729fb41b3f4f4f354c2dc35" -dependencies = [ - "aead", - "chacha20", - "cipher 0.4.4", - "poly1305", - "zeroize", -] - -[[package]] -name = "cipher" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ee52072ec15386f770805afd189a01c8841be8696bed250fa2f13c4c0d6dfb7" -dependencies = [ - "generic-array 0.14.7", -] - -[[package]] -name = "cipher" -version = "0.4.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" -dependencies = [ - "crypto-common", - "inout", - "zeroize", -] - -[[package]] -name = "clap" -version = "4.1.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42dfd32784433290c51d92c438bb72ea5063797fc3cc9a21a8c4346bebbb2098" -dependencies = [ - "bitflags 2.0.2", - "clap_derive", - "clap_lex", - "is-terminal", - "once_cell", - "strsim", - "termcolor", -] - -[[package]] -name = "clap_complete" -version = "4.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37686beaba5ac9f3ab01ee3172f792fc6ffdd685bfb9e63cfef02c0571a4e8e1" -dependencies = [ - "clap", -] - -[[package]] -name = "clap_complete_fig" -version = "4.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2171bc6242ad7a1801422bff039574449b5bd832b715222e500714ce10f91a54" -dependencies = [ - "clap", - "clap_complete", -] - -[[package]] -name = "clap_derive" -version = "4.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fddf67631444a3a3e3e5ac51c36a5e01335302de677bd78759eaa90ab1f46644" -dependencies = [ - "heck 0.4.1", - "proc-macro-error", - "proc-macro2", - "quote", - "syn 1.0.109", -] - -[[package]] -name = "clap_lex" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "033f6b7a4acb1f358c742aaca805c939ee73b4c6209ae4318ec7aca81c42e646" -dependencies = [ - "os_str_bytes", -] - -[[package]] -name = "cocoa" -version = "0.24.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f425db7937052c684daec3bd6375c8abe2d146dca4b8b143d6db777c39138f3a" -dependencies = [ - "bitflags 1.3.2", - "block", - "cocoa-foundation", - "core-foundation", - "core-graphics", - "foreign-types", - "libc", - "objc", -] - -[[package]] -name = "cocoa-foundation" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "931d3837c286f56e3c58423ce4eba12d08db2374461a785c86f672b08b5650d6" -dependencies = [ - "bitflags 1.3.2", - "block", - "core-foundation", - "core-graphics-types", - "foreign-types", - "libc", - "objc", -] - -[[package]] -name = "color_quant" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" - -[[package]] -name = "colored" -version = "1.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4ffc801dacf156c5854b9df4f425a626539c3a6ef7893cc0c5084a23f0b6c59" -dependencies = [ - "atty", - "lazy_static", - "winapi", -] - -[[package]] -name = "colored" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3616f750b84d8f0de8a58bda93e08e2a81ad3f523089b05f1dffecab48c6cbd" -dependencies = [ - "atty", - "lazy_static", - "winapi", -] - -[[package]] -name = "combine" -version = "4.6.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35ed6e9d84f0b51a7f52daf1c7d71dd136fd7a3f41a8462b8cdb8c78d920fad4" -dependencies = [ - "bytes", - "memchr", -] - -[[package]] -name = "const-oid" -version = "0.9.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28c122c3980598d243d63d9a704629a2d748d101f278052ff068be5a4423ab6f" - -[[package]] -name = "constant_time_eq" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13418e745008f7349ec7e449155f419a61b92b58a99cc3616942b926825ec76b" - -[[package]] -name = "convert_case" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" - -[[package]] -name = "core-foundation" -version = "0.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "194a7a9e6de53fa55116934067c844d9d749312f75c6f6d0980e8c252f8c2146" -dependencies = [ - "core-foundation-sys", - "libc", -] - -[[package]] -name = "core-foundation-sys" -version = "0.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc" - -[[package]] -name = "core-graphics" -version = "0.22.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2581bbab3b8ffc6fcbd550bf46c355135d16e9ff2a6ea032ad6b9bf1d7efe4fb" -dependencies = [ - "bitflags 1.3.2", - "core-foundation", - "core-graphics-types", - "foreign-types", - "libc", -] - -[[package]] -name = "core-graphics-types" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a68b68b3446082644c91ac778bf50cd4104bfb002b5a6a7c44cca5a2c70788b" -dependencies = [ - "bitflags 1.3.2", - "core-foundation", - "foreign-types", - "libc", -] - -[[package]] -name = "cosmos-sdk-proto" -version = "0.19.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73c9d2043a9e617b0d602fbc0a0ecd621568edbf3a9774890a6d562389bd8e1c" -dependencies = [ - "prost", - "prost-types", - "tendermint-proto", -] - -[[package]] -name = "cosmrs" -version = "0.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af13955d6f356272e6def9ff5e2450a7650df536d8934f47052a20c76513d2f6" -dependencies = [ - "bip32", - "cosmos-sdk-proto", - "ecdsa 0.16.8", - "eyre", - "getrandom 0.2.10", - "k256 0.13.1", - "rand_core 0.6.4", - "serde", - "serde_json", - "subtle-encoding", - "tendermint", - "tendermint-rpc", - "thiserror", -] - -[[package]] -name = "cosmwasm-crypto" -version = "1.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e272708a9745dad8b591ef8a718571512130f2b39b33e3d7a27c558e3069394" -dependencies = [ - "digest 0.10.7", - "ed25519-zebra", - "k256 0.11.6", - "rand_core 0.6.4", - "thiserror", -] - -[[package]] -name = "cosmwasm-derive" -version = "1.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "296db6a3caca5283425ae0cf347f4e46999ba3f6620dbea8939a0e00347831ce" -dependencies = [ - "syn 1.0.109", -] - -[[package]] -name = "cosmwasm-schema" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb6b2fb76758ef59cddc77f2e2ae91c22f77da49037e9f182e9c2833f0e959b1" -dependencies = [ - "cosmwasm-schema-derive", - "schemars", - "serde", - "serde_json", - "thiserror", -] - -[[package]] -name = "cosmwasm-schema-derive" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2bfa39422f0d9f1c9a6fd3711573258495314dfa3aae738ea825ecd9964bc659" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.109", -] - -[[package]] -name = "cosmwasm-std" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f6dc2ee23313add5ecacc3ccac217b9967ad9d2d11bd56e5da6aa65a9da6138" -dependencies = [ - "base64 0.13.1", - "bnum", - "cosmwasm-crypto", - "cosmwasm-derive", - "derivative", - "forward_ref", - "hex", - "schemars", - "serde", - "serde-json-wasm", - "sha2 0.10.6", - "thiserror", -] - -[[package]] -name = "cpufeatures" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28d997bd5e24a5928dd43e46dc529867e207907fe0b239c3477d924f7f2ca320" -dependencies = [ - "libc", -] - -[[package]] -name = "crc" -version = "2.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49fc9a695bca7f35f5f4c15cddc84415f66a74ea78eef08e90c5024f2b540e23" -dependencies = [ - "crc-catalog 1.1.1", -] - -[[package]] -name = "crc" -version = "3.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86ec7a15cbe22e59248fc7eadb1907dab5ba09372595da4d73dd805ed4417dfe" -dependencies = [ - "crc-catalog 2.2.0", -] - -[[package]] -name = "crc-catalog" -version = "1.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ccaeedb56da03b09f598226e25e80088cb4cd25f316e6e4df7d695f0feeb1403" - -[[package]] -name = "crc-catalog" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9cace84e55f07e7301bae1c519df89cdad8cc3cd868413d3fdbdeca9ff3db484" - -[[package]] -name = "crc32fast" -version = "1.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" -dependencies = [ - "cfg-if", -] - -[[package]] -name = "crossbeam-channel" -version = "0.5.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf2b3e8478797446514c91ef04bafcb59faba183e621ad488df88983cc14128c" -dependencies = [ - "cfg-if", - "crossbeam-utils", -] - -[[package]] -name = "crossbeam-deque" -version = "0.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce6fd6f855243022dcecf8702fef0c297d4338e226845fe067f6341ad9fa0cef" -dependencies = [ - "cfg-if", - "crossbeam-epoch", - "crossbeam-utils", -] - -[[package]] -name = "crossbeam-epoch" -version = "0.9.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46bd5f3f85273295a9d14aedfb86f6aadbff6d8f5295c4a9edb08e819dcf5695" -dependencies = [ - "autocfg", - "cfg-if", - "crossbeam-utils", - "memoffset", - "scopeguard", -] - -[[package]] -name = "crossbeam-queue" -version = "0.3.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1cfb3ea8a53f37c40dea2c7bedcbd88bdfae54f5e2175d6ecaff1c988353add" -dependencies = [ - "cfg-if", - "crossbeam-utils", -] - -[[package]] -name = "crossbeam-utils" -version = "0.8.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c063cd8cc95f5c377ed0d4b49a4b21f632396ff690e8470c29b3359b346984b" -dependencies = [ - "cfg-if", -] - -[[package]] -name = "crypto-bigint" -version = "0.4.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef2b4b23cddf68b89b8f8069890e8c270d54e2d5fe1b143820234805e4cb17ef" -dependencies = [ - "generic-array 0.14.7", - "rand_core 0.6.4", - "subtle 2.4.1", - "zeroize", -] - -[[package]] -name = "crypto-bigint" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf4c2f4e1afd912bc40bfd6fed5d9dc1f288e0ba01bfcc835cc5bc3eb13efe15" -dependencies = [ - "generic-array 0.14.7", - "rand_core 0.6.4", - "subtle 2.4.1", - "zeroize", -] - -[[package]] -name = "crypto-common" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" -dependencies = [ - "generic-array 0.14.7", - "rand_core 0.6.4", - "typenum", -] - -[[package]] -name = "crypto-mac" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4434400df11d95d556bac068ddfedd482915eb18fe8bea89bc80b6e4b1c179e5" -dependencies = [ - "generic-array 0.12.4", - "subtle 1.0.0", -] - -[[package]] -name = "crypto-mac" -version = "0.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1d1a86f49236c215f271d40892d5fc950490551400b02ef360692c29815c714" -dependencies = [ - "generic-array 0.14.7", - "subtle 2.4.1", -] - -[[package]] -name = "cssparser" -version = "0.27.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "754b69d351cdc2d8ee09ae203db831e005560fc6030da058f86ad60c92a9cb0a" -dependencies = [ - "cssparser-macros", - "dtoa-short", - "itoa 0.4.8", - "matches", - "phf 0.8.0", - "proc-macro2", - "quote", - "smallvec", - "syn 1.0.109", -] - -[[package]] -name = "cssparser-macros" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfae75de57f2b2e85e8768c3ea840fd159c8f33e2b6522c7835b7abac81be16e" -dependencies = [ - "quote", - "syn 1.0.109", -] - -[[package]] -name = "ct-logs" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1a816186fa68d9e426e3cb4ae4dff1fcd8e4a2c34b781bf7a822574a0d0aac8" -dependencies = [ - "sct 0.6.1", -] - -[[package]] -name = "ctor" -version = "0.1.26" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d2301688392eb071b0bf1a37be05c469d3cc4dbbd95df672fe28ab021e6a096" -dependencies = [ - "quote", - "syn 1.0.109", -] - -[[package]] -name = "ctr" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "049bb91fb4aaf0e3c7efa6cd5ef877dbbbd15b39dad06d9948de4ec8a75761ea" -dependencies = [ - "cipher 0.3.0", -] - -[[package]] -name = "ctr" -version = "0.9.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0369ee1ad671834580515889b80f2ea915f23b8be8d0daa4bbaf2ac5c7590835" -dependencies = [ - "cipher 0.4.4", -] - -[[package]] -name = "curve25519-dalek" -version = "3.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b9fdf9972b2bd6af2d913799d9ebc165ea4d2e65878e329d9c6b372c4491b61" -dependencies = [ - "byteorder", - "digest 0.9.0", - "rand_core 0.5.1", - "subtle 2.4.1", - "zeroize", -] - -[[package]] -name = "curve25519-dalek-ng" -version = "4.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c359b7249347e46fb28804470d071c921156ad62b3eef5d34e2ba867533dec8" -dependencies = [ - "byteorder", - "digest 0.9.0", - "rand_core 0.6.4", - "subtle-ng", - "zeroize", -] - -[[package]] -name = "cw-controllers" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5d8edce4b78785f36413f67387e4be7d0cb7d032b5d4164bcc024f9c3f3f2ea" -dependencies = [ - "cosmwasm-schema", - "cosmwasm-std", - "cw-storage-plus", - "cw-utils", - "schemars", - "serde", - "thiserror", -] - -[[package]] -name = "cw-storage-plus" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f0e92a069d62067f3472c62e30adedb4cab1754725c0f2a682b3128d2bf3c79" -dependencies = [ - "cosmwasm-std", - "schemars", - "serde", -] - -[[package]] -name = "cw-utils" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c80e93d1deccb8588db03945016a292c3c631e6325d349ebb35d2db6f4f946f7" -dependencies = [ - "cosmwasm-schema", - "cosmwasm-std", - "cw2", - "schemars", - "semver 1.0.17", - "serde", - "thiserror", -] - -[[package]] -name = "cw2" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29ac2dc7a55ad64173ca1e0a46697c31b7a5c51342f55a1e84a724da4eb99908" -dependencies = [ - "cosmwasm-schema", - "cosmwasm-std", - "cw-storage-plus", - "schemars", - "serde", - "thiserror", -] - -[[package]] -name = "cw20" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "011c45920f8200bd5d32d4fe52502506f64f2f75651ab408054d4cfc75ca3a9b" -dependencies = [ - "cosmwasm-schema", - "cosmwasm-std", - "cw-utils", - "schemars", - "serde", -] - -[[package]] -name = "cw3" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "171af3d9127de6805a7dd819fb070c7d2f6c3ea85f4193f42cef259f0a7f33d5" -dependencies = [ - "cosmwasm-schema", - "cosmwasm-std", - "cw-utils", - "cw20", - "schemars", - "serde", - "thiserror", -] - -[[package]] -name = "cw4" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a398696307efadaaa2d0850076f865fa706c959d493cb4203314f72be6b77a64" -dependencies = [ - "cosmwasm-schema", - "cosmwasm-std", - "cw-storage-plus", - "schemars", - "serde", -] - -[[package]] -name = "darling" -version = "0.13.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a01d95850c592940db9b8194bc39f4bc0e89dee5c4265e4b1807c34a9aba453c" -dependencies = [ - "darling_core", - "darling_macro", -] - -[[package]] -name = "darling_core" -version = "0.13.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "859d65a907b6852c9361e3185c862aae7fafd2887876799fa55f5f99dc40d610" -dependencies = [ - "fnv", - "ident_case", - "proc-macro2", - "quote", - "strsim", - "syn 1.0.109", -] - -[[package]] -name = "darling_macro" -version = "0.13.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c972679f83bdf9c42bd905396b6c3588a843a17f0f16dfcfa3e2c5d57441835" -dependencies = [ - "darling_core", - "quote", - "syn 1.0.109", -] - -[[package]] -name = "dashmap" -version = "5.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "907076dfda823b0b36d2a1bb5f90c96660a5bbcd7729e10727f07858f22c4edc" -dependencies = [ - "cfg-if", - "hashbrown 0.12.3", - "lock_api", - "once_cell", - "parking_lot_core 0.9.7", -] - -[[package]] -name = "dbus" -version = "0.9.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bb21987b9fb1613058ba3843121dd18b163b254d8a6e797e144cbac14d96d1b" -dependencies = [ - "libc", - "libdbus-sys", - "winapi", -] - -[[package]] -name = "der" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1a467a65c5e759bce6e65eaf91cc29f466cdc57cb65777bd646872a8a1fd4de" -dependencies = [ - "const-oid", - "zeroize", -] - -[[package]] -name = "der" -version = "0.7.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fffa369a668c8af7dbf8b5e56c9f744fbd399949ed171606040001947de40b1c" -dependencies = [ - "const-oid", - "zeroize", -] - -[[package]] -name = "derivative" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.109", -] - -[[package]] -name = "derive_more" -version = "0.99.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fb810d30a7c1953f91334de7244731fc3f3c10d7fe163338a35b9f640960321" -dependencies = [ - "convert_case", - "proc-macro2", - "quote", - "rustc_version", - "syn 1.0.109", -] - -[[package]] -name = "digest" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3d0c8c8752312f9713efd397ff63acb9f85585afbf179282e720e7704954dd5" -dependencies = [ - "generic-array 0.12.4", -] - -[[package]] -name = "digest" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" -dependencies = [ - "generic-array 0.14.7", -] - -[[package]] -name = "digest" -version = "0.10.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" -dependencies = [ - "block-buffer 0.10.4", - "const-oid", - "crypto-common", - "subtle 2.4.1", -] - -[[package]] -name = "dirs" -version = "4.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca3aa72a6f96ea37bbc5aa912f6788242832f75369bdfdadcb0e38423f100059" -dependencies = [ - "dirs-sys 0.3.7", -] - -[[package]] -name = "dirs" -version = "5.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44c45a9d03d6676652bcb5e724c7e988de1acad23a711b5217ab9cbecbec2225" -dependencies = [ - "dirs-sys 0.4.1", -] - -[[package]] -name = "dirs-next" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b98cf8ebf19c3d1b223e151f99a4f9f0690dca41414773390fc824184ac833e1" -dependencies = [ - "cfg-if", - "dirs-sys-next", -] - -[[package]] -name = "dirs-sys" -version = "0.3.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b1d1d91c932ef41c0f2663aa8b0ca0342d444d842c06914aa0a7e352d0bada6" -dependencies = [ - "libc", - "redox_users", - "winapi", -] - -[[package]] -name = "dirs-sys" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "520f05a5cbd335fae5a99ff7a6ab8627577660ee5cfd6a94a6a929b52ff0321c" -dependencies = [ - "libc", - "option-ext", - "redox_users", - "windows-sys 0.48.0", -] - -[[package]] -name = "dirs-sys-next" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d" -dependencies = [ - "libc", - "redox_users", - "winapi", -] - -[[package]] -name = "dispatch" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd0c93bb4b0c6d9b77f4435b0ae98c24d17f1c45b2ff844c6151a07256ca923b" - -[[package]] -name = "dotenv" -version = "0.15.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77c90badedccf4105eca100756a0b1289e191f6fcbdadd3cee1d2f614f97da8f" - -[[package]] -name = "dotenvy" -version = "0.15.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03d8c417d7a8cb362e0c37e5d815f5eb7c37f79ff93707329d5a194e42e54ca0" - -[[package]] -name = "dtoa" -version = "0.4.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56899898ce76aaf4a0f24d914c97ea6ed976d42fec6ad33fcbb0a1103e07b2b0" - -[[package]] -name = "dtoa-short" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bde03329ae10e79ede66c9ce4dc930aa8599043b0743008548680f25b91502d6" -dependencies = [ - "dtoa", -] - -[[package]] -name = "dunce" -version = "1.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bd4b30a6560bbd9b4620f4de34c3f14f60848e58a9b7216801afcb4c7b31c3c" - -[[package]] -name = "dyn-clone" -version = "1.0.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68b0cf012f1230e43cd00ebb729c6bb58707ecfa8ad08b52ef3a4ccd2697fc30" - -[[package]] -name = "ecdsa" -version = "0.14.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "413301934810f597c1d19ca71c8710e99a3f1ba28a0d2ebc01551a2daeea3c5c" -dependencies = [ - "der 0.6.1", - "elliptic-curve 0.12.3", - "rfc6979 0.3.1", - "signature 1.6.4", -] - -[[package]] -name = "ecdsa" -version = "0.16.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4b1e0c257a9e9f25f90ff76d7a68360ed497ee519c8e428d1825ef0000799d4" -dependencies = [ - "der 0.7.8", - "digest 0.10.7", - "elliptic-curve 0.13.5", - "rfc6979 0.4.0", - "signature 2.1.0", - "spki 0.7.2", -] - -[[package]] -name = "ed25519" -version = "1.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91cff35c70bba8a626e3185d8cd48cc11b5437e1a5bcd15b9b5fa3c64b6dfee7" -dependencies = [ - "signature 1.6.4", -] - -[[package]] -name = "ed25519" -version = "2.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60f6d271ca33075c88028be6f04d502853d63a5ece419d269c15315d4fc1cf1d" -dependencies = [ - "pkcs8 0.10.2", - "signature 2.1.0", -] - -[[package]] -name = "ed25519-consensus" -version = "2.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c8465edc8ee7436ffea81d21a019b16676ee3db267aa8d5a8d729581ecf998b" -dependencies = [ - "curve25519-dalek-ng", - "hex", - "rand_core 0.6.4", - "sha2 0.9.9", - "zeroize", -] - -[[package]] -name = "ed25519-dalek" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c762bae6dcaf24c4c84667b8579785430908723d5c889f469d76a41d59cc7a9d" -dependencies = [ - "curve25519-dalek", - "ed25519 1.5.3", - "rand 0.7.3", - "serde", - "sha2 0.9.9", - "zeroize", -] - -[[package]] -name = "ed25519-zebra" -version = "3.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c24f403d068ad0b359e577a77f92392118be3f3c927538f2bb544a5ecd828c6" -dependencies = [ - "curve25519-dalek", - "hashbrown 0.12.3", - "hex", - "rand_core 0.6.4", - "serde", - "sha2 0.9.9", - "zeroize", -] - -[[package]] -name = "either" -version = "1.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91" - -[[package]] -name = "elliptic-curve" -version = "0.12.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7bb888ab5300a19b8e5bceef25ac745ad065f3c9f7efc6de1b91958110891d3" -dependencies = [ - "base16ct 0.1.1", - "crypto-bigint 0.4.9", - "der 0.6.1", - "digest 0.10.7", - "ff 0.12.1", - "generic-array 0.14.7", - "group 0.12.1", - "pkcs8 0.9.0", - "rand_core 0.6.4", - "sec1 0.3.0", - "subtle 2.4.1", - "zeroize", -] - -[[package]] -name = "elliptic-curve" -version = "0.13.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "968405c8fdc9b3bf4df0a6638858cc0b52462836ab6b1c87377785dd09cf1c0b" -dependencies = [ - "base16ct 0.2.0", - "crypto-bigint 0.5.2", - "digest 0.10.7", - "ff 0.13.0", - "generic-array 0.14.7", - "group 0.13.0", - "pkcs8 0.10.2", - "rand_core 0.6.4", - "sec1 0.7.3", - "subtle 2.4.1", - "zeroize", -] - -[[package]] -name = "embed_plist" -version = "1.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ef6b89e5b37196644d8796de5268852ff179b44e96276cf4290264843743bb7" - -[[package]] -name = "encoding_rs" -version = "0.8.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "071a31f4ee85403370b58aca746f01041ede6f0da2730960ad001edc2b71b394" -dependencies = [ - "cfg-if", -] - -[[package]] -name = "enum-iterator" -version = "1.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45a0ac4aeb3a18f92eaf09c6bb9b3ac30ff61ca95514fc58cbead1c9a6bf5401" -dependencies = [ - "enum-iterator-derive", -] - -[[package]] -name = "enum-iterator-derive" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "355f93763ef7b0ae1c43c4d8eccc9d5848d84ad1a1d8ce61c421d1ac85a19d05" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.109", -] - -[[package]] -name = "env_logger" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44533bbbb3bb3c1fa17d9f2e4e38bbbaf8396ba82193c4cb1b6445d711445d36" -dependencies = [ - "atty", - "humantime 1.3.0", - "log", - "regex", - "termcolor", -] - -[[package]] -name = "equivalent" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" - -[[package]] -name = "errno" -version = "0.2.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f639046355ee4f37944e44f60642c6f3a7efa3cf6b78c78a0d989a8ce6c396a1" -dependencies = [ - "errno-dragonfly", - "libc", - "winapi", -] - -[[package]] -name = "errno-dragonfly" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" -dependencies = [ - "cc", - "libc", -] - -[[package]] -name = "event-listener" -version = "2.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" - -[[package]] -name = "eyre" -version = "0.6.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c2b6b5a29c02cdc822728b7d7b8ae1bab3e3b05d44522770ddd49722eeac7eb" -dependencies = [ - "indenter", - "once_cell", -] - -[[package]] -name = "fastrand" -version = "1.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e51093e27b0797c359783294ca4f0a911c270184cb10f85783b118614a1501be" -dependencies = [ - "instant", -] - -[[package]] -name = "fern" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3bdd7b0849075e79ee9a1836df22c717d1eba30451796fdc631b04565dd11e2a" -dependencies = [ - "colored 1.9.3", - "log", -] - -[[package]] -name = "ff" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0f40b2dcd8bc322217a5f6559ae5f9e9d1de202a2ecee2e9eafcbece7562a4f" -dependencies = [ - "rand_core 0.6.4", - "subtle 2.4.1", -] - -[[package]] -name = "ff" -version = "0.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "131655483be284720a17d74ff97592b8e76576dc25563148601df2d7c9080924" -dependencies = [ - "rand_core 0.6.4", - "subtle 2.4.1", -] - -[[package]] -name = "ff" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d013fc25338cc558c5c2cfbad646908fb23591e2404481826742b651c9af7160" -dependencies = [ - "rand_core 0.6.4", - "subtle 2.4.1", -] - -[[package]] -name = "ff" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ded41244b729663b1e574f1b4fb731469f69f79c17667b5d776b16cda0479449" -dependencies = [ - "rand_core 0.6.4", - "subtle 2.4.1", -] - -[[package]] -name = "field-offset" -version = "0.3.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3cf3a800ff6e860c863ca6d4b16fd999db8b752819c1606884047b73e468535" -dependencies = [ - "memoffset", - "rustc_version", -] - -[[package]] -name = "filetime" -version = "0.2.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a3de6e8d11b22ff9edc6d916f890800597d60f8b2da1caf2955c274638d6412" -dependencies = [ - "cfg-if", - "libc", - "redox_syscall", - "windows-sys 0.45.0", -] - -[[package]] -name = "fix-path-env" -version = "0.1.0" -source = "git+https://github.com/tauri-apps/fix-path-env-rs?branch=release#b7905421de7e4f589baf6d34a6dd0c3cb83197c3" -dependencies = [ - "strip-ansi-escapes", - "thiserror", -] - -[[package]] -name = "flate2" -version = "1.0.25" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8a2db397cb1c8772f31494cb8917e48cd1e64f0fa7efac59fbd741a0a8ce841" -dependencies = [ - "crc32fast", - "miniz_oxide", -] - -[[package]] -name = "flex-error" -version = "0.4.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c606d892c9de11507fa0dcffc116434f94e105d0bbdc4e405b61519464c49d7b" -dependencies = [ - "eyre", - "paste", -] - -[[package]] -name = "flume" -version = "0.10.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1657b4441c3403d9f7b3409e47575237dac27b1b5726df654a6ecbf92f0f7577" -dependencies = [ - "futures-core", - "futures-sink", - "pin-project", - "spin 0.9.6", -] - -[[package]] -name = "fnv" -version = "1.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" - -[[package]] -name = "foreign-types" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" -dependencies = [ - "foreign-types-shared", -] - -[[package]] -name = "foreign-types-shared" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" - -[[package]] -name = "form_urlencoded" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9c384f161156f5260c24a097c56119f9be8c798586aecc13afbcbe7b7e26bf8" -dependencies = [ - "percent-encoding", -] - -[[package]] -name = "forward_ref" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8cbd1169bd7b4a0a20d92b9af7a7e0422888bd38a6f5ec29c1fd8c1558a272e" - -[[package]] -name = "funty" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" - -[[package]] -name = "futf" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df420e2e84819663797d1ec6544b13c5be84629e7bb00dc960d6917db2987843" -dependencies = [ - "mac", - "new_debug_unreachable", -] - -[[package]] -name = "futures" -version = "0.3.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "531ac96c6ff5fd7c62263c5e3c67a603af4fcaee2e1a0ae5565ba3a11e69e549" -dependencies = [ - "futures-channel", - "futures-core", - "futures-executor", - "futures-io", - "futures-sink", - "futures-task", - "futures-util", -] - -[[package]] -name = "futures-channel" -version = "0.3.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "164713a5a0dcc3e7b4b1ed7d3b433cabc18025386f9339346e8daf15963cf7ac" -dependencies = [ - "futures-core", - "futures-sink", -] - -[[package]] -name = "futures-core" -version = "0.3.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86d7a0c1aa76363dac491de0ee99faf6941128376f1cf96f07db7603b7de69dd" - -[[package]] -name = "futures-executor" -version = "0.3.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1997dd9df74cdac935c76252744c1ed5794fac083242ea4fe77ef3ed60ba0f83" -dependencies = [ - "futures-core", - "futures-task", - "futures-util", -] - -[[package]] -name = "futures-intrusive" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a604f7a68fbf8103337523b1fadc8ade7361ee3f112f7c680ad179651616aed5" -dependencies = [ - "futures-core", - "lock_api", - "parking_lot 0.11.2", -] - -[[package]] -name = "futures-io" -version = "0.3.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89d422fa3cbe3b40dca574ab087abb5bc98258ea57eea3fd6f1fa7162c778b91" - -[[package]] -name = "futures-macro" -version = "0.3.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3eb14ed937631bd8b8b8977f2c198443447a8355b6e3ca599f38c975e5a963b6" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.109", -] - -[[package]] -name = "futures-sink" -version = "0.3.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec93083a4aecafb2a80a885c9de1f0ccae9dbd32c2bb54b0c3a65690e0b8d2f2" - -[[package]] -name = "futures-task" -version = "0.3.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd65540d33b37b16542a0438c12e6aeead10d4ac5d05bd3f805b8f35ab592879" - -[[package]] -name = "futures-util" -version = "0.3.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ef6b17e481503ec85211fed8f39d1970f128935ca1f814cd32ac4a6842e84ab" -dependencies = [ - "futures-channel", - "futures-core", - "futures-io", - "futures-macro", - "futures-sink", - "futures-task", - "memchr", - "pin-project-lite", - "pin-utils", - "slab", -] - -[[package]] -name = "fxhash" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c" -dependencies = [ - "byteorder", -] - -[[package]] -name = "gdk" -version = "0.16.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa9cb33da481c6c040404a11f8212d193889e9b435db2c14fd86987f630d3ce1" -dependencies = [ - "bitflags 1.3.2", - "cairo-rs", - "gdk-pixbuf", - "gdk-sys", - "gio", - "glib", - "libc", - "pango", -] - -[[package]] -name = "gdk-pixbuf" -version = "0.16.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3578c60dee9d029ad86593ed88cb40f35c1b83360e12498d055022385dd9a05" -dependencies = [ - "bitflags 1.3.2", - "gdk-pixbuf-sys", - "gio", - "glib", - "libc", -] - -[[package]] -name = "gdk-pixbuf-sys" -version = "0.16.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3092cf797a5f1210479ea38070d9ae8a5b8e9f8f1be9f32f4643c529c7d70016" -dependencies = [ - "gio-sys", - "glib-sys", - "gobject-sys", - "libc", - "system-deps", -] - -[[package]] -name = "gdk-sys" -version = "0.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d76354f97a913e55b984759a997b693aa7dc71068c9e98bcce51aa167a0a5c5a" -dependencies = [ - "cairo-sys-rs", - "gdk-pixbuf-sys", - "gio-sys", - "glib-sys", - "gobject-sys", - "libc", - "pango-sys", - "pkg-config", - "system-deps", -] - -[[package]] -name = "gdkx11-sys" -version = "0.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fa2bf8b5b8c414bc5d05e48b271896d0fd3ddb57464a3108438082da61de6af" -dependencies = [ - "gdk-sys", - "glib-sys", - "libc", - "system-deps", - "x11", -] - -[[package]] -name = "generator" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33a20a288a94683f5f4da0adecdbe095c94a77c295e514cc6484e9394dd8376e" -dependencies = [ - "cc", - "libc", - "log", - "rustversion", - "windows 0.44.0", -] - -[[package]] -name = "generic-array" -version = "0.12.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffdf9f34f1447443d37393cc6c2b8313aebddcd96906caf34e54c68d8e57d7bd" -dependencies = [ - "typenum", -] - -[[package]] -name = "generic-array" -version = "0.14.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" -dependencies = [ - "serde", - "typenum", - "version_check", - "zeroize", -] - -[[package]] -name = "getrandom" -version = "0.1.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce" -dependencies = [ - "cfg-if", - "js-sys", - "libc", - "wasi 0.9.0+wasi-snapshot-preview1", - "wasm-bindgen", -] - -[[package]] -name = "getrandom" -version = "0.2.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be4136b2a15dd319360be1c07d9933517ccf0be8f16bf62a3bee4f0d618df427" -dependencies = [ - "cfg-if", - "js-sys", - "libc", - "wasi 0.11.0+wasi-snapshot-preview1", - "wasm-bindgen", -] - -[[package]] -name = "getset" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e45727250e75cc04ff2846a66397da8ef2b3db8e40e0cef4df67950a07621eb9" -dependencies = [ - "proc-macro-error", - "proc-macro2", - "quote", - "syn 1.0.109", -] - -[[package]] -name = "gio" -version = "0.16.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a1c84b4534a290a29160ef5c6eff2a9c95833111472e824fc5cb78b513dd092" -dependencies = [ - "bitflags 1.3.2", - "futures-channel", - "futures-core", - "futures-io", - "futures-util", - "gio-sys", - "glib", - "libc", - "once_cell", - "pin-project-lite", - "smallvec", - "thiserror", -] - -[[package]] -name = "gio-sys" -version = "0.16.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e9b693b8e39d042a95547fc258a7b07349b1f0b48f4b2fa3108ba3c51c0b5229" -dependencies = [ - "glib-sys", - "gobject-sys", - "libc", - "system-deps", - "winapi", -] - -[[package]] -name = "git2" -version = "0.14.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0155506aab710a86160ddb504a480d2964d7ab5b9e62419be69e0032bc5931c" -dependencies = [ - "bitflags 1.3.2", - "libc", - "libgit2-sys", - "log", - "url", -] - -[[package]] -name = "glib" -version = "0.16.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ddd4df61a866ed7259d6189b8bcb1464989a77f1d85d25d002279bbe9dd38b2f" -dependencies = [ - "bitflags 1.3.2", - "futures-channel", - "futures-core", - "futures-executor", - "futures-task", - "futures-util", - "gio-sys", - "glib-macros", - "glib-sys", - "gobject-sys", - "libc", - "once_cell", - "smallvec", - "thiserror", -] - -[[package]] -name = "glib-macros" -version = "0.16.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e084807350b01348b6d9dbabb724d1a0bb987f47a2c85de200e98e12e30733bf" -dependencies = [ - "anyhow", - "heck 0.4.1", - "proc-macro-crate", - "proc-macro-error", - "proc-macro2", - "quote", - "syn 1.0.109", -] - -[[package]] -name = "glib-sys" -version = "0.16.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c61a4f46316d06bfa33a7ac22df6f0524c8be58e3db2d9ca99ccb1f357b62a65" -dependencies = [ - "libc", - "system-deps", -] - -[[package]] -name = "glob" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" - -[[package]] -name = "globset" -version = "0.4.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "029d74589adefde59de1a0c4f4732695c32805624aec7b68d91503d4dba79afc" -dependencies = [ - "aho-corasick", - "bstr", - "fnv", - "log", - "regex", -] - -[[package]] -name = "gloo-timers" -version = "0.2.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b995a66bb87bebce9a0f4a95aed01daca4872c050bfcb21653361c03bc35e5c" -dependencies = [ - "futures-channel", - "futures-core", - "js-sys", - "wasm-bindgen", -] - -[[package]] -name = "gobject-sys" -version = "0.16.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3520bb9c07ae2a12c7f2fbb24d4efc11231c8146a86956413fb1a79bb760a0f1" -dependencies = [ - "glib-sys", - "libc", - "system-deps", -] - -[[package]] -name = "group" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c363a5301b8f153d80747126a04b3c82073b9fe3130571a9d170cacdeaf7912" -dependencies = [ - "byteorder", - "ff 0.10.1", - "rand_core 0.6.4", - "subtle 2.4.1", -] - -[[package]] -name = "group" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc5ac374b108929de78460075f3dc439fa66df9d8fc77e8f12caa5165fcf0c89" -dependencies = [ - "byteorder", - "ff 0.11.1", - "rand_core 0.6.4", - "subtle 2.4.1", -] - -[[package]] -name = "group" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5dfbfb3a6cfbd390d5c9564ab283a0349b9b9fcd46a706c1eb10e0db70bfbac7" -dependencies = [ - "ff 0.12.1", - "rand_core 0.6.4", - "subtle 2.4.1", -] - -[[package]] -name = "group" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" -dependencies = [ - "ff 0.13.0", - "rand_core 0.6.4", - "subtle 2.4.1", -] - -[[package]] -name = "gtk" -version = "0.16.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4d3507d43908c866c805f74c9dd593c0ce7ba5c38e576e41846639cdcd4bee6" -dependencies = [ - "atk", - "bitflags 1.3.2", - "cairo-rs", - "field-offset", - "futures-channel", - "gdk", - "gdk-pixbuf", - "gio", - "glib", - "gtk-sys", - "gtk3-macros", - "libc", - "once_cell", - "pango", - "pkg-config", -] - -[[package]] -name = "gtk-sys" -version = "0.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89b5f8946685d5fe44497007786600c2f368ff6b1e61a16251c89f72a97520a3" -dependencies = [ - "atk-sys", - "cairo-sys-rs", - "gdk-pixbuf-sys", - "gdk-sys", - "gio-sys", - "glib-sys", - "gobject-sys", - "libc", - "pango-sys", - "system-deps", -] - -[[package]] -name = "gtk3-macros" -version = "0.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8cfd6557b1018b773e43c8de9d0d13581d6b36190d0501916cbec4731db5ccff" -dependencies = [ - "anyhow", - "proc-macro-crate", - "proc-macro-error", - "proc-macro2", - "quote", - "syn 1.0.109", -] - -[[package]] -name = "h2" -version = "0.3.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97ec8491ebaf99c8eaa73058b045fe58073cd6be7f596ac993ced0b0a0c01049" -dependencies = [ - "bytes", - "fnv", - "futures-core", - "futures-sink", - "futures-util", - "http", - "indexmap 1.9.2", - "slab", - "tokio", - "tokio-util", - "tracing", -] - -[[package]] -name = "handlebars" -version = "3.5.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4498fc115fa7d34de968184e473529abb40eeb6be8bc5f7faba3d08c316cb3e3" -dependencies = [ - "log", - "pest", - "pest_derive", - "quick-error 2.0.1", - "serde", - "serde_json", -] - -[[package]] -name = "hashbrown" -version = "0.11.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e" -dependencies = [ - "ahash", -] - -[[package]] -name = "hashbrown" -version = "0.12.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" -dependencies = [ - "ahash", -] - -[[package]] -name = "hashbrown" -version = "0.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c6201b9ff9fd90a5a3bac2e56a830d0caa509576f0e503818ee82c181b3437a" - -[[package]] -name = "hashlink" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7249a3129cbc1ffccd74857f81464a323a152173cdb134e0fd81bc803b29facf" -dependencies = [ - "hashbrown 0.11.2", -] - -[[package]] -name = "hashlink" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69fe1fcf8b4278d860ad0548329f892a3631fb63f82574df68275f34cdbe0ffa" -dependencies = [ - "hashbrown 0.12.3", -] - -[[package]] -name = "headers" -version = "0.3.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3e372db8e5c0d213e0cd0b9be18be2aca3d44cf2fe30a9d46a65581cd454584" -dependencies = [ - "base64 0.13.1", - "bitflags 1.3.2", - "bytes", - "headers-core", - "http", - "httpdate", - "mime", - "sha1", -] - -[[package]] -name = "headers-core" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7f66481bfee273957b1f20485a4ff3362987f85b2c236580d81b4eb7a326429" -dependencies = [ - "http", -] - -[[package]] -name = "heck" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d621efb26863f0e9924c6ac577e8275e5e6b77455db64ffa6c65c904e9e132c" -dependencies = [ - "unicode-segmentation", -] - -[[package]] -name = "heck" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" -dependencies = [ - "unicode-segmentation", -] - -[[package]] -name = "hermit-abi" -version = "0.1.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" -dependencies = [ - "libc", -] - -[[package]] -name = "hermit-abi" -version = "0.2.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee512640fe35acbfb4bb779db6f0d80704c2cacfa2e39b601ef3e3f47d1ae4c7" -dependencies = [ - "libc", -] - -[[package]] -name = "hermit-abi" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fed44880c466736ef9a5c5b5facefb5ed0785676d0c02d612db14e54f0d84286" - -[[package]] -name = "hex" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" - -[[package]] -name = "hex-literal" -version = "0.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ebdb29d2ea9ed0083cd8cece49bbd968021bd99b0849edb4a9a7ee0fdf6a4e0" - -[[package]] -name = "hkdf" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01706d578d5c281058480e673ae4086a9f4710d8df1ad80a5b03e39ece5f886b" -dependencies = [ - "digest 0.9.0", - "hmac 0.11.0", -] - -[[package]] -name = "hkdf" -version = "0.12.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "791a029f6b9fc27657f6f188ec6e5e43f6911f6f878e0dc5501396e09809d437" -dependencies = [ - "hmac 0.12.1", -] - -[[package]] -name = "hmac" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a2a2320eb7ec0ebe8da8f744d7812d9fc4cb4d09344ac01898dbcb6a20ae69b" -dependencies = [ - "crypto-mac 0.11.1", - "digest 0.9.0", -] - -[[package]] -name = "hmac" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" -dependencies = [ - "digest 0.10.7", -] - -[[package]] -name = "html5ever" -version = "0.25.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5c13fb08e5d4dfc151ee5e88bae63f7773d61852f3bdc73c9f4b9e1bde03148" -dependencies = [ - "log", - "mac", - "markup5ever", - "proc-macro2", - "quote", - "syn 1.0.109", -] - -[[package]] -name = "http" -version = "0.2.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd6effc99afb63425aff9b05836f029929e345a6148a14b7ecd5ab67af944482" -dependencies = [ - "bytes", - "fnv", - "itoa 1.0.6", -] - -[[package]] -name = "http-body" -version = "0.4.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1" -dependencies = [ - "bytes", - "http", - "pin-project-lite", -] - -[[package]] -name = "http-range" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21dec9db110f5f872ed9699c3ecf50cf16f423502706ba5c72462e28d3157573" - -[[package]] -name = "httparse" -version = "1.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" - -[[package]] -name = "httpdate" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421" - -[[package]] -name = "humantime" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df004cfca50ef23c36850aaaa59ad52cc70d0e90243c3c7737a4dd32dc7a3c4f" -dependencies = [ - "quick-error 1.2.3", -] - -[[package]] -name = "humantime" -version = "2.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" - -[[package]] -name = "humantime-serde" -version = "1.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57a3db5ea5923d99402c94e9feb261dc5ee9b4efa158b0315f788cf549cc200c" -dependencies = [ - "humantime 2.1.0", - "serde", -] - -[[package]] -name = "hyper" -version = "0.14.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffb1cfd654a8219eaef89881fdb3bb3b1cdc5fa75ded05d6933b2b382e395468" -dependencies = [ - "bytes", - "futures-channel", - "futures-core", - "futures-util", - "h2", - "http", - "http-body", - "httparse", - "httpdate", - "itoa 1.0.6", - "pin-project-lite", - "socket2", - "tokio", - "tower-service", - "tracing", - "want", -] - -[[package]] -name = "hyper-proxy" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca815a891b24fdfb243fa3239c86154392b0953ee584aa1a2a1f66d20cbe75cc" -dependencies = [ - "bytes", - "futures", - "headers", - "http", - "hyper", - "hyper-rustls", - "rustls-native-certs", - "tokio", - "tokio-rustls 0.22.0", - "tower-service", - "webpki 0.21.4", -] - -[[package]] -name = "hyper-rustls" -version = "0.22.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f9f7a97316d44c0af9b0301e65010573a853a9fc97046d7331d7f6bc0fd5a64" -dependencies = [ - "ct-logs", - "futures-util", - "hyper", - "log", - "rustls 0.19.1", - "rustls-native-certs", - "tokio", - "tokio-rustls 0.22.0", - "webpki 0.21.4", - "webpki-roots 0.21.1", -] - -[[package]] -name = "hyper-tls" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" -dependencies = [ - "bytes", - "hyper", - "native-tls", - "tokio", - "tokio-native-tls", -] - -[[package]] -name = "ico" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "031530fe562d8c8d71c0635013d6d155bbfe8ba0aa4b4d2d24ce8af6b71047bd" -dependencies = [ - "byteorder", - "png", -] - -[[package]] -name = "ident_case" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" - -[[package]] -name = "idna" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e14ddfc70884202db2244c223200c204c2bda1bc6e0998d11b5e024d657209e6" -dependencies = [ - "unicode-bidi", - "unicode-normalization", -] - -[[package]] -name = "ignore" -version = "0.4.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "713f1b139373f96a2e0ce3ac931cd01ee973c3c5dd7c40c0c2efe96ad2b6751d" -dependencies = [ - "crossbeam-utils", - "globset", - "lazy_static", - "log", - "memchr", - "regex", - "same-file", - "thread_local", - "walkdir", - "winapi-util", -] - -[[package]] -name = "image" -version = "0.24.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69b7ea949b537b0fd0af141fff8c77690f2ce96f4f41f042ccb6c69c6c965945" -dependencies = [ - "bytemuck", - "byteorder", - "color_quant", - "num-rational", - "num-traits", -] - -[[package]] -name = "indenter" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683" - -[[package]] -name = "indexmap" -version = "1.9.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1885e79c1fc4b10f0e172c475f458b7f7b93061064d98c3293e98c5ba0c8b399" -dependencies = [ - "autocfg", - "hashbrown 0.12.3", - "serde", -] - -[[package]] -name = "indexmap" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5477fe2230a79769d8dc68e0eabf5437907c0457a5614a9e8dddb67f65eb65d" -dependencies = [ - "equivalent", - "hashbrown 0.14.0", -] - -[[package]] -name = "infer" -version = "0.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a898e4b7951673fce96614ce5751d13c40fc5674bc2d759288e46c3ab62598b3" -dependencies = [ - "cfb", -] - -[[package]] -name = "inout" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5" -dependencies = [ - "generic-array 0.14.7", -] - -[[package]] -name = "input_buffer" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f97967975f448f1a7ddb12b0bc41069d09ed6a1c161a92687e057325db35d413" -dependencies = [ - "bytes", -] - -[[package]] -name = "instant" -version = "0.1.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" -dependencies = [ - "cfg-if", -] - -[[package]] -name = "io-lifetimes" -version = "1.0.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09270fd4fa1111bc614ed2246c7ef56239a3063d5be0d1ec3b589c505d400aeb" -dependencies = [ - "hermit-abi 0.3.1", - "libc", - "windows-sys 0.45.0", -] - -[[package]] -name = "ipnet" -version = "2.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30e22bd8629359895450b59ea7a776c850561b96a3b1d31321c1949d9e6c9146" - -[[package]] -name = "is-terminal" -version = "0.4.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8687c819457e979cc940d09cb16e42a1bf70aa6b60a549de6d3a62a0ee90c69e" -dependencies = [ - "hermit-abi 0.3.1", - "io-lifetimes", - "rustix", - "windows-sys 0.45.0", -] - -[[package]] -name = "itertools" -version = "0.10.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" -dependencies = [ - "either", -] - -[[package]] -name = "itoa" -version = "0.4.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4" - -[[package]] -name = "itoa" -version = "1.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "453ad9f582a441959e5f0d088b02ce04cfe8d51a8eaf077f12ac6d3e94164ca6" - -[[package]] -name = "javascriptcore-rs" -version = "0.17.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "110b9902c80c12bf113c432d0b71c7a94490b294a8234f326fd0abca2fac0b00" -dependencies = [ - "bitflags 1.3.2", - "glib", - "javascriptcore-rs-sys", -] - -[[package]] -name = "javascriptcore-rs-sys" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "98a216519a52cd941a733a0ad3f1023cfdb1cd47f3955e8e863ed56f558f916c" -dependencies = [ - "glib-sys", - "gobject-sys", - "libc", - "system-deps", -] - -[[package]] -name = "jni" -version = "0.20.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "039022cdf4d7b1cf548d31f60ae783138e5fd42013f6271049d7df7afadef96c" -dependencies = [ - "cesu8", - "combine", - "jni-sys", - "log", - "thiserror", - "walkdir", -] - -[[package]] -name = "jni-sys" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" - -[[package]] -name = "jobserver" -version = "0.1.26" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "936cfd212a0155903bcbc060e316fb6cc7cbf2e1907329391ebadc1fe0ce77c2" -dependencies = [ - "libc", -] - -[[package]] -name = "js-sys" -version = "0.3.60" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49409df3e3bf0856b916e2ceaca09ee28e6871cf7d9ce97a692cacfdb2a25a47" -dependencies = [ - "wasm-bindgen", -] - -[[package]] -name = "json-patch" -version = "0.2.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb3fa5a61630976fc4c353c70297f2e93f1930e3ccee574d59d618ccbd5154ce" -dependencies = [ - "serde", - "serde_json", - "treediff 3.0.2", -] - -[[package]] -name = "json-patch" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e712e62827c382a77b87f590532febb1f8b2fdbc3eefa1ee37fe7281687075ef" -dependencies = [ - "serde", - "serde_json", - "thiserror", - "treediff 4.0.2", -] - -[[package]] -name = "k256" -version = "0.11.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72c1e0b51e7ec0a97369623508396067a486bd0cbed95a2659a4b863d28cfc8b" -dependencies = [ - "cfg-if", - "ecdsa 0.14.8", - "elliptic-curve 0.12.3", - "sha2 0.10.6", -] - -[[package]] -name = "k256" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cadb76004ed8e97623117f3df85b17aaa6626ab0b0831e6573f104df16cd1bcc" -dependencies = [ - "cfg-if", - "ecdsa 0.16.8", - "elliptic-curve 0.13.5", - "once_cell", - "sha2 0.10.6", - "signature 2.1.0", -] - -[[package]] -name = "keystream" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c33070833c9ee02266356de0c43f723152bd38bd96ddf52c82b3af10c9138b28" - -[[package]] -name = "kuchiki" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ea8e9c6e031377cff82ee3001dc8026cdf431ed4e2e6b51f98ab8c73484a358" -dependencies = [ - "cssparser", - "html5ever", - "matches", - "selectors", -] - -[[package]] -name = "lazy_static" -version = "1.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" - -[[package]] -name = "libc" -version = "0.2.147" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3" - -[[package]] -name = "libdbus-sys" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f8d7ae751e1cb825c840ae5e682f59b098cdfd213c350ac268b61449a5f58a0" -dependencies = [ - "pkg-config", -] - -[[package]] -name = "libgit2-sys" -version = "0.13.5+1.4.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51e5ea06c26926f1002dd553fded6cfcdc9784c1f60feeb58368b4d9b07b6dba" -dependencies = [ - "cc", - "libc", - "libz-sys", - "pkg-config", -] - -[[package]] -name = "libm" -version = "0.2.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "348108ab3fba42ec82ff6e9564fc4ca0247bdccdc68dd8af9764bbc79c3c8ffb" - -[[package]] -name = "libsqlite3-sys" -version = "0.24.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "898745e570c7d0453cc1fbc4a701eb6c662ed54e8fec8b7d14be137ebeeb9d14" -dependencies = [ - "cc", - "pkg-config", - "vcpkg", -] - -[[package]] -name = "libz-sys" -version = "1.1.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9702761c3935f8cc2f101793272e202c72b99da8f4224a19ddcf1279a6450bbf" -dependencies = [ - "cc", - "libc", - "pkg-config", - "vcpkg", -] - -[[package]] -name = "line-wrap" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f30344350a2a51da54c1d53be93fade8a237e545dbcc4bdbe635413f2117cab9" -dependencies = [ - "safemem", -] - -[[package]] -name = "linked-hash-map" -version = "0.5.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" - -[[package]] -name = "linux-raw-sys" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f051f77a7c8e6957c0696eac88f26b0117e54f52d3fc682ab19397a8812846a4" - -[[package]] -name = "lioness" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ae926706ba42c425c9457121178330d75e273df2e82e28b758faf3de3a9acb9" -dependencies = [ - "arrayref", - "blake2", - "chacha", - "keystream", -] - -[[package]] -name = "lock_api" -version = "0.4.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "435011366fe56583b16cf956f9df0095b405b82d76425bc8981c0e22e60ec4df" -dependencies = [ - "autocfg", - "scopeguard", -] - -[[package]] -name = "log" -version = "0.4.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" -dependencies = [ - "cfg-if", - "serde", -] - -[[package]] -name = "loom" -version = "0.5.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff50ecb28bb86013e935fb6683ab1f6d3a20016f123c76fd4c27470076ac30f5" -dependencies = [ - "cfg-if", - "generator", - "scoped-tls", - "serde", - "serde_json", - "tracing", - "tracing-subscriber", -] - -[[package]] -name = "mac" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c41e0c4fef86961ac6d6f8a82609f55f31b05e4fce149ac5710e439df7619ba4" - -[[package]] -name = "mac-notification-sys" -version = "0.5.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e72d50edb17756489e79d52eb146927bec8eba9dd48faadf9ef08bca3791ad5" -dependencies = [ - "cc", - "dirs-next", - "objc-foundation", - "objc_id", - "time", -] - -[[package]] -name = "malloc_buf" -version = "0.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62bb907fe88d54d8d9ce32a3cceab4218ed2f6b7d35617cafe9adf84e43919cb" -dependencies = [ - "libc", -] - -[[package]] -name = "markup5ever" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a24f40fb03852d1cdd84330cddcaf98e9ec08a7b7768e952fad3b4cf048ec8fd" -dependencies = [ - "log", - "phf 0.8.0", - "phf_codegen", - "string_cache", - "string_cache_codegen", - "tendril", -] - -[[package]] -name = "matchers" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558" -dependencies = [ - "regex-automata", -] - -[[package]] -name = "matches" -version = "0.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2532096657941c2fea9c289d370a250971c689d4f143798ff67113ec042024a5" - -[[package]] -name = "memchr" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" - -[[package]] -name = "memoffset" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d61c719bcfbcf5d62b3a09efa6088de8c54bc0bfcd3ea7ae39fcc186108b8de1" -dependencies = [ - "autocfg", -] - -[[package]] -name = "mime" -version = "0.3.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" - -[[package]] -name = "minimal-lexical" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" - -[[package]] -name = "miniz_oxide" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b275950c28b37e794e8c55d88aeb5e139d0ce23fdbbeda68f8d7174abdf9e8fa" -dependencies = [ - "adler", -] - -[[package]] -name = "mio" -version = "0.8.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b9d9a46eff5b4ff64b45a9e316a6d1e0bc719ef429cbec4dc630684212bfdf9" -dependencies = [ - "libc", - "log", - "wasi 0.11.0+wasi-snapshot-preview1", - "windows-sys 0.45.0", -] - -[[package]] -name = "native-tls" -version = "0.2.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07226173c32f2926027b63cce4bcd8076c3552846cbe7925f3aaffeac0a3b92e" -dependencies = [ - "lazy_static", - "libc", - "log", - "openssl", - "openssl-probe", - "openssl-sys", - "schannel", - "security-framework", - "security-framework-sys", - "tempfile", -] - -[[package]] -name = "ndk" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2032c77e030ddee34a6787a64166008da93f6a352b629261d0fee232b8742dd4" -dependencies = [ - "bitflags 1.3.2", - "jni-sys", - "ndk-sys", - "num_enum", - "thiserror", -] - -[[package]] -name = "ndk-context" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "27b02d87554356db9e9a873add8782d4ea6e3e58ea071a9adb9a2e8ddb884a8b" - -[[package]] -name = "ndk-sys" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e5a6ae77c8ee183dcbbba6150e2e6b9f3f4196a7666c02a715a95692ec1fa97" -dependencies = [ - "jni-sys", -] - -[[package]] -name = "new_debug_unreachable" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4a24736216ec316047a1fc4252e27dabb04218aa4a3f37c6e7ddbf1f9782b54" - -[[package]] -name = "nodrop" -version = "0.1.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72ef4a56884ca558e5ddb05a1d1e7e1bfd9a68d9ed024c21704cc98872dae1bb" - -[[package]] -name = "nom" -version = "7.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" -dependencies = [ - "memchr", - "minimal-lexical", -] - -[[package]] -name = "notify-rust" -version = "4.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2bfa211d18e360f08e36c364308f394b5eb23a6629150690e109a916dc6f610e" -dependencies = [ - "dbus", - "log", - "mac-notification-sys", - "tauri-winrt-notification", -] - -[[package]] -name = "nu-ansi-term" -version = "0.46.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" -dependencies = [ - "overload", - "winapi", -] - -[[package]] -name = "num-derive" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "876a53fff98e03a936a674b29568b0e605f06b29372c2489ff4de23f1949743d" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.109", -] - -[[package]] -name = "num-integer" -version = "0.1.45" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" -dependencies = [ - "autocfg", - "num-traits", -] - -[[package]] -name = "num-rational" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0638a1c9d0a3c0914158145bc76cff373a75a627e6ecbfb71cbe6f453a5a19b0" -dependencies = [ - "autocfg", - "num-integer", - "num-traits", -] - -[[package]] -name = "num-traits" -version = "0.2.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" -dependencies = [ - "autocfg", - "libm", -] - -[[package]] -name = "num_cpus" -version = "1.15.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fac9e2da13b5eb447a6ce3d392f23a29d8694bff781bf03a16cd9ac8697593b" -dependencies = [ - "hermit-abi 0.2.6", - "libc", -] - -[[package]] -name = "num_enum" -version = "0.5.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f646caf906c20226733ed5b1374287eb97e3c2a5c227ce668c1f2ce20ae57c9" -dependencies = [ - "num_enum_derive", -] - -[[package]] -name = "num_enum_derive" -version = "0.5.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcbff9bc912032c62bf65ef1d5aea88983b420f4f839db1e9b0c281a25c9c799" -dependencies = [ - "proc-macro-crate", - "proc-macro2", - "quote", - "syn 1.0.109", -] - -[[package]] -name = "num_threads" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2819ce041d2ee131036f4fc9d6ae7ae125a3a40e97ba64d04fe799ad9dabbb44" -dependencies = [ - "libc", -] - -[[package]] -name = "nym-api-requests" -version = "0.1.0" -dependencies = [ - "bs58 0.4.0", - "cosmrs", - "cosmwasm-std", - "getset", - "nym-coconut-interface", - "nym-mixnet-contract-common", - "schemars", - "serde", -] - -[[package]] -name = "nym-bandwidth-controller" -version = "0.1.0" -dependencies = [ - "bip39", - "nym-coconut-interface", - "nym-credential-storage", - "nym-credentials", - "nym-crypto", - "nym-network-defaults", - "nym-validator-client", - "rand 0.7.3", - "thiserror", - "url", -] - -[[package]] -name = "nym-bin-common" -version = "0.6.0" -dependencies = [ - "atty", - "clap", - "clap_complete", - "clap_complete_fig", - "log", - "pretty_env_logger", - "semver 0.11.0", - "serde", - "vergen", -] - -[[package]] -name = "nym-client-core" -version = "1.1.15" -dependencies = [ - "async-trait", - "base64 0.21.2", - "dashmap", - "dirs 4.0.0", - "futures", - "gloo-timers", - "humantime-serde", - "log", - "nym-bandwidth-controller", - "nym-config", - "nym-credential-storage", - "nym-crypto", - "nym-explorer-api-requests", - "nym-gateway-client", - "nym-gateway-requests", - "nym-network-defaults", - "nym-nonexhaustive-delayqueue", - "nym-pemstore", - "nym-sphinx", - "nym-task", - "nym-topology", - "nym-validator-client", - "rand 0.7.3", - "reqwest", - "serde", - "serde_json", - "sha2 0.10.6", - "sqlx 0.6.3", - "tap", - "thiserror", - "time", - "tokio", - "tokio-stream", - "tokio-tungstenite", - "tungstenite", - "url", - "wasm-bindgen", - "wasm-bindgen-futures", - "wasm-utils", - "wasmtimer", - "zeroize", -] - -[[package]] -name = "nym-coconut" -version = "0.5.0" -dependencies = [ - "bls12_381 0.6.0", - "bs58 0.4.0", - "digest 0.9.0", - "ff 0.11.1", - "getrandom 0.2.10", - "group 0.11.0", - "itertools", - "nym-dkg", - "nym-pemstore", - "rand 0.8.5", - "serde", - "serde_derive", - "sha2 0.9.9", - "thiserror", -] - -[[package]] -name = "nym-coconut-bandwidth-contract-common" -version = "0.1.0" -dependencies = [ - "cosmwasm-schema", - "cosmwasm-std", - "nym-multisig-contract-common", -] - -[[package]] -name = "nym-coconut-dkg-common" -version = "0.1.0" -dependencies = [ - "cosmwasm-schema", - "cosmwasm-std", - "cw-utils", - "nym-contracts-common", - "nym-multisig-contract-common", -] - -[[package]] -name = "nym-coconut-interface" -version = "0.1.0" -dependencies = [ - "bs58 0.4.0", - "getset", - "nym-coconut", - "serde", - "thiserror", -] - -[[package]] -name = "nym-config" -version = "0.1.0" -dependencies = [ - "dirs 5.0.1", - "handlebars", - "log", - "nym-network-defaults", - "serde", - "toml 0.7.6", - "url", -] - -[[package]] -name = "nym-connect" -version = "1.1.12" -dependencies = [ - "anyhow", - "bip39", - "dirs 4.0.0", - "eyre", - "fern", - "fix-path-env", - "futures", - "itertools", - "log", - "nym-api-requests", - "nym-bin-common", - "nym-client-core", - "nym-config", - "nym-contracts-common", - "nym-credential-storage", - "nym-crypto", - "nym-socks5-client-core", - "nym-task", - "pretty_env_logger", - "rand 0.8.5", - "reqwest", - "rust-embed", - "serde", - "serde_json", - "serde_repr", - "tap", - "tauri", - "tauri-build", - "tempfile", - "thiserror", - "time", - "tokio", - "ts-rs", - "url", - "yaml-rust", -] - -[[package]] -name = "nym-contracts-common" -version = "0.5.0" -dependencies = [ - "bs58 0.4.0", - "cosmwasm-schema", - "cosmwasm-std", - "schemars", - "serde", - "thiserror", -] - -[[package]] -name = "nym-credential-storage" -version = "0.1.0" -dependencies = [ - "async-trait", - "log", - "sqlx 0.5.13", - "thiserror", - "tokio", -] - -[[package]] -name = "nym-credentials" -version = "0.1.0" -dependencies = [ - "bls12_381 0.5.0", - "cosmrs", - "nym-api-requests", - "nym-coconut-interface", - "nym-crypto", - "nym-validator-client", - "thiserror", -] - -[[package]] -name = "nym-crypto" -version = "0.4.0" -dependencies = [ - "aes 0.8.2", - "blake3", - "bs58 0.4.0", - "cipher 0.4.4", - "ctr 0.9.2", - "digest 0.10.7", - "ed25519-dalek", - "generic-array 0.14.7", - "hkdf 0.12.3", - "hmac 0.12.1", - "nym-pemstore", - "nym-sphinx-types", - "rand 0.7.3", - "subtle-encoding", - "thiserror", - "x25519-dalek", - "zeroize", -] - -[[package]] -name = "nym-dkg" -version = "0.1.0" -dependencies = [ - "bitvec", - "bls12_381 0.6.0", - "bs58 0.4.0", - "ff 0.11.1", - "group 0.11.0", - "lazy_static", - "nym-pemstore", - "rand 0.8.5", - "rand_chacha 0.3.1", - "rand_core 0.6.4", - "serde", - "serde_derive", - "sha2 0.9.9", - "thiserror", - "zeroize", -] - -[[package]] -name = "nym-explorer-api-requests" -version = "0.1.0" -dependencies = [ - "nym-contracts-common", - "nym-mixnet-contract-common", - "nym-validator-client", - "schemars", - "serde", -] - -[[package]] -name = "nym-gateway-client" -version = "0.1.0" -dependencies = [ - "futures", - "getrandom 0.2.10", - "log", - "nym-bandwidth-controller", - "nym-coconut-interface", - "nym-credential-storage", - "nym-crypto", - "nym-gateway-requests", - "nym-network-defaults", - "nym-pemstore", - "nym-sphinx", - "nym-task", - "nym-validator-client", - "rand 0.7.3", - "serde", - "thiserror", - "tokio", - "tokio-stream", - "tokio-tungstenite", - "tungstenite", - "url", - "wasm-bindgen", - "wasm-bindgen-futures", - "wasm-utils", - "wasmtimer", -] - -[[package]] -name = "nym-gateway-requests" -version = "0.1.0" -dependencies = [ - "bs58 0.4.0", - "futures", - "generic-array 0.14.7", - "log", - "nym-coconut-interface", - "nym-credentials", - "nym-crypto", - "nym-pemstore", - "nym-sphinx", - "rand 0.7.3", - "serde", - "serde_json", - "thiserror", - "tungstenite", - "zeroize", -] - -[[package]] -name = "nym-group-contract-common" -version = "0.1.0" -dependencies = [ - "cosmwasm-schema", - "cw-controllers", - "cw4", - "schemars", - "serde", -] - -[[package]] -name = "nym-mixnet-contract-common" -version = "0.6.0" -dependencies = [ - "bs58 0.4.0", - "cosmwasm-schema", - "cosmwasm-std", - "humantime-serde", - "log", - "nym-contracts-common", - "schemars", - "serde", - "serde-json-wasm", - "serde_repr", - "thiserror", - "time", -] - -[[package]] -name = "nym-multisig-contract-common" -version = "0.1.0" -dependencies = [ - "cosmwasm-schema", - "cosmwasm-std", - "cw-storage-plus", - "cw-utils", - "cw3", - "cw4", - "schemars", - "serde", - "thiserror", -] - -[[package]] -name = "nym-name-service-common" -version = "0.1.0" -dependencies = [ - "cosmwasm-schema", - "cosmwasm-std", - "cw-controllers", - "cw-utils", - "nym-contracts-common", - "serde", - "thiserror", -] - -[[package]] -name = "nym-network-defaults" -version = "0.1.0" -dependencies = [ - "cfg-if", - "dotenvy", - "hex-literal", - "once_cell", - "serde", - "thiserror", - "url", -] - -[[package]] -name = "nym-nonexhaustive-delayqueue" -version = "0.1.0" -dependencies = [ - "tokio", - "tokio-stream", - "tokio-util", - "wasmtimer", -] - -[[package]] -name = "nym-ordered-buffer" -version = "0.1.0" -dependencies = [ - "log", - "thiserror", -] - -[[package]] -name = "nym-outfox" -version = "0.1.0" -dependencies = [ - "blake3", - "chacha20", - "chacha20poly1305", - "curve25519-dalek", - "getrandom 0.2.10", - "log", - "rand 0.7.3", - "rayon", - "sphinx-packet", - "thiserror", - "zeroize", -] - -[[package]] -name = "nym-pemstore" -version = "0.3.0" -dependencies = [ - "pem", -] - -[[package]] -name = "nym-service-provider-directory-common" -version = "0.1.0" -dependencies = [ - "cosmwasm-schema", - "cosmwasm-std", - "cw-controllers", - "cw-utils", - "nym-contracts-common", - "thiserror", -] - -[[package]] -name = "nym-service-providers-common" -version = "0.1.0" -dependencies = [ - "async-trait", - "log", - "nym-bin-common", - "nym-sphinx-anonymous-replies", - "serde", - "serde_json", - "thiserror", -] - -[[package]] -name = "nym-socks5-client-core" -version = "0.1.0" -dependencies = [ - "dirs 4.0.0", - "futures", - "log", - "nym-bandwidth-controller", - "nym-client-core", - "nym-config", - "nym-contracts-common", - "nym-credential-storage", - "nym-mixnet-contract-common", - "nym-network-defaults", - "nym-service-providers-common", - "nym-socks5-proxy-helpers", - "nym-socks5-requests", - "nym-sphinx", - "nym-task", - "nym-validator-client", - "pin-project", - "rand 0.7.3", - "reqwest", - "schemars", - "serde", - "tap", - "thiserror", - "tokio", - "url", -] - -[[package]] -name = "nym-socks5-proxy-helpers" -version = "0.1.0" -dependencies = [ - "bytes", - "futures", - "log", - "nym-ordered-buffer", - "nym-socks5-requests", - "nym-task", - "tokio", - "tokio-util", -] - -[[package]] -name = "nym-socks5-requests" -version = "0.1.0" -dependencies = [ - "bincode", - "log", - "nym-service-providers-common", - "nym-sphinx-addressing", - "serde", - "serde_json", - "tap", - "thiserror", -] - -[[package]] -name = "nym-sphinx" -version = "0.1.0" -dependencies = [ - "log", - "nym-crypto", - "nym-sphinx-acknowledgements", - "nym-sphinx-addressing", - "nym-sphinx-anonymous-replies", - "nym-sphinx-chunking", - "nym-sphinx-cover", - "nym-sphinx-forwarding", - "nym-sphinx-framing", - "nym-sphinx-params", - "nym-sphinx-routing", - "nym-sphinx-types", - "nym-topology", - "rand 0.7.3", - "rand_distr", - "thiserror", - "tokio", -] - -[[package]] -name = "nym-sphinx-acknowledgements" -version = "0.1.0" -dependencies = [ - "nym-crypto", - "nym-pemstore", - "nym-sphinx-addressing", - "nym-sphinx-params", - "nym-sphinx-routing", - "nym-sphinx-types", - "nym-topology", - "rand 0.7.3", - "thiserror", - "zeroize", -] - -[[package]] -name = "nym-sphinx-addressing" -version = "0.1.0" -dependencies = [ - "nym-crypto", - "nym-sphinx-types", - "serde", - "thiserror", -] - -[[package]] -name = "nym-sphinx-anonymous-replies" -version = "0.1.0" -dependencies = [ - "bs58 0.4.0", - "nym-crypto", - "nym-sphinx-addressing", - "nym-sphinx-params", - "nym-sphinx-routing", - "nym-sphinx-types", - "nym-topology", - "rand 0.7.3", - "serde", - "thiserror", - "wasm-bindgen", -] - -[[package]] -name = "nym-sphinx-chunking" -version = "0.1.0" -dependencies = [ - "log", - "nym-sphinx-addressing", - "nym-sphinx-params", - "nym-sphinx-types", - "rand 0.7.3", - "thiserror", -] - -[[package]] -name = "nym-sphinx-cover" -version = "0.1.0" -dependencies = [ - "nym-crypto", - "nym-sphinx-acknowledgements", - "nym-sphinx-addressing", - "nym-sphinx-chunking", - "nym-sphinx-forwarding", - "nym-sphinx-params", - "nym-sphinx-routing", - "nym-sphinx-types", - "nym-topology", - "rand 0.7.3", - "thiserror", -] - -[[package]] -name = "nym-sphinx-forwarding" -version = "0.1.0" -dependencies = [ - "nym-outfox", - "nym-sphinx-addressing", - "nym-sphinx-params", - "nym-sphinx-types", - "thiserror", -] - -[[package]] -name = "nym-sphinx-framing" -version = "0.1.0" -dependencies = [ - "bytes", - "nym-sphinx-params", - "nym-sphinx-types", - "thiserror", - "tokio-util", -] - -[[package]] -name = "nym-sphinx-params" -version = "0.1.0" -dependencies = [ - "nym-crypto", - "nym-sphinx-types", - "serde", - "thiserror", -] - -[[package]] -name = "nym-sphinx-routing" -version = "0.1.0" -dependencies = [ - "nym-sphinx-addressing", - "nym-sphinx-types", - "thiserror", -] - -[[package]] -name = "nym-sphinx-types" -version = "0.2.0" -dependencies = [ - "nym-outfox", - "sphinx-packet", - "thiserror", -] - -[[package]] -name = "nym-task" -version = "0.1.0" -dependencies = [ - "futures", - "log", - "thiserror", - "tokio", - "wasm-bindgen", - "wasm-bindgen-futures", -] - -[[package]] -name = "nym-topology" -version = "0.1.0" -dependencies = [ - "async-trait", - "bs58 0.4.0", - "log", - "nym-bin-common", - "nym-crypto", - "nym-mixnet-contract-common", - "nym-sphinx-addressing", - "nym-sphinx-routing", - "nym-sphinx-types", - "rand 0.7.3", - "thiserror", -] - -[[package]] -name = "nym-validator-client" -version = "0.1.0" -dependencies = [ - "async-trait", - "base64 0.13.1", - "bip39", - "colored 2.0.0", - "cosmrs", - "cosmwasm-std", - "cw3", - "cw4", - "eyre", - "flate2", - "futures", - "itertools", - "log", - "nym-api-requests", - "nym-coconut-bandwidth-contract-common", - "nym-coconut-dkg-common", - "nym-coconut-interface", - "nym-config", - "nym-contracts-common", - "nym-group-contract-common", - "nym-mixnet-contract-common", - "nym-multisig-contract-common", - "nym-name-service-common", - "nym-network-defaults", - "nym-service-provider-directory-common", - "nym-vesting-contract-common", - "openssl", - "prost", - "reqwest", - "serde", - "serde_json", - "sha2 0.9.9", - "tendermint-rpc", - "thiserror", - "tokio", - "url", - "zeroize", -] - -[[package]] -name = "nym-vesting-contract-common" -version = "0.7.0" -dependencies = [ - "cosmwasm-schema", - "cosmwasm-std", - "nym-contracts-common", - "nym-mixnet-contract-common", - "serde", - "thiserror", -] - -[[package]] -name = "objc" -version = "0.2.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "915b1b472bc21c53464d6c8461c9d3af805ba1ef837e1cac254428f4a77177b1" -dependencies = [ - "malloc_buf", - "objc_exception", -] - -[[package]] -name = "objc-foundation" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1add1b659e36c9607c7aab864a76c7a4c2760cd0cd2e120f3fb8b952c7e22bf9" -dependencies = [ - "block", - "objc", - "objc_id", -] - -[[package]] -name = "objc_exception" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad970fb455818ad6cba4c122ad012fae53ae8b4795f86378bce65e4f6bab2ca4" -dependencies = [ - "cc", -] - -[[package]] -name = "objc_id" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c92d4ddb4bd7b50d730c215ff871754d0da6b2178849f8a2a2ab69712d0c073b" -dependencies = [ - "objc", -] - -[[package]] -name = "once_cell" -version = "1.17.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3" - -[[package]] -name = "opaque-debug" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2839e79665f131bdb5782e51f2c6c9599c133c6098982a54c794358bf432529c" - -[[package]] -name = "opaque-debug" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" - -[[package]] -name = "open" -version = "3.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2078c0039e6a54a0c42c28faa984e115fb4c2d5bf2208f77d1961002df8576f8" -dependencies = [ - "pathdiff", - "windows-sys 0.42.0", -] - -[[package]] -name = "openssl" -version = "0.10.56" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "729b745ad4a5575dd06a3e1af1414bd330ee561c01b3899eb584baeaa8def17e" -dependencies = [ - "bitflags 1.3.2", - "cfg-if", - "foreign-types", - "libc", - "once_cell", - "openssl-macros", - "openssl-sys", -] - -[[package]] -name = "openssl-macros" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b501e44f11665960c7e7fcf062c7d96a14ade4aa98116c004b2e37b5be7d736c" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.109", -] - -[[package]] -name = "openssl-probe" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" - -[[package]] -name = "openssl-src" -version = "111.25.2+1.1.1t" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "320708a054ad9b3bf314688b5db87cf4d6683d64cfc835e2337924ae62bf4431" -dependencies = [ - "cc", -] - -[[package]] -name = "openssl-sys" -version = "0.9.91" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "866b5f16f90776b9bb8dc1e1802ac6f0513de3a7a7465867bfbc563dc737faac" -dependencies = [ - "cc", - "libc", - "openssl-src", - "pkg-config", - "vcpkg", -] - -[[package]] -name = "option-ext" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" - -[[package]] -name = "os_str_bytes" -version = "6.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ceedf44fb00f2d1984b0bc98102627ce622e083e49a5bacdb3e514fa4238e267" - -[[package]] -name = "overload" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" - -[[package]] -name = "pairing" -version = "0.20.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7de9d09263c9966e8196fe0380c9dbbc7ea114b5cf371ba29004bc1f9c6db7f3" -dependencies = [ - "group 0.10.0", -] - -[[package]] -name = "pairing" -version = "0.21.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2e415e349a3006dd7d9482cdab1c980a845bed1377777d768cb693a44540b42" -dependencies = [ - "group 0.11.0", -] - -[[package]] -name = "pango" -version = "0.16.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cdff66b271861037b89d028656184059e03b0b6ccb36003820be19f7200b1e94" -dependencies = [ - "bitflags 1.3.2", - "gio", - "glib", - "libc", - "once_cell", - "pango-sys", -] - -[[package]] -name = "pango-sys" -version = "0.16.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e134909a9a293e04d2cc31928aa95679c5e4df954d0b85483159bd20d8f047f" -dependencies = [ - "glib-sys", - "gobject-sys", - "libc", - "system-deps", -] - -[[package]] -name = "parking_lot" -version = "0.11.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99" -dependencies = [ - "instant", - "lock_api", - "parking_lot_core 0.8.6", -] - -[[package]] -name = "parking_lot" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" -dependencies = [ - "lock_api", - "parking_lot_core 0.9.7", -] - -[[package]] -name = "parking_lot_core" -version = "0.8.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60a2cfe6f0ad2bfc16aefa463b497d5c7a5ecd44a23efa72aa342d90177356dc" -dependencies = [ - "cfg-if", - "instant", - "libc", - "redox_syscall", - "smallvec", - "winapi", -] - -[[package]] -name = "parking_lot_core" -version = "0.9.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9069cbb9f99e3a5083476ccb29ceb1de18b9118cafa53e90c9551235de2b9521" -dependencies = [ - "cfg-if", - "libc", - "redox_syscall", - "smallvec", - "windows-sys 0.45.0", -] - -[[package]] -name = "paste" -version = "1.0.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f746c4065a8fa3fe23974dd82f15431cc8d40779821001404d10d2e79ca7d79" - -[[package]] -name = "pathdiff" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8835116a5c179084a830efb3adc117ab007512b535bc1a21c991d3b32a6b44dd" - -[[package]] -name = "pbkdf2" -version = "0.12.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8ed6a7761f76e3b9f92dfb0a60a6a6477c61024b775147ff0973a02653abaf2" -dependencies = [ - "digest 0.10.7", - "hmac 0.12.1", -] - -[[package]] -name = "peg" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07c0b841ea54f523f7aa556956fbd293bcbe06f2e67d2eb732b7278aaf1d166a" -dependencies = [ - "peg-macros", - "peg-runtime", -] - -[[package]] -name = "peg-macros" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5aa52829b8decbef693af90202711348ab001456803ba2a98eb4ec8fb70844c" -dependencies = [ - "peg-runtime", - "proc-macro2", - "quote", -] - -[[package]] -name = "peg-runtime" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c719dcf55f09a3a7e764c6649ab594c18a177e3599c467983cdf644bfc0a4088" - -[[package]] -name = "pem" -version = "0.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd56cbd21fea48d0c440b41cd69c589faacade08c992d9a54e471b79d0fd13eb" -dependencies = [ - "base64 0.13.1", - "once_cell", - "regex", -] - -[[package]] -name = "percent-encoding" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e" - -[[package]] -name = "pest" -version = "2.5.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8cbd939b234e95d72bc393d51788aec68aeeb5d51e748ca08ff3aad58cb722f7" -dependencies = [ - "thiserror", - "ucd-trie", -] - -[[package]] -name = "pest_derive" -version = "2.5.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a81186863f3d0a27340815be8f2078dd8050b14cd71913db9fbda795e5f707d7" -dependencies = [ - "pest", - "pest_generator", -] - -[[package]] -name = "pest_generator" -version = "2.5.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75a1ef20bf3193c15ac345acb32e26b3dc3223aff4d77ae4fc5359567683796b" -dependencies = [ - "pest", - "pest_meta", - "proc-macro2", - "quote", - "syn 1.0.109", -] - -[[package]] -name = "pest_meta" -version = "2.5.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e3b284b1f13a20dc5ebc90aff59a51b8d7137c221131b52a7260c08cbc1cc80" -dependencies = [ - "once_cell", - "pest", - "sha2 0.10.6", -] - -[[package]] -name = "phf" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3dfb61232e34fcb633f43d12c58f83c1df82962dcdfa565a4e866ffc17dafe12" -dependencies = [ - "phf_macros 0.8.0", - "phf_shared 0.8.0", - "proc-macro-hack", -] - -[[package]] -name = "phf" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fabbf1ead8a5bcbc20f5f8b939ee3f5b0f6f281b6ad3468b84656b658b455259" -dependencies = [ - "phf_macros 0.10.0", - "phf_shared 0.10.0", - "proc-macro-hack", -] - -[[package]] -name = "phf_codegen" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cbffee61585b0411840d3ece935cce9cb6321f01c45477d30066498cd5e1a815" -dependencies = [ - "phf_generator 0.8.0", - "phf_shared 0.8.0", -] - -[[package]] -name = "phf_generator" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17367f0cc86f2d25802b2c26ee58a7b23faeccf78a396094c13dced0d0182526" -dependencies = [ - "phf_shared 0.8.0", - "rand 0.7.3", -] - -[[package]] -name = "phf_generator" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d5285893bb5eb82e6aaf5d59ee909a06a16737a8970984dd7746ba9283498d6" -dependencies = [ - "phf_shared 0.10.0", - "rand 0.8.5", -] - -[[package]] -name = "phf_macros" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f6fde18ff429ffc8fe78e2bf7f8b7a5a5a6e2a8b58bc5a9ac69198bbda9189c" -dependencies = [ - "phf_generator 0.8.0", - "phf_shared 0.8.0", - "proc-macro-hack", - "proc-macro2", - "quote", - "syn 1.0.109", -] - -[[package]] -name = "phf_macros" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58fdf3184dd560f160dd73922bea2d5cd6e8f064bf4b13110abd81b03697b4e0" -dependencies = [ - "phf_generator 0.10.0", - "phf_shared 0.10.0", - "proc-macro-hack", - "proc-macro2", - "quote", - "syn 1.0.109", -] - -[[package]] -name = "phf_shared" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c00cf8b9eafe68dde5e9eaa2cef8ee84a9336a47d566ec55ca16589633b65af7" -dependencies = [ - "siphasher", -] - -[[package]] -name = "phf_shared" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6796ad771acdc0123d2a88dc428b5e38ef24456743ddb1744ed628f9815c096" -dependencies = [ - "siphasher", -] - -[[package]] -name = "pin-project" -version = "1.0.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad29a609b6bcd67fee905812e544992d216af9d755757c05ed2d0e15a74c6ecc" -dependencies = [ - "pin-project-internal", -] - -[[package]] -name = "pin-project-internal" -version = "1.0.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "069bdb1e05adc7a8990dce9cc75370895fbe4e3d58b9b73bf1aee56359344a55" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.109", -] - -[[package]] -name = "pin-project-lite" -version = "0.2.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" - -[[package]] -name = "pin-utils" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" - -[[package]] -name = "pkcs8" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9eca2c590a5f85da82668fa685c09ce2888b9430e83299debf1f34b65fd4a4ba" -dependencies = [ - "der 0.6.1", - "spki 0.6.0", -] - -[[package]] -name = "pkcs8" -version = "0.10.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" -dependencies = [ - "der 0.7.8", - "spki 0.7.2", -] - -[[package]] -name = "pkg-config" -version = "0.3.26" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ac9a59f73473f1b8d852421e59e64809f025994837ef743615c6d0c5b305160" - -[[package]] -name = "plist" -version = "1.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9bd9647b268a3d3e14ff09c23201133a62589c658db02bb7388c7246aafe0590" -dependencies = [ - "base64 0.21.2", - "indexmap 1.9.2", - "line-wrap", - "quick-xml 0.28.1", - "serde", - "time", -] - -[[package]] -name = "png" -version = "0.17.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d708eaf860a19b19ce538740d2b4bdeeb8337fa53f7738455e706623ad5c638" -dependencies = [ - "bitflags 1.3.2", - "crc32fast", - "flate2", - "miniz_oxide", -] - -[[package]] -name = "poly1305" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8159bd90725d2df49889a078b54f4f79e87f1f8a8444194cdca81d38f5393abf" -dependencies = [ - "cpufeatures", - "opaque-debug 0.3.0", - "universal-hash", -] - -[[package]] -name = "ppv-lite86" -version = "0.2.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" - -[[package]] -name = "precomputed-hash" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" - -[[package]] -name = "pretty_env_logger" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "926d36b9553851b8b0005f1275891b392ee4d2d833852c417ed025477350fb9d" -dependencies = [ - "env_logger", - "log", -] - -[[package]] -name = "proc-macro-crate" -version = "1.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919" -dependencies = [ - "once_cell", - "toml_edit", -] - -[[package]] -name = "proc-macro-error" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" -dependencies = [ - "proc-macro-error-attr", - "proc-macro2", - "quote", - "syn 1.0.109", - "version_check", -] - -[[package]] -name = "proc-macro-error-attr" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" -dependencies = [ - "proc-macro2", - "quote", - "version_check", -] - -[[package]] -name = "proc-macro-hack" -version = "0.5.20+deprecated" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc375e1527247fe1a97d8b7156678dfe7c1af2fc075c9a4db3690ecd2a148068" - -[[package]] -name = "proc-macro2" -version = "1.0.53" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba466839c78239c09faf015484e5cc04860f88242cff4d03eb038f04b4699b73" -dependencies = [ - "unicode-ident", -] - -[[package]] -name = "prost" -version = "0.11.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b82eaa1d779e9a4bc1c3217db8ffbeabaae1dca241bf70183242128d48681cd" -dependencies = [ - "bytes", - "prost-derive", -] - -[[package]] -name = "prost-derive" -version = "0.11.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5d2d8d10f3c6ded6da8b05b5fb3b8a5082514344d56c9f871412d29b4e075b4" -dependencies = [ - "anyhow", - "itertools", - "proc-macro2", - "quote", - "syn 1.0.109", -] - -[[package]] -name = "prost-types" -version = "0.11.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "213622a1460818959ac1181aaeb2dc9c7f63df720db7d788b3e24eacd1983e13" -dependencies = [ - "prost", -] - -[[package]] -name = "quick-error" -version = "1.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" - -[[package]] -name = "quick-error" -version = "2.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3" - -[[package]] -name = "quick-xml" -version = "0.23.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11bafc859c6815fbaffbbbf4229ecb767ac913fecb27f9ad4343662e9ef099ea" -dependencies = [ - "memchr", -] - -[[package]] -name = "quick-xml" -version = "0.28.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5c1a97b1bc42b1d550bfb48d4262153fe400a12bab1511821736f7eac76d7e2" -dependencies = [ - "memchr", -] - -[[package]] -name = "quote" -version = "1.0.26" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4424af4bf778aae2051a77b60283332f386554255d722233d09fbfc7e30da2fc" -dependencies = [ - "proc-macro2", -] - -[[package]] -name = "radium" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" - -[[package]] -name = "rand" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" -dependencies = [ - "getrandom 0.1.16", - "libc", - "rand_chacha 0.2.2", - "rand_core 0.5.1", - "rand_hc", - "rand_pcg", -] - -[[package]] -name = "rand" -version = "0.8.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" -dependencies = [ - "libc", - "rand_chacha 0.3.1", - "rand_core 0.6.4", -] - -[[package]] -name = "rand_chacha" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402" -dependencies = [ - "ppv-lite86", - "rand_core 0.5.1", -] - -[[package]] -name = "rand_chacha" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" -dependencies = [ - "ppv-lite86", - "rand_core 0.6.4", -] - -[[package]] -name = "rand_core" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" -dependencies = [ - "getrandom 0.1.16", -] - -[[package]] -name = "rand_core" -version = "0.6.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" -dependencies = [ - "getrandom 0.2.10", -] - -[[package]] -name = "rand_distr" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9e9532ada3929fb8b2e9dbe28d1e06c9b2cc65813f074fcb6bd5fbefeff9d56" -dependencies = [ - "num-traits", - "rand 0.7.3", -] - -[[package]] -name = "rand_hc" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" -dependencies = [ - "rand_core 0.5.1", -] - -[[package]] -name = "rand_pcg" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16abd0c1b639e9eb4d7c50c0b8100b0d0f849be2349829c740fe8e6eb4816429" -dependencies = [ - "rand_core 0.5.1", -] - -[[package]] -name = "raw-window-handle" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f851a03551ceefd30132e447f07f96cb7011d6b658374f3aed847333adb5559" - -[[package]] -name = "rayon" -version = "1.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d2df5196e37bcc87abebc0053e20787d73847bb33134a69841207dd0a47f03b" -dependencies = [ - "either", - "rayon-core", -] - -[[package]] -name = "rayon-core" -version = "1.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b8f95bd6966f5c87776639160a66bd8ab9895d9d4ab01ddba9fc60661aebe8d" -dependencies = [ - "crossbeam-channel", - "crossbeam-deque", - "crossbeam-utils", - "num_cpus", -] - -[[package]] -name = "redox_syscall" -version = "0.2.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" -dependencies = [ - "bitflags 1.3.2", -] - -[[package]] -name = "redox_users" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b" -dependencies = [ - "getrandom 0.2.10", - "redox_syscall", - "thiserror", -] - -[[package]] -name = "regex" -version = "1.7.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cce168fea28d3e05f158bda4576cf0c844d5045bc2cc3620fa0292ed5bb5814c" -dependencies = [ - "aho-corasick", - "memchr", - "regex-syntax", -] - -[[package]] -name = "regex-automata" -version = "0.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" -dependencies = [ - "regex-syntax", -] - -[[package]] -name = "regex-syntax" -version = "0.6.29" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" - -[[package]] -name = "reqwest" -version = "0.11.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cde824a14b7c14f85caff81225f411faacc04a2013f41670f41443742b1c1c55" -dependencies = [ - "base64 0.21.2", - "bytes", - "encoding_rs", - "futures-core", - "futures-util", - "h2", - "http", - "http-body", - "hyper", - "hyper-tls", - "ipnet", - "js-sys", - "log", - "mime", - "native-tls", - "once_cell", - "percent-encoding", - "pin-project-lite", - "serde", - "serde_json", - "serde_urlencoded", - "tokio", - "tokio-native-tls", - "tokio-socks", - "tower-service", - "url", - "wasm-bindgen", - "wasm-bindgen-futures", - "web-sys", - "winreg", -] - -[[package]] -name = "rfc6979" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7743f17af12fa0b03b803ba12cd6a8d9483a587e89c69445e3909655c0b9fabb" -dependencies = [ - "crypto-bigint 0.4.9", - "hmac 0.12.1", - "zeroize", -] - -[[package]] -name = "rfc6979" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2" -dependencies = [ - "hmac 0.12.1", - "subtle 2.4.1", -] - -[[package]] -name = "rfd" -version = "0.11.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b344b73e5688c6ad9389095d8e78a877637078f490ae17259937371113ebcb67" -dependencies = [ - "block", - "dispatch", - "glib-sys", - "gobject-sys", - "gtk-sys", - "js-sys", - "log", - "objc", - "objc-foundation", - "objc_id", - "raw-window-handle", - "wasm-bindgen", - "wasm-bindgen-futures", - "web-sys", - "windows 0.44.0", -] - -[[package]] -name = "ring" -version = "0.16.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc" -dependencies = [ - "cc", - "libc", - "once_cell", - "spin 0.5.2", - "untrusted", - "web-sys", - "winapi", -] - -[[package]] -name = "ripemd" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd124222d17ad93a644ed9d011a40f4fb64aa54275c08cc216524a9ea82fb09f" -dependencies = [ - "digest 0.10.7", -] - -[[package]] -name = "rust-embed" -version = "6.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb133b9a38b5543fad3807fb2028ea47c5f2b566f4f5e28a11902f1a358348b6" -dependencies = [ - "rust-embed-impl", - "rust-embed-utils", - "walkdir", -] - -[[package]] -name = "rust-embed-impl" -version = "6.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d4e0f0ced47ded9a68374ac145edd65a6c1fa13a96447b873660b2a568a0fd7" -dependencies = [ - "proc-macro2", - "quote", - "rust-embed-utils", - "syn 1.0.109", - "walkdir", -] - -[[package]] -name = "rust-embed-utils" -version = "7.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "512b0ab6853f7e14e3c8754acb43d6f748bb9ced66aa5915a6553ac8213f7731" -dependencies = [ - "globset", - "sha2 0.10.6", - "walkdir", -] - -[[package]] -name = "rustc_version" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" -dependencies = [ - "semver 1.0.17", -] - -[[package]] -name = "rustix" -version = "0.36.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db4165c9963ab29e422d6c26fbc1d37f15bace6b2810221f9d925023480fcf0e" -dependencies = [ - "bitflags 1.3.2", - "errno", - "io-lifetimes", - "libc", - "linux-raw-sys", - "windows-sys 0.45.0", -] - -[[package]] -name = "rustls" -version = "0.19.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35edb675feee39aec9c99fa5ff985081995a06d594114ae14cbe797ad7b7a6d7" -dependencies = [ - "base64 0.13.1", - "log", - "ring", - "sct 0.6.1", - "webpki 0.21.4", -] - -[[package]] -name = "rustls" -version = "0.20.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fff78fc74d175294f4e83b28343315ffcfb114b156f0185e9741cb5570f50e2f" -dependencies = [ - "log", - "ring", - "sct 0.7.0", - "webpki 0.22.0", -] - -[[package]] -name = "rustls-native-certs" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a07b7c1885bd8ed3831c289b7870b13ef46fe0e856d288c30d9cc17d75a2092" -dependencies = [ - "openssl-probe", - "rustls 0.19.1", - "schannel", - "security-framework", -] - -[[package]] -name = "rustls-pemfile" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d194b56d58803a43635bdc398cd17e383d6f71f9182b9a192c127ca42494a59b" -dependencies = [ - "base64 0.21.2", -] - -[[package]] -name = "rustversion" -version = "1.0.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f3208ce4d8448b3f3e7d168a73f5e0c43a61e32930de3bceeccedb388b6bf06" - -[[package]] -name = "ryu" -version = "1.0.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f91339c0467de62360649f8d3e185ca8de4224ff281f66000de5eb2a77a79041" - -[[package]] -name = "safemem" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef703b7cb59335eae2eb93ceb664c0eb7ea6bf567079d843e09420219668e072" - -[[package]] -name = "same-file" -version = "1.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" -dependencies = [ - "winapi-util", -] - -[[package]] -name = "schannel" -version = "0.1.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "713cfb06c7059f3588fb8044c0fad1d09e3c01d225e25b9220dbfdcf16dbb1b3" -dependencies = [ - "windows-sys 0.42.0", -] - -[[package]] -name = "schemars" -version = "0.8.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02c613288622e5f0c3fdc5dbd4db1c5fbe752746b1d1a56a0630b78fd00de44f" -dependencies = [ - "dyn-clone", - "indexmap 1.9.2", - "schemars_derive", - "serde", - "serde_json", -] - -[[package]] -name = "schemars_derive" -version = "0.8.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "109da1e6b197438deb6db99952990c7f959572794b80ff93707d55a232545e7c" -dependencies = [ - "proc-macro2", - "quote", - "serde_derive_internals", - "syn 1.0.109", -] - -[[package]] -name = "scoped-tls" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" - -[[package]] -name = "scopeguard" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" - -[[package]] -name = "sct" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b362b83898e0e69f38515b82ee15aa80636befe47c3b6d3d89a911e78fc228ce" -dependencies = [ - "ring", - "untrusted", -] - -[[package]] -name = "sct" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d53dcdb7c9f8158937a7981b48accfd39a43af418591a5d008c7b22b5e1b7ca4" -dependencies = [ - "ring", - "untrusted", -] - -[[package]] -name = "sec1" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3be24c1842290c45df0a7bf069e0c268a747ad05a192f2fd7dcfdbc1cba40928" -dependencies = [ - "base16ct 0.1.1", - "der 0.6.1", - "generic-array 0.14.7", - "pkcs8 0.9.0", - "subtle 2.4.1", - "zeroize", -] - -[[package]] -name = "sec1" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc" -dependencies = [ - "base16ct 0.2.0", - "der 0.7.8", - "generic-array 0.14.7", - "pkcs8 0.10.2", - "subtle 2.4.1", - "zeroize", -] - -[[package]] -name = "security-framework" -version = "2.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a332be01508d814fed64bf28f798a146d73792121129962fdf335bb3c49a4254" -dependencies = [ - "bitflags 1.3.2", - "core-foundation", - "core-foundation-sys", - "libc", - "security-framework-sys", -] - -[[package]] -name = "security-framework-sys" -version = "2.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31c9bb296072e961fcbd8853511dd39c2d8be2deb1e17c6860b1d30732b323b4" -dependencies = [ - "core-foundation-sys", - "libc", -] - -[[package]] -name = "selectors" -version = "0.22.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df320f1889ac4ba6bc0cdc9c9af7af4bd64bb927bccdf32d81140dc1f9be12fe" -dependencies = [ - "bitflags 1.3.2", - "cssparser", - "derive_more", - "fxhash", - "log", - "matches", - "phf 0.8.0", - "phf_codegen", - "precomputed-hash", - "servo_arc", - "smallvec", - "thin-slice", -] - -[[package]] -name = "semver" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f301af10236f6df4160f7c3f04eec6dbc70ace82d23326abad5edee88801c6b6" -dependencies = [ - "semver-parser", -] - -[[package]] -name = "semver" -version = "1.0.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bebd363326d05ec3e2f532ab7660680f3b02130d780c299bca73469d521bc0ed" -dependencies = [ - "serde", -] - -[[package]] -name = "semver-parser" -version = "0.10.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00b0bef5b7f9e0df16536d3961cfb6e84331c065b4066afb39768d0e319411f7" -dependencies = [ - "pest", -] - -[[package]] -name = "serde" -version = "1.0.158" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "771d4d9c4163ee138805e12c710dd365e4f44be8be0503cb1bb9eb989425d9c9" -dependencies = [ - "serde_derive", -] - -[[package]] -name = "serde-json-wasm" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a15bee9b04dd165c3f4e142628982ddde884c2022a89e8ddf99c4829bf2c3a58" -dependencies = [ - "serde", -] - -[[package]] -name = "serde_bytes" -version = "0.11.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "416bda436f9aab92e02c8e10d49a15ddd339cea90b6e340fe51ed97abb548294" -dependencies = [ - "serde", -] - -[[package]] -name = "serde_derive" -version = "1.0.158" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e801c1712f48475582b7696ac71e0ca34ebb30e09338425384269d9717c62cad" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.5", -] - -[[package]] -name = "serde_derive_internals" -version = "0.26.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85bf8229e7920a9f636479437026331ce11aa132b4dde37d121944a44d6e5f3c" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.109", -] - -[[package]] -name = "serde_json" -version = "1.0.94" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c533a59c9d8a93a09c6ab31f0fd5e5f4dd1b8fc9434804029839884765d04ea" -dependencies = [ - "itoa 1.0.6", - "ryu", - "serde", -] - -[[package]] -name = "serde_repr" -version = "0.1.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bcec881020c684085e55a25f7fd888954d56609ef363479dc5a1305eb0d40cab" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.5", -] - -[[package]] -name = "serde_spanned" -version = "0.6.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96426c9936fd7a0124915f9185ea1d20aa9445cc9821142f0a73bc9207a2e186" -dependencies = [ - "serde", -] - -[[package]] -name = "serde_urlencoded" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" -dependencies = [ - "form_urlencoded", - "itoa 1.0.6", - "ryu", - "serde", -] - -[[package]] -name = "serde_with" -version = "1.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "678b5a069e50bf00ecd22d0cd8ddf7c236f68581b03db652061ed5eb13a312ff" -dependencies = [ - "serde", - "serde_with_macros", -] - -[[package]] -name = "serde_with_macros" -version = "1.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e182d6ec6f05393cc0e5ed1bf81ad6db3a8feedf8ee515ecdd369809bcce8082" -dependencies = [ - "darling", - "proc-macro2", - "quote", - "syn 1.0.109", -] - -[[package]] -name = "serialize-to-javascript" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9823f2d3b6a81d98228151fdeaf848206a7855a7a042bbf9bf870449a66cafb" -dependencies = [ - "serde", - "serde_json", - "serialize-to-javascript-impl", -] - -[[package]] -name = "serialize-to-javascript-impl" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74064874e9f6a15f04c1f3cb627902d0e6b410abbf36668afa873c61889f1763" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.109", -] - -[[package]] -name = "servo_arc" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d98238b800e0d1576d8b6e3de32827c2d74bee68bb97748dcf5071fb53965432" -dependencies = [ - "nodrop", - "stable_deref_trait", -] - -[[package]] -name = "sha-1" -version = "0.9.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99cd6713db3cf16b6c84e06321e049a9b9f699826e16096d23bbcc44d15d51a6" -dependencies = [ - "block-buffer 0.9.0", - "cfg-if", - "cpufeatures", - "digest 0.9.0", - "opaque-debug 0.3.0", -] - -[[package]] -name = "sha1" -version = "0.10.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f04293dc80c3993519f2d7f6f511707ee7094fe0c6d3406feb330cdb3540eba3" -dependencies = [ - "cfg-if", - "cpufeatures", - "digest 0.10.7", -] - -[[package]] -name = "sha2" -version = "0.9.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d58a1e1bf39749807d89cf2d98ac2dfa0ff1cb3faa38fbb64dd88ac8013d800" -dependencies = [ - "block-buffer 0.9.0", - "cfg-if", - "cpufeatures", - "digest 0.9.0", - "opaque-debug 0.3.0", -] - -[[package]] -name = "sha2" -version = "0.10.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82e6b795fe2e3b1e845bafcb27aa35405c4d47cdfc92af5fc8d3002f76cebdc0" -dependencies = [ - "cfg-if", - "cpufeatures", - "digest 0.10.7", -] - -[[package]] -name = "sharded-slab" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "900fba806f70c630b0a382d0d825e17a0f19fcd059a2ade1ff237bcddf446b31" -dependencies = [ - "lazy_static", -] - -[[package]] -name = "signal-hook-registry" -version = "1.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1" -dependencies = [ - "libc", -] - -[[package]] -name = "signature" -version = "1.6.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74233d3b3b2f6d4b006dc19dee745e73e2a6bfb6f93607cd3b02bd5b00797d7c" -dependencies = [ - "digest 0.10.7", - "rand_core 0.6.4", -] - -[[package]] -name = "signature" -version = "2.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e1788eed21689f9cf370582dfc467ef36ed9c707f073528ddafa8d83e3b8500" -dependencies = [ - "digest 0.10.7", - "rand_core 0.6.4", -] - -[[package]] -name = "siphasher" -version = "0.3.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7bd3e3206899af3f8b12af284fafc038cc1dc2b41d1b89dd17297221c5d225de" - -[[package]] -name = "slab" -version = "0.4.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6528351c9bc8ab22353f9d776db39a20288e8d6c37ef8cfe3317cf875eecfc2d" -dependencies = [ - "autocfg", -] - -[[package]] -name = "smallvec" -version = "1.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0" - -[[package]] -name = "socket2" -version = "0.4.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64a4a911eed85daf18834cfaa86a79b7d266ff93ff5ba14005426219480ed662" -dependencies = [ - "libc", - "winapi", -] - -[[package]] -name = "soup3" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82bc46048125fefd69d30b32b9d263d6556c9ffe82a7a7df181a86d912da5616" -dependencies = [ - "bitflags 1.3.2", - "futures-channel", - "gio", - "glib", - "libc", - "once_cell", - "soup3-sys", -] - -[[package]] -name = "soup3-sys" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "014bbeb1c4cdb30739dc181e8d98b7908f124d9555843afa89b5570aaf4ec62b" -dependencies = [ - "gio-sys", - "glib-sys", - "gobject-sys", - "libc", - "system-deps", -] - -[[package]] -name = "sphinx-packet" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc43eda802856ee82a7555c7b75ceb9e07451741c7a2f5f23d036020e01189d4" -dependencies = [ - "aes 0.7.5", - "arrayref", - "blake2", - "bs58 0.4.0", - "byteorder", - "chacha", - "curve25519-dalek", - "digest 0.9.0", - "hkdf 0.11.0", - "hmac 0.11.0", - "lioness", - "log", - "rand 0.7.3", - "rand_distr", - "sha2 0.9.9", - "subtle 2.4.1", -] - -[[package]] -name = "spin" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" - -[[package]] -name = "spin" -version = "0.9.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5d6e0250b93c8427a177b849d144a96d5acc57006149479403d7861ab721e34" -dependencies = [ - "lock_api", -] - -[[package]] -name = "spki" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67cf02bbac7a337dc36e4f5a693db6c21e7863f45070f7064577eb4367a3212b" -dependencies = [ - "base64ct", - "der 0.6.1", -] - -[[package]] -name = "spki" -version = "0.7.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d1e996ef02c474957d681f1b05213dfb0abab947b446a62d37770b23500184a" -dependencies = [ - "base64ct", - "der 0.7.8", -] - -[[package]] -name = "sqlformat" -version = "0.1.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4b7922be017ee70900be125523f38bdd644f4f06a1b16e8fa5a8ee8c34bffd4" -dependencies = [ - "itertools", - "nom", - "unicode_categories", -] - -[[package]] -name = "sqlformat" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c12bc9199d1db8234678b7051747c07f517cdcf019262d1847b94ec8b1aee3e" -dependencies = [ - "itertools", - "nom", - "unicode_categories", -] - -[[package]] -name = "sqlx" -version = "0.5.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "551873805652ba0d912fec5bbb0f8b4cdd96baf8e2ebf5970e5671092966019b" -dependencies = [ - "sqlx-core 0.5.13", - "sqlx-macros 0.5.13", -] - -[[package]] -name = "sqlx" -version = "0.6.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8de3b03a925878ed54a954f621e64bf55a3c1bd29652d0d1a17830405350188" -dependencies = [ - "sqlx-core 0.6.3", - "sqlx-macros 0.6.3", -] - -[[package]] -name = "sqlx-core" -version = "0.5.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e48c61941ccf5ddcada342cd59e3e5173b007c509e1e8e990dafc830294d9dc5" -dependencies = [ - "ahash", - "atoi 0.4.0", - "bitflags 1.3.2", - "byteorder", - "bytes", - "crc 2.1.0", - "crossbeam-queue", - "either", - "event-listener", - "flume", - "futures-channel", - "futures-core", - "futures-executor", - "futures-intrusive", - "futures-util", - "hashlink 0.7.0", - "hex", - "indexmap 1.9.2", - "itoa 1.0.6", - "libc", - "libsqlite3-sys", - "log", - "memchr", - "once_cell", - "paste", - "percent-encoding", - "rustls 0.19.1", - "sha2 0.10.6", - "smallvec", - "sqlformat 0.1.8", - "sqlx-rt 0.5.13", - "stringprep", - "thiserror", - "tokio-stream", - "url", - "webpki 0.21.4", - "webpki-roots 0.21.1", -] - -[[package]] -name = "sqlx-core" -version = "0.6.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa8241483a83a3f33aa5fff7e7d9def398ff9990b2752b6c6112b83c6d246029" -dependencies = [ - "ahash", - "atoi 1.0.0", - "bitflags 1.3.2", - "byteorder", - "bytes", - "crc 3.0.1", - "crossbeam-queue", - "dotenvy", - "either", - "event-listener", - "flume", - "futures-channel", - "futures-core", - "futures-executor", - "futures-intrusive", - "futures-util", - "hashlink 0.8.1", - "hex", - "indexmap 1.9.2", - "itoa 1.0.6", - "libc", - "libsqlite3-sys", - "log", - "memchr", - "once_cell", - "paste", - "percent-encoding", - "rustls 0.20.8", - "rustls-pemfile", - "sha2 0.10.6", - "smallvec", - "sqlformat 0.2.1", - "sqlx-rt 0.6.3", - "stringprep", - "thiserror", - "tokio-stream", - "url", - "webpki-roots 0.22.6", -] - -[[package]] -name = "sqlx-macros" -version = "0.5.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc0fba2b0cae21fc00fe6046f8baa4c7fcb49e379f0f592b04696607f69ed2e1" -dependencies = [ - "dotenv", - "either", - "heck 0.4.1", - "once_cell", - "proc-macro2", - "quote", - "sha2 0.10.6", - "sqlx-core 0.5.13", - "sqlx-rt 0.5.13", - "syn 1.0.109", - "url", -] - -[[package]] -name = "sqlx-macros" -version = "0.6.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9966e64ae989e7e575b19d7265cb79d7fc3cbbdf179835cb0d716f294c2049c9" -dependencies = [ - "dotenvy", - "either", - "heck 0.4.1", - "once_cell", - "proc-macro2", - "quote", - "sha2 0.10.6", - "sqlx-core 0.6.3", - "sqlx-rt 0.6.3", - "syn 1.0.109", - "url", -] - -[[package]] -name = "sqlx-rt" -version = "0.5.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4db708cd3e459078f85f39f96a00960bd841f66ee2a669e90bf36907f5a79aae" -dependencies = [ - "once_cell", - "tokio", - "tokio-rustls 0.22.0", -] - -[[package]] -name = "sqlx-rt" -version = "0.6.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "804d3f245f894e61b1e6263c84b23ca675d96753b5abfd5cc8597d86806e8024" -dependencies = [ - "once_cell", - "tokio", - "tokio-rustls 0.23.4", -] - -[[package]] -name = "stable_deref_trait" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" - -[[package]] -name = "state" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dbe866e1e51e8260c9eed836a042a5e7f6726bb2b411dffeaa712e19c388f23b" -dependencies = [ - "loom", -] - -[[package]] -name = "string_cache" -version = "0.8.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f91138e76242f575eb1d3b38b4f1362f10d3a43f47d182a5b359af488a02293b" -dependencies = [ - "new_debug_unreachable", - "once_cell", - "parking_lot 0.12.1", - "phf_shared 0.10.0", - "precomputed-hash", - "serde", -] - -[[package]] -name = "string_cache_codegen" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6bb30289b722be4ff74a408c3cc27edeaad656e06cb1fe8fa9231fa59c728988" -dependencies = [ - "phf_generator 0.10.0", - "phf_shared 0.10.0", - "proc-macro2", - "quote", -] - -[[package]] -name = "stringprep" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ee348cb74b87454fff4b551cbf727025810a004f88aeacae7f85b87f4e9a1c1" -dependencies = [ - "unicode-bidi", - "unicode-normalization", -] - -[[package]] -name = "strip-ansi-escapes" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "011cbb39cf7c1f62871aea3cc46e5817b0937b49e9447370c93cacbe93a766d8" -dependencies = [ - "vte", -] - -[[package]] -name = "strsim" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" - -[[package]] -name = "strum" -version = "0.22.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7ac893c7d471c8a21f31cfe213ec4f6d9afeed25537c772e08ef3f005f8729e" -dependencies = [ - "strum_macros", -] - -[[package]] -name = "strum_macros" -version = "0.22.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "339f799d8b549e3744c7ac7feb216383e4005d94bdb22561b3ab8f3b808ae9fb" -dependencies = [ - "heck 0.3.3", - "proc-macro2", - "quote", - "syn 1.0.109", -] - -[[package]] -name = "subtle" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d67a5a62ba6e01cb2192ff309324cb4875d0c451d55fe2319433abe7a05a8ee" - -[[package]] -name = "subtle" -version = "2.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601" - -[[package]] -name = "subtle-encoding" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7dcb1ed7b8330c5eed5441052651dd7a12c75e2ed88f2ec024ae1fa3a5e59945" -dependencies = [ - "zeroize", -] - -[[package]] -name = "subtle-ng" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "734676eb262c623cec13c3155096e08d1f8f29adce39ba17948b18dad1e54142" - -[[package]] -name = "swift-rs" -version = "1.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2eab55f44d02ced20ffa22fd5b4bd083ab59c940c7637d37fec4426b1a01d769" -dependencies = [ - "base64 0.21.2", - "serde", - "serde_json", -] - -[[package]] -name = "syn" -version = "1.0.109" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - -[[package]] -name = "syn" -version = "2.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89c2d1c76a26822187a1fbb5964e3fff108bc208f02e820ab9dac1234f6b388a" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - -[[package]] -name = "synstructure" -version = "0.12.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f36bdaa60a83aca3921b5259d5400cbf5e90fc51931376a9bd4a0eb79aa7210f" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.109", - "unicode-xid", -] - -[[package]] -name = "system-deps" -version = "6.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2955b1fe31e1fa2fbd1976b71cc69a606d7d4da16f6de3333d0c92d51419aeff" -dependencies = [ - "cfg-expr", - "heck 0.4.1", - "pkg-config", - "toml 0.5.11", - "version-compare", -] - -[[package]] -name = "tao" -version = "0.17.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d021218bcb43d4bf42112b1da5f7d8454502ffb188489ba0089fe4e3e34a8f6" -dependencies = [ - "bitflags 1.3.2", - "cairo-rs", - "cc", - "cocoa", - "core-foundation", - "core-graphics", - "crossbeam-channel", - "dispatch", - "gdk", - "gdk-pixbuf", - "gdk-sys", - "gdkx11-sys", - "gio", - "glib", - "glib-sys", - "gtk", - "image", - "instant", - "jni", - "lazy_static", - "libc", - "log", - "ndk", - "ndk-context", - "ndk-sys", - "objc", - "once_cell", - "parking_lot 0.12.1", - "png", - "raw-window-handle", - "scopeguard", - "serde", - "tao-macros", - "unicode-segmentation", - "uuid 1.3.0", - "windows 0.39.0", - "windows-implement 0.39.0", - "x11-dl", -] - -[[package]] -name = "tao-macros" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b27a4bcc5eb524658234589bdffc7e7bfb996dbae6ce9393bfd39cb4159b445" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.109", -] - -[[package]] -name = "tap" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" - -[[package]] -name = "tar" -version = "0.4.38" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b55807c0344e1e6c04d7c965f5289c39a8d94ae23ed5c0b57aabac549f871c6" -dependencies = [ - "filetime", - "libc", - "xattr", -] - -[[package]] -name = "tauri" -version = "2.0.0-alpha.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3eb6015452c545d13d2eb04166376474ed0296bc647229f798937f75d3e9908" -dependencies = [ - "anyhow", - "attohttpc", - "cocoa", - "dirs-next", - "embed_plist", - "encoding_rs", - "flate2", - "futures-util", - "glib", - "glob", - "gtk", - "heck 0.4.1", - "http", - "ignore", - "libc", - "log", - "notify-rust", - "objc", - "once_cell", - "open", - "paste", - "percent-encoding", - "rand 0.8.5", - "raw-window-handle", - "regex", - "rfd", - "semver 1.0.17", - "serde", - "serde_json", - "serde_repr", - "serialize-to-javascript", - "state", - "tar", - "tauri-macros", - "tauri-runtime", - "tauri-runtime-wry", - "tauri-utils", - "tempfile", - "thiserror", - "tokio", - "url", - "uuid 1.3.0", - "webkit2gtk", - "webview2-com", - "windows 0.39.0", -] - -[[package]] -name = "tauri-build" -version = "2.0.0-alpha.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "383f94e1b46fb4e249e4eafbd0589ffbe02b241b49dadebd47640598a17e584e" -dependencies = [ - "anyhow", - "cargo_toml", - "filetime", - "heck 0.4.1", - "json-patch 0.3.0", - "semver 1.0.17", - "serde", - "serde_json", - "swift-rs", - "tauri-utils", - "tauri-winres", - "walkdir", -] - -[[package]] -name = "tauri-codegen" -version = "2.0.0-alpha.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af7d3464109ac3b7fbff0d78f1bc6b309af49c3d6267ebace90c5a47fbac58cb" -dependencies = [ - "base64 0.13.1", - "brotli", - "ico", - "json-patch 0.2.7", - "plist", - "png", - "proc-macro2", - "quote", - "regex", - "semver 1.0.17", - "serde", - "serde_json", - "sha2 0.10.6", - "tauri-utils", - "thiserror", - "time", - "url", - "uuid 1.3.0", - "walkdir", -] - -[[package]] -name = "tauri-macros" -version = "2.0.0-alpha.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "969f323a5c603fe2d113dd1098b6d1a965fd92b78e7bd7f74eb0bb6d7d948183" -dependencies = [ - "heck 0.4.1", - "proc-macro2", - "quote", - "syn 1.0.109", - "tauri-codegen", - "tauri-utils", -] - -[[package]] -name = "tauri-runtime" -version = "0.13.0-alpha.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c7369709ff9ac16a1235906801033804d8ea33e4d023e066f06df0c5c9592a0" -dependencies = [ - "gtk", - "http", - "http-range", - "rand 0.8.5", - "raw-window-handle", - "serde", - "serde_json", - "tauri-utils", - "thiserror", - "uuid 1.3.0", - "webview2-com", - "windows 0.39.0", -] - -[[package]] -name = "tauri-runtime-wry" -version = "0.13.0-alpha.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b914d691ef830dfa98658409eea9d9116240ae0c9d3eb228eed2a9c139a60916" -dependencies = [ - "cocoa", - "gtk", - "percent-encoding", - "rand 0.8.5", - "raw-window-handle", - "tauri-runtime", - "tauri-utils", - "uuid 1.3.0", - "webkit2gtk", - "webview2-com", - "windows 0.39.0", - "wry", -] - -[[package]] -name = "tauri-utils" -version = "2.0.0-alpha.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a488bb0c60e2ce9efbe8283a11299675e01f55caf12304889f8639b3c35713c" -dependencies = [ - "brotli", - "ctor", - "glob", - "heck 0.4.1", - "html5ever", - "infer", - "json-patch 0.3.0", - "kuchiki", - "memchr", - "phf 0.10.1", - "proc-macro2", - "quote", - "semver 1.0.17", - "serde", - "serde_json", - "serde_with", - "thiserror", - "url", - "walkdir", - "windows 0.44.0", -] - -[[package]] -name = "tauri-winres" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b7a78dc04f75fb5ab815e66ac561c81e92a968a40f29e7c21afd152d694fad8" -dependencies = [ - "toml 0.5.11", - "version_check", -] - -[[package]] -name = "tauri-winrt-notification" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c58de036c4d2e20717024de2a3c4bf56c301f07b21bc8ef9b57189fce06f1f3b" -dependencies = [ - "quick-xml 0.23.1", - "strum", - "windows 0.39.0", -] - -[[package]] -name = "tempfile" -version = "3.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af18f7ae1acd354b992402e9ec5864359d693cd8a79dcbef59f76891701c1e95" -dependencies = [ - "cfg-if", - "fastrand", - "redox_syscall", - "rustix", - "windows-sys 0.42.0", -] - -[[package]] -name = "tendermint" -version = "0.32.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f0a7d05cf78524782337f8edd55cbc578d159a16ad4affe2135c92f7dbac7f0" -dependencies = [ - "bytes", - "digest 0.10.7", - "ed25519 2.2.2", - "ed25519-consensus", - "flex-error", - "futures", - "k256 0.13.1", - "num-traits", - "once_cell", - "prost", - "prost-types", - "ripemd", - "serde", - "serde_bytes", - "serde_json", - "serde_repr", - "sha2 0.10.6", - "signature 2.1.0", - "subtle 2.4.1", - "subtle-encoding", - "tendermint-proto", - "time", - "zeroize", -] - -[[package]] -name = "tendermint-config" -version = "0.32.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "71a72dbbea6dde12045d261f2c70c0de039125675e8a026c8d5ad34522756372" -dependencies = [ - "flex-error", - "serde", - "serde_json", - "tendermint", - "toml 0.5.11", - "url", -] - -[[package]] -name = "tendermint-proto" -version = "0.32.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0cec054567d16d85e8c3f6a3139963d1a66d9d3051ed545d31562550e9bcc3d" -dependencies = [ - "bytes", - "flex-error", - "num-derive", - "num-traits", - "prost", - "prost-types", - "serde", - "serde_bytes", - "subtle-encoding", - "time", -] - -[[package]] -name = "tendermint-rpc" -version = "0.32.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d119d83a130537fc4a98c3c9eb6899ebe857fea4860400a61675bfb5f0b35129" -dependencies = [ - "async-trait", - "bytes", - "flex-error", - "futures", - "getrandom 0.2.10", - "http", - "hyper", - "hyper-proxy", - "hyper-rustls", - "peg", - "pin-project", - "semver 1.0.17", - "serde", - "serde_bytes", - "serde_json", - "subtle 2.4.1", - "subtle-encoding", - "tendermint", - "tendermint-config", - "tendermint-proto", - "thiserror", - "time", - "tokio", - "tracing", - "url", - "uuid 0.8.2", - "walkdir", -] - -[[package]] -name = "tendril" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d24a120c5fc464a3458240ee02c299ebcb9d67b5249c8848b09d639dca8d7bb0" -dependencies = [ - "futf", - "mac", - "utf-8", -] - -[[package]] -name = "termcolor" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be55cf8942feac5c765c2c993422806843c9a9a45d4d5c407ad6dd2ea95eb9b6" -dependencies = [ - "winapi-util", -] - -[[package]] -name = "thin-slice" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8eaa81235c7058867fa8c0e7314f33dcce9c215f535d1913822a2b3f5e289f3c" - -[[package]] -name = "thiserror" -version = "1.0.40" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "978c9a314bd8dc99be594bc3c175faaa9794be04a5a5e153caba6915336cebac" -dependencies = [ - "thiserror-impl", -] - -[[package]] -name = "thiserror-impl" -version = "1.0.40" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9456a42c5b0d803c8cd86e73dd7cc9edd429499f37a3550d286d5e86720569f" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.5", -] - -[[package]] -name = "thread_local" -version = "1.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fdd6f064ccff2d6567adcb3873ca630700f00b5ad3f060c25b5dcfd9a4ce152" -dependencies = [ - "cfg-if", - "once_cell", -] - -[[package]] -name = "time" -version = "0.3.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd0cbfecb4d19b5ea75bb31ad904eb5b9fa13f21079c3b92017ebdf4999a5890" -dependencies = [ - "itoa 1.0.6", - "js-sys", - "libc", - "num_threads", - "serde", - "time-core", - "time-macros", -] - -[[package]] -name = "time-core" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e153e1f1acaef8acc537e68b44906d2db6436e2b35ac2c6b42640fff91f00fd" - -[[package]] -name = "time-macros" -version = "0.2.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd80a657e71da814b8e5d60d3374fc6d35045062245d80224748ae522dd76f36" -dependencies = [ - "time-core", -] - -[[package]] -name = "tinyvec" -version = "1.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" -dependencies = [ - "tinyvec_macros", -] - -[[package]] -name = "tinyvec_macros" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" - -[[package]] -name = "tokio" -version = "1.26.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03201d01c3c27a29c8a5cee5b55a93ddae1ccf6f08f65365c2c918f8c1b76f64" -dependencies = [ - "autocfg", - "bytes", - "libc", - "memchr", - "mio", - "num_cpus", - "pin-project-lite", - "signal-hook-registry", - "socket2", - "tokio-macros", - "windows-sys 0.45.0", -] - -[[package]] -name = "tokio-macros" -version = "1.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d266c00fde287f55d3f1c3e96c500c362a2b8c695076ec180f27918820bc6df8" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.109", -] - -[[package]] -name = "tokio-native-tls" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" -dependencies = [ - "native-tls", - "tokio", -] - -[[package]] -name = "tokio-rustls" -version = "0.22.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc6844de72e57df1980054b38be3a9f4702aba4858be64dd700181a8a6d0e1b6" -dependencies = [ - "rustls 0.19.1", - "tokio", - "webpki 0.21.4", -] - -[[package]] -name = "tokio-rustls" -version = "0.23.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c43ee83903113e03984cb9e5cebe6c04a5116269e900e3ddba8f068a62adda59" -dependencies = [ - "rustls 0.20.8", - "tokio", - "webpki 0.22.0", -] - -[[package]] -name = "tokio-socks" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51165dfa029d2a65969413a6cc96f354b86b464498702f174a4efa13608fd8c0" -dependencies = [ - "either", - "futures-util", - "thiserror", - "tokio", -] - -[[package]] -name = "tokio-stream" -version = "0.1.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fb52b74f05dbf495a8fba459fdc331812b96aa086d9eb78101fa0d4569c3313" -dependencies = [ - "futures-core", - "pin-project-lite", - "tokio", - "tokio-util", -] - -[[package]] -name = "tokio-tungstenite" -version = "0.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e96bb520beab540ab664bd5a9cfeaa1fcd846fa68c830b42e2c8963071251d2" -dependencies = [ - "futures-util", - "log", - "pin-project", - "tokio", - "tungstenite", -] - -[[package]] -name = "tokio-util" -version = "0.7.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5427d89453009325de0d8f342c9490009f76e999cb7672d77e46267448f7e6b2" -dependencies = [ - "bytes", - "futures-core", - "futures-sink", - "pin-project-lite", - "slab", - "tokio", - "tracing", -] - -[[package]] -name = "toml" -version = "0.5.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234" -dependencies = [ - "serde", -] - -[[package]] -name = "toml" -version = "0.7.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c17e963a819c331dcacd7ab957d80bc2b9a9c1e71c804826d2f283dd65306542" -dependencies = [ - "serde", - "serde_spanned", - "toml_datetime", - "toml_edit", -] - -[[package]] -name = "toml_datetime" -version = "0.6.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7cda73e2f1397b1262d6dfdcef8aafae14d1de7748d66822d3bfeeb6d03e5e4b" -dependencies = [ - "serde", -] - -[[package]] -name = "toml_edit" -version = "0.19.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8123f27e969974a3dfba720fdb560be359f57b44302d280ba72e76a74480e8a" -dependencies = [ - "indexmap 2.0.0", - "serde", - "serde_spanned", - "toml_datetime", - "winnow", -] - -[[package]] -name = "tower-service" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" - -[[package]] -name = "tracing" -version = "0.1.37" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8" -dependencies = [ - "cfg-if", - "pin-project-lite", - "tracing-attributes", - "tracing-core", -] - -[[package]] -name = "tracing-attributes" -version = "0.1.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4017f8f45139870ca7e672686113917c71c7a6e02d4924eda67186083c03081a" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.109", -] - -[[package]] -name = "tracing-core" -version = "0.1.30" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24eb03ba0eab1fd845050058ce5e616558e8f8d8fca633e6b163fe25c797213a" -dependencies = [ - "once_cell", - "valuable", -] - -[[package]] -name = "tracing-log" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78ddad33d2d10b1ed7eb9d1f518a5674713876e97e5bb9b7345a7984fbb4f922" -dependencies = [ - "lazy_static", - "log", - "tracing-core", -] - -[[package]] -name = "tracing-subscriber" -version = "0.3.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6176eae26dd70d0c919749377897b54a9276bd7061339665dd68777926b5a70" -dependencies = [ - "matchers", - "nu-ansi-term", - "once_cell", - "regex", - "sharded-slab", - "smallvec", - "thread_local", - "tracing", - "tracing-core", - "tracing-log", -] - -[[package]] -name = "treediff" -version = "3.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "761e8d5ad7ce14bb82b7e61ccc0ca961005a275a060b9644a2431aa11553c2ff" -dependencies = [ - "serde_json", -] - -[[package]] -name = "treediff" -version = "4.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52984d277bdf2a751072b5df30ec0377febdb02f7696d64c2d7d54630bac4303" -dependencies = [ - "serde_json", -] - -[[package]] -name = "try-lock" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3528ecfd12c466c6f163363caf2d02a71161dd5e1cc6ae7b34207ea2d42d81ed" - -[[package]] -name = "ts-rs" -version = "6.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4added4070a4fdf9df03457206cd2e4b12417c8560a2954d91ffcbe60177a56a" -dependencies = [ - "thiserror", - "ts-rs-macros", -] - -[[package]] -name = "ts-rs-macros" -version = "6.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f807fdb3151fee75df7485b901a89624358cd07a67a8fb1a5831bf5a07681ff" -dependencies = [ - "Inflector", - "proc-macro2", - "quote", - "syn 1.0.109", - "termcolor", -] - -[[package]] -name = "tungstenite" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fe8dada8c1a3aeca77d6b51a4f1314e0f4b8e438b7b1b71e3ddaca8080e4093" -dependencies = [ - "base64 0.13.1", - "byteorder", - "bytes", - "http", - "httparse", - "input_buffer", - "log", - "rand 0.8.5", - "sha-1", - "thiserror", - "url", - "utf-8", -] - -[[package]] -name = "typenum" -version = "1.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba" - -[[package]] -name = "ucd-trie" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e79c4d996edb816c91e4308506774452e55e95c3c9de07b6729e17e15a5ef81" - -[[package]] -name = "unicode-bidi" -version = "0.3.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460" - -[[package]] -name = "unicode-ident" -version = "1.0.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5464a87b239f13a63a501f2701565754bae92d243d4bb7eb12f6d57d2269bf4" - -[[package]] -name = "unicode-normalization" -version = "0.1.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921" -dependencies = [ - "tinyvec", -] - -[[package]] -name = "unicode-segmentation" -version = "1.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36" - -[[package]] -name = "unicode-xid" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c" - -[[package]] -name = "unicode_categories" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39ec24b3121d976906ece63c9daad25b85969647682eee313cb5779fdd69e14e" - -[[package]] -name = "universal-hash" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d3160b73c9a19f7e2939a2fdad446c57c1bbbbf4d919d3213ff1267a580d8b5" -dependencies = [ - "crypto-common", - "subtle 2.4.1", -] - -[[package]] -name = "untrusted" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" - -[[package]] -name = "url" -version = "2.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d68c799ae75762b8c3fe375feb6600ef5602c883c5d21eb51c09f22b83c4643" -dependencies = [ - "form_urlencoded", - "idna", - "percent-encoding", - "serde", -] - -[[package]] -name = "utf-8" -version = "0.7.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" - -[[package]] -name = "utf8parse" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" - -[[package]] -name = "uuid" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc5cf98d8186244414c848017f0e2676b3fcb46807f6668a97dfe67359a3c4b7" - -[[package]] -name = "uuid" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1674845326ee10d37ca60470760d4288a6f80f304007d92e5c53bab78c9cfd79" -dependencies = [ - "getrandom 0.2.10", -] - -[[package]] -name = "valuable" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" - -[[package]] -name = "vcpkg" -version = "0.2.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" - -[[package]] -name = "vergen" -version = "7.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "447f9238a4553957277b3ee09d80babeae0811f1b3baefb093de1c0448437a37" -dependencies = [ - "anyhow", - "cfg-if", - "enum-iterator", - "getset", - "git2", - "rustc_version", - "rustversion", - "thiserror", - "time", -] - -[[package]] -name = "version-compare" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "579a42fc0b8e0c63b76519a339be31bed574929511fa53c1a3acae26eb258f29" - -[[package]] -name = "version_check" -version = "0.9.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" - -[[package]] -name = "vte" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6cbce692ab4ca2f1f3047fcf732430249c0e971bfdd2b234cf2c47ad93af5983" -dependencies = [ - "arrayvec 0.5.2", - "utf8parse", - "vte_generate_state_changes", -] - -[[package]] -name = "vte_generate_state_changes" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d257817081c7dffcdbab24b9e62d2def62e2ff7d00b1c20062551e6cccc145ff" -dependencies = [ - "proc-macro2", - "quote", -] - -[[package]] -name = "walkdir" -version = "2.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36df944cda56c7d8d8b7496af378e6b16de9284591917d307c9b4d313c44e698" -dependencies = [ - "same-file", - "winapi-util", -] - -[[package]] -name = "want" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ce8a968cb1cd110d136ff8b819a556d6fb6d919363c61534f6860c7eb172ba0" -dependencies = [ - "log", - "try-lock", -] - -[[package]] -name = "wasi" -version = "0.9.0+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" - -[[package]] -name = "wasi" -version = "0.11.0+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" - -[[package]] -name = "wasm-bindgen" -version = "0.2.83" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eaf9f5aceeec8be17c128b2e93e031fb8a4d469bb9c4ae2d7dc1888b26887268" -dependencies = [ - "cfg-if", - "wasm-bindgen-macro", -] - -[[package]] -name = "wasm-bindgen-backend" -version = "0.2.83" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c8ffb332579b0557b52d268b91feab8df3615f265d5270fec2a8c95b17c1142" -dependencies = [ - "bumpalo", - "log", - "once_cell", - "proc-macro2", - "quote", - "syn 1.0.109", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-futures" -version = "0.4.33" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23639446165ca5a5de86ae1d8896b737ae80319560fbaa4c2887b7da6e7ebd7d" -dependencies = [ - "cfg-if", - "js-sys", - "wasm-bindgen", - "web-sys", -] - -[[package]] -name = "wasm-bindgen-macro" -version = "0.2.83" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "052be0f94026e6cbc75cdefc9bae13fd6052cdcaf532fa6c45e7ae33a1e6c810" -dependencies = [ - "quote", - "wasm-bindgen-macro-support", -] - -[[package]] -name = "wasm-bindgen-macro-support" -version = "0.2.83" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07bc0c051dc5f23e307b13285f9d75df86bfdf816c5721e573dec1f9b8aa193c" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.109", - "wasm-bindgen-backend", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-shared" -version = "0.2.83" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c38c045535d93ec4f0b4defec448e4291638ee608530863b1e2ba115d4fff7f" - -[[package]] -name = "wasm-utils" -version = "0.1.0" -dependencies = [ - "futures", - "getrandom 0.2.10", - "js-sys", - "tungstenite", - "wasm-bindgen", - "wasm-bindgen-futures", - "web-sys", -] - -[[package]] -name = "wasmtimer" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f656cd8858a5164932d8a90f936700860976ec21eb00e0fe2aa8cab13f6b4cf" -dependencies = [ - "futures", - "js-sys", - "parking_lot 0.12.1", - "pin-utils", - "slab", - "wasm-bindgen", -] - -[[package]] -name = "web-sys" -version = "0.3.60" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bcda906d8be16e728fd5adc5b729afad4e444e106ab28cd1c7256e54fa61510f" -dependencies = [ - "js-sys", - "wasm-bindgen", -] - -[[package]] -name = "webkit2gtk" -version = "0.19.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8eea819afe15eb8dcdff4f19d8bfda540bae84d874c10e6f4b8faf2d6704bd1" -dependencies = [ - "bitflags 1.3.2", - "cairo-rs", - "gdk", - "gdk-sys", - "gio", - "gio-sys", - "glib", - "glib-sys", - "gobject-sys", - "gtk", - "gtk-sys", - "javascriptcore-rs", - "libc", - "once_cell", - "soup3", - "webkit2gtk-sys", -] - -[[package]] -name = "webkit2gtk-sys" -version = "0.19.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0ac7a95ddd3fdfcaf83d8e513b4b1ad101b95b413b6aa6662ed95f284fc3d5b" -dependencies = [ - "bitflags 1.3.2", - "cairo-sys-rs", - "gdk-sys", - "gio-sys", - "glib-sys", - "gobject-sys", - "gtk-sys", - "javascriptcore-rs-sys", - "libc", - "pkg-config", - "soup3-sys", - "system-deps", -] - -[[package]] -name = "webpki" -version = "0.21.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8e38c0608262c46d4a56202ebabdeb094cef7e560ca7a226c6bf055188aa4ea" -dependencies = [ - "ring", - "untrusted", -] - -[[package]] -name = "webpki" -version = "0.22.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f095d78192e208183081cc07bc5515ef55216397af48b873e5edcd72637fa1bd" -dependencies = [ - "ring", - "untrusted", -] - -[[package]] -name = "webpki-roots" -version = "0.21.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aabe153544e473b775453675851ecc86863d2a81d786d741f6b76778f2a48940" -dependencies = [ - "webpki 0.21.4", -] - -[[package]] -name = "webpki-roots" -version = "0.22.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6c71e40d7d2c34a5106301fb632274ca37242cd0c9d3e64dbece371a40a2d87" -dependencies = [ - "webpki 0.22.0", -] - -[[package]] -name = "webview2-com" -version = "0.19.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4a769c9f1a64a8734bde70caafac2b96cada12cd4aefa49196b3a386b8b4178" -dependencies = [ - "webview2-com-macros", - "webview2-com-sys", - "windows 0.39.0", - "windows-implement 0.39.0", -] - -[[package]] -name = "webview2-com-macros" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eaebe196c01691db62e9e4ca52c5ef1e4fd837dcae27dae3ada599b5a8fd05ac" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.109", -] - -[[package]] -name = "webview2-com-sys" -version = "0.19.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aac48ef20ddf657755fdcda8dfed2a7b4fc7e4581acce6fe9b88c3d64f29dee7" -dependencies = [ - "regex", - "serde", - "serde_json", - "thiserror", - "windows 0.39.0", - "windows-bindgen", - "windows-metadata", -] - -[[package]] -name = "winapi" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" -dependencies = [ - "winapi-i686-pc-windows-gnu", - "winapi-x86_64-pc-windows-gnu", -] - -[[package]] -name = "winapi-i686-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" - -[[package]] -name = "winapi-util" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" -dependencies = [ - "winapi", -] - -[[package]] -name = "winapi-x86_64-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" - -[[package]] -name = "windows" -version = "0.39.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1c4bd0a50ac6020f65184721f758dba47bb9fbc2133df715ec74a237b26794a" -dependencies = [ - "windows-implement 0.39.0", - "windows_aarch64_msvc 0.39.0", - "windows_i686_gnu 0.39.0", - "windows_i686_msvc 0.39.0", - "windows_x86_64_gnu 0.39.0", - "windows_x86_64_msvc 0.39.0", -] - -[[package]] -name = "windows" -version = "0.44.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e745dab35a0c4c77aa3ce42d595e13d2003d6902d6b08c9ef5fc326d08da12b" -dependencies = [ - "windows-implement 0.44.0", - "windows-interface", - "windows-targets 0.42.2", -] - -[[package]] -name = "windows-bindgen" -version = "0.39.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68003dbd0e38abc0fb85b939240f4bce37c43a5981d3df37ccbaaa981b47cb41" -dependencies = [ - "windows-metadata", - "windows-tokens", -] - -[[package]] -name = "windows-implement" -version = "0.39.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba01f98f509cb5dc05f4e5fc95e535f78260f15fea8fe1a8abdd08f774f1cee7" -dependencies = [ - "syn 1.0.109", - "windows-tokens", -] - -[[package]] -name = "windows-implement" -version = "0.44.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ce87ca8e3417b02dc2a8a22769306658670ec92d78f1bd420d6310a67c245c6" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.109", -] - -[[package]] -name = "windows-interface" -version = "0.44.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "853f69a591ecd4f810d29f17e902d40e349fb05b0b11fff63b08b826bfe39c7f" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.109", -] - -[[package]] -name = "windows-metadata" -version = "0.39.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ee5e275231f07c6e240d14f34e1b635bf1faa1c76c57cfd59a5cdb9848e4278" - -[[package]] -name = "windows-sys" -version = "0.42.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" -dependencies = [ - "windows_aarch64_gnullvm 0.42.2", - "windows_aarch64_msvc 0.42.2", - "windows_i686_gnu 0.42.2", - "windows_i686_msvc 0.42.2", - "windows_x86_64_gnu 0.42.2", - "windows_x86_64_gnullvm 0.42.2", - "windows_x86_64_msvc 0.42.2", -] - -[[package]] -name = "windows-sys" -version = "0.45.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" -dependencies = [ - "windows-targets 0.42.2", -] - -[[package]] -name = "windows-sys" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" -dependencies = [ - "windows-targets 0.48.1", -] - -[[package]] -name = "windows-targets" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" -dependencies = [ - "windows_aarch64_gnullvm 0.42.2", - "windows_aarch64_msvc 0.42.2", - "windows_i686_gnu 0.42.2", - "windows_i686_msvc 0.42.2", - "windows_x86_64_gnu 0.42.2", - "windows_x86_64_gnullvm 0.42.2", - "windows_x86_64_msvc 0.42.2", -] - -[[package]] -name = "windows-targets" -version = "0.48.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05d4b17490f70499f20b9e791dcf6a299785ce8af4d709018206dc5b4953e95f" -dependencies = [ - "windows_aarch64_gnullvm 0.48.0", - "windows_aarch64_msvc 0.48.0", - "windows_i686_gnu 0.48.0", - "windows_i686_msvc 0.48.0", - "windows_x86_64_gnu 0.48.0", - "windows_x86_64_gnullvm 0.48.0", - "windows_x86_64_msvc 0.48.0", -] - -[[package]] -name = "windows-tokens" -version = "0.39.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f838de2fe15fe6bac988e74b798f26499a8b21a9d97edec321e79b28d1d7f597" - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.39.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec7711666096bd4096ffa835238905bb33fb87267910e154b18b44eaabb340f2" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3" - -[[package]] -name = "windows_i686_gnu" -version = "0.39.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "763fc57100a5f7042e3057e7e8d9bdd7860d330070251a73d003563a3bb49e1b" - -[[package]] -name = "windows_i686_gnu" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" - -[[package]] -name = "windows_i686_gnu" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241" - -[[package]] -name = "windows_i686_msvc" -version = "0.39.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7bc7cbfe58828921e10a9f446fcaaf649204dcfe6c1ddd712c5eebae6bda1106" - -[[package]] -name = "windows_i686_msvc" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" - -[[package]] -name = "windows_i686_msvc" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.39.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6868c165637d653ae1e8dc4d82c25d4f97dd6605eaa8d784b5c6e0ab2a252b65" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.39.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e4d40883ae9cae962787ca76ba76390ffa29214667a111db9e0a1ad8377e809" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" - -[[package]] -name = "winnow" -version = "0.5.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5504cc7644f4b593cbc05c4a55bf9bd4e94b867c3c0bd440934174d50482427d" -dependencies = [ - "memchr", -] - -[[package]] -name = "winreg" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80d0f4e272c85def139476380b12f9ac60926689dd2e01d4923222f40580869d" -dependencies = [ - "winapi", -] - -[[package]] -name = "wry" -version = "0.26.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f8052d7e38efb90235f1895d43904fc152e1efd96383fe57e30ea596738fa3b" -dependencies = [ - "base64 0.13.1", - "block", - "cocoa", - "core-graphics", - "crossbeam-channel", - "dunce", - "gdk", - "gio", - "glib", - "gtk", - "html5ever", - "http", - "kuchiki", - "libc", - "log", - "objc", - "objc_id", - "once_cell", - "serde", - "serde_json", - "sha2 0.10.6", - "soup3", - "tao", - "thiserror", - "url", - "webkit2gtk", - "webkit2gtk-sys", - "webview2-com", - "windows 0.39.0", - "windows-implement 0.39.0", -] - -[[package]] -name = "wyz" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed" -dependencies = [ - "tap", -] - -[[package]] -name = "x11" -version = "2.21.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "502da5464ccd04011667b11c435cb992822c2c0dbde1770c988480d312a0db2e" -dependencies = [ - "libc", - "pkg-config", -] - -[[package]] -name = "x11-dl" -version = "2.21.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38735924fedd5314a6e548792904ed8c6de6636285cb9fec04d5b1db85c1516f" -dependencies = [ - "libc", - "once_cell", - "pkg-config", -] - -[[package]] -name = "x25519-dalek" -version = "1.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a0c105152107e3b96f6a00a65e86ce82d9b125230e1c4302940eca58ff71f4f" -dependencies = [ - "curve25519-dalek", - "rand_core 0.5.1", - "zeroize", -] - -[[package]] -name = "xattr" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d1526bbe5aaeb5eb06885f4d987bcdfa5e23187055de9b83fe00156a821fabc" -dependencies = [ - "libc", -] - -[[package]] -name = "yaml-rust" -version = "0.4.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56c1936c4cc7a1c9ab21a1ebb602eb942ba868cbd44a99cb7cdc5892335e1c85" -dependencies = [ - "linked-hash-map", -] - -[[package]] -name = "zeroize" -version = "1.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a0956f1ba7c7909bfb66c2e9e4124ab6f6482560f6628b5aaeba39207c9aad9" -dependencies = [ - "zeroize_derive", -] - -[[package]] -name = "zeroize_derive" -version = "1.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44bf07cb3e50ea2003396695d58bf46bc9887a1f362260446fad6bc4e79bd36c" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.109", - "synstructure", -] - -[[patch.unused]] -name = "tauri-mobile" -version = "0.2.5" -source = "git+https://github.com/tauri-apps/tauri-mobile?branch=dev#f9791c2d908ab49bdf44907c53b394c2560f3306" diff --git a/nym-connect/mobile/src-tauri/Cargo.toml b/nym-connect/mobile/src-tauri/Cargo.toml deleted file mode 100644 index 2056047897..0000000000 --- a/nym-connect/mobile/src-tauri/Cargo.toml +++ /dev/null @@ -1,79 +0,0 @@ -[package] -name = "nym-connect" -version = "1.1.12" -description = "nym-connect mobile" -authors = ["Nym Technologies SA"] -license = "" -repository = "" -default-run = "nym-connect" -edition = "2021" -build = "src/build.rs" -rust-version = "1.58" - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[lib] -crate-type = ["staticlib", "cdylib", "rlib"] - -[build-dependencies] -tauri-build = { version = "2.0.0-alpha.1", features = [] } -# tauri-build = { git = "https://github.com/tauri-apps/tauri", branch = "next", features = [] } - -# TODO untill new tauri version includes https://github.com/tauri-apps/tauri-mobile/pull/111 -[patch.crates-io] -tauri-mobile = { git = "https://github.com/tauri-apps/tauri-mobile", branch = "dev" } - -[dependencies] -anyhow = "1.0" -bip39 = { version = "2.0.0", features = ["zeroize"] } -dirs = "4.0" -eyre = "0.6.5" -fix-path-env = { git = "https://github.com/tauri-apps/fix-path-env-rs", branch = "release"} -futures = "0.3" -fern = { version = "0.6.1", features = ["colored"] } -itertools = "0.10.5" -log = { version = "0.4", features = ["serde"] } -pretty_env_logger = "0.4.0" -rand = "0.8" -reqwest = { version = "0.11.18", features = ["json", "socks"] } -rust-embed = { version = "6.4.2", features = ["include-exclude"] } -serde = { version = "1.0", features = ["derive"] } -serde_json = "1.0" -serde_repr = "0.1" -tap = "1.0.1" -# tauri = { git = "https://github.com/tauri-apps/tauri", branch = "next", features = ["clipboard-write-text", "native-tls-vendored", "notification-all", "shell-open"] } -tauri = { version = "2.0.0-alpha.3", features = ["clipboard-write-text", "native-tls-vendored", "notification-all", "shell-open"] } -#tendermint-rpc = "0.23.0" -thiserror = "1.0" -time = { version = "0.3.17", features = ["local-offset"] } -tokio = { version = "1.24.1", features = ["sync", "time"] } -url = "2.2" -yaml-rust = "0.4" - -nym-client-core = { path = "../../../common/client-core", features = [], default-features = false } -nym-api-requests = { path = "../../../nym-api/nym-api-requests" } -nym-contracts-common = { path = "../../../common/cosmwasm-smart-contracts/contracts-common"} -nym-config-common = { path = "../../../common/config", package = "nym-config" } -nym-credential-storage = { path = "../../../common/credential-storage" } -nym-crypto = { path = "../../../common/crypto" } -nym-bin-common = { path = "../../../common/bin-common"} -nym-socks5-client-core = { path = "../../../common/socks5-client-core", default-features = false } -nym-task = { path = "../../../common/task" } - -[dev-dependencies] -ts-rs = "6.1.2" -tempfile = "3.3.0" - -[features] -default = ["custom-protocol"] -custom-protocol = ["tauri/custom-protocol"] - -# [profile.dev] -# strip = true -# opt-level = "s" -# lto = true - -[profile.release] -strip = true -opt-level = "s" -lto = true diff --git a/nym-connect/mobile/src-tauri/gen/android/nym_connect/.editorconfig b/nym-connect/mobile/src-tauri/gen/android/nym_connect/.editorconfig deleted file mode 100644 index ebe51d3bfa..0000000000 --- a/nym-connect/mobile/src-tauri/gen/android/nym_connect/.editorconfig +++ /dev/null @@ -1,12 +0,0 @@ -# EditorConfig is awesome: https://EditorConfig.org - -# top-most EditorConfig file -root = true - -[*] -indent_style = space -indent_size = 2 -end_of_line = lf -charset = utf-8 -trim_trailing_whitespace = false -insert_final_newline = false \ No newline at end of file diff --git a/nym-connect/mobile/src-tauri/gen/android/nym_connect/.gitignore b/nym-connect/mobile/src-tauri/gen/android/nym_connect/.gitignore deleted file mode 100644 index edf9888947..0000000000 --- a/nym-connect/mobile/src-tauri/gen/android/nym_connect/.gitignore +++ /dev/null @@ -1,16 +0,0 @@ -*.iml -.gradle -/local.properties -/.idea/caches -/.idea/libraries -/.idea/modules.xml -/.idea/workspace.xml -/.idea/navEditor.xml -/.idea/assetWizardSettings.xml -.DS_Store -build -/captures -.externalNativeBuild -.cxx -local.properties -app/src/main/jniLibs/** diff --git a/nym-connect/mobile/src-tauri/gen/android/nym_connect/app/.gitignore b/nym-connect/mobile/src-tauri/gen/android/nym_connect/app/.gitignore deleted file mode 100644 index 1b13319aad..0000000000 --- a/nym-connect/mobile/src-tauri/gen/android/nym_connect/app/.gitignore +++ /dev/null @@ -1 +0,0 @@ -/src/main/java/net/nymtech/nym_connect/generated \ No newline at end of file diff --git a/nym-connect/mobile/src-tauri/gen/android/nym_connect/app/build.gradle.kts b/nym-connect/mobile/src-tauri/gen/android/nym_connect/app/build.gradle.kts deleted file mode 100644 index 3b092d967a..0000000000 --- a/nym-connect/mobile/src-tauri/gen/android/nym_connect/app/build.gradle.kts +++ /dev/null @@ -1,112 +0,0 @@ -plugins { - id("com.android.application") - id("org.jetbrains.kotlin.android") - id("rustPlugin") -} - -android { - compileSdk = 33 - defaultConfig { - manifestPlaceholders["usesCleartextTraffic"] = "false" - applicationId = "net.nymtech.nym_connect" - minSdk = 24 - targetSdk = 33 - versionCode = 1 - versionName = "1.1.9" - } - sourceSets.getByName("main") { - // Vulkan validation layers - val ndkHome = System.getenv("NDK_HOME") - jniLibs.srcDir("${ndkHome}/sources/third_party/vulkan/src/build-android/jniLibs") - } - buildTypes { - getByName("debug") { - manifestPlaceholders["usesCleartextTraffic"] = "true" - isDebuggable = true - isJniDebuggable = true - isMinifyEnabled = false - packagingOptions { - jniLibs.keepDebugSymbols.add("*/arm64-v8a/*.so") - - jniLibs.keepDebugSymbols.add("*/armeabi-v7a/*.so") - - jniLibs.keepDebugSymbols.add("*/x86/*.so") - - jniLibs.keepDebugSymbols.add("*/x86_64/*.so") - } - } - getByName("release") { - isMinifyEnabled = false - proguardFiles(getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro") - } - } - flavorDimensions.add("abi") - productFlavors { - create("universal") { - val abiList = findProperty("abiList") as? String - - dimension = "abi" - ndk { - abiFilters += abiList?.split(",")?.map { it.trim() } ?: listOf( "arm64-v8a", "armeabi-v7a", "x86", "x86_64", - ) - } - } - create("arm64") { - dimension = "abi" - ndk { - abiFilters += listOf("arm64-v8a") - } - } - - create("arm") { - dimension = "abi" - ndk { - abiFilters += listOf("armeabi-v7a") - } - } - - create("x86") { - dimension = "abi" - ndk { - abiFilters += listOf("x86") - } - } - - create("x86_64") { - dimension = "abi" - ndk { - abiFilters += listOf("x86_64") - } - } - } - - assetPacks += mutableSetOf() - namespace = "net.nymtech.nym_connect" -} - -rust { - rootDirRel = "../../../../" - targets = listOf("aarch64", "armv7", "i686", "x86_64") - arches = listOf("arm64", "arm", "x86", "x86_64") -} - -dependencies { - implementation("androidx.webkit:webkit:1.5.0") - implementation("androidx.appcompat:appcompat:1.5.1") - implementation("com.google.android.material:material:1.7.0") - implementation("androidx.core:core-splashscreen:1.0.0") - testImplementation("junit:junit:4.13.2") - androidTestImplementation("androidx.test.ext:junit:1.1.4") - androidTestImplementation("androidx.test.espresso:espresso-core:3.5.0") -} - -afterEvaluate { - android.applicationVariants.all { - tasks["mergeUniversalReleaseJniLibFolders"].dependsOn(tasks["rustBuildRelease"]) - tasks["mergeUniversalDebugJniLibFolders"].dependsOn(tasks["rustBuildDebug"]) - productFlavors.filter{ it.name != "universal" }.forEach { _ -> - val archAndBuildType = name.capitalize() - tasks["merge${archAndBuildType}JniLibFolders"].dependsOn(tasks["rustBuild${archAndBuildType}"]) - } - } -} diff --git a/nym-connect/mobile/src-tauri/gen/android/nym_connect/app/proguard-rules.pro b/nym-connect/mobile/src-tauri/gen/android/nym_connect/app/proguard-rules.pro deleted file mode 100644 index 481bb43481..0000000000 --- a/nym-connect/mobile/src-tauri/gen/android/nym_connect/app/proguard-rules.pro +++ /dev/null @@ -1,21 +0,0 @@ -# Add project specific ProGuard rules here. -# You can control the set of applied configuration files using the -# proguardFiles setting in build.gradle. -# -# For more details, see -# http://developer.android.com/guide/developing/tools/proguard.html - -# If your project uses WebView with JS, uncomment the following -# and specify the fully qualified class name to the JavaScript interface -# class: -#-keepclassmembers class fqcn.of.javascript.interface.for.webview { -# public *; -#} - -# Uncomment this to preserve the line number information for -# debugging stack traces. -#-keepattributes SourceFile,LineNumberTable - -# If you keep the line number information, uncomment this to -# hide the original source file name. -#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/nym-connect/mobile/src-tauri/gen/android/nym_connect/app/src/main/AndroidManifest.xml b/nym-connect/mobile/src-tauri/gen/android/nym_connect/app/src/main/AndroidManifest.xml deleted file mode 100644 index 1c575ab17a..0000000000 --- a/nym-connect/mobile/src-tauri/gen/android/nym_connect/app/src/main/AndroidManifest.xml +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - - - - - - - - - - diff --git a/nym-connect/mobile/src-tauri/gen/android/nym_connect/app/src/main/java/net/nymtech/nym_connect/MainActivity.kt b/nym-connect/mobile/src-tauri/gen/android/nym_connect/app/src/main/java/net/nymtech/nym_connect/MainActivity.kt deleted file mode 100644 index 1162a2e43b..0000000000 --- a/nym-connect/mobile/src-tauri/gen/android/nym_connect/app/src/main/java/net/nymtech/nym_connect/MainActivity.kt +++ /dev/null @@ -1,3 +0,0 @@ -package net.nymtech.nym_connect - -class MainActivity : TauriActivity() diff --git a/nym-connect/mobile/src-tauri/gen/android/nym_connect/app/src/main/java/net/nymtech/nym_connect/generated/.gitkeep b/nym-connect/mobile/src-tauri/gen/android/nym_connect/app/src/main/java/net/nymtech/nym_connect/generated/.gitkeep deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/nym-connect/mobile/src-tauri/gen/android/nym_connect/app/src/main/res/drawable-v24/ic_launcher_foreground.xml b/nym-connect/mobile/src-tauri/gen/android/nym_connect/app/src/main/res/drawable-v24/ic_launcher_foreground.xml deleted file mode 100644 index 2b068d1146..0000000000 --- a/nym-connect/mobile/src-tauri/gen/android/nym_connect/app/src/main/res/drawable-v24/ic_launcher_foreground.xml +++ /dev/null @@ -1,30 +0,0 @@ - - - - - - - - - - - \ No newline at end of file diff --git a/nym-connect/mobile/src-tauri/gen/android/nym_connect/app/src/main/res/drawable/ic_launcher_background.xml b/nym-connect/mobile/src-tauri/gen/android/nym_connect/app/src/main/res/drawable/ic_launcher_background.xml deleted file mode 100644 index 07d5da9cbf..0000000000 --- a/nym-connect/mobile/src-tauri/gen/android/nym_connect/app/src/main/res/drawable/ic_launcher_background.xml +++ /dev/null @@ -1,170 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/nym-connect/mobile/src-tauri/gen/android/nym_connect/app/src/main/res/layout/activity_main.xml b/nym-connect/mobile/src-tauri/gen/android/nym_connect/app/src/main/res/layout/activity_main.xml deleted file mode 100644 index 4fc244418b..0000000000 --- a/nym-connect/mobile/src-tauri/gen/android/nym_connect/app/src/main/res/layout/activity_main.xml +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/nym-connect/mobile/src-tauri/gen/android/nym_connect/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/nym-connect/mobile/src-tauri/gen/android/nym_connect/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml deleted file mode 100644 index 036d09bc5f..0000000000 --- a/nym-connect/mobile/src-tauri/gen/android/nym_connect/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - - \ No newline at end of file diff --git a/nym-connect/mobile/src-tauri/gen/android/nym_connect/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/nym-connect/mobile/src-tauri/gen/android/nym_connect/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml deleted file mode 100644 index 036d09bc5f..0000000000 --- a/nym-connect/mobile/src-tauri/gen/android/nym_connect/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - - \ No newline at end of file diff --git a/nym-connect/mobile/src-tauri/gen/android/nym_connect/app/src/main/res/mipmap-hdpi/ic_launcher.png b/nym-connect/mobile/src-tauri/gen/android/nym_connect/app/src/main/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index 4974445c9ab3aea5d84fadbc81b783135062e19e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3357 zcmV+&4dU{NP)YpZPR!9Oqw=rO5g7nw3HT*RaR77S638K1l--( zT@e+0j4#B;y0SVesH^TnL>?=SEyL;xIzSQn2CMVm^WDSK_T6}J+#0QBb*b(Exy~YewYppP-biDHg z3irp6hhd&nS~50f-^%%3o*7<8C%afdGNlG~y$KtVZ8(_c#_7T`^t&rCSWtyQ7bCYC z1365Ny%q!6jLbR=q}OA>)_{T32J|O4qC2q>Us>z$YE&WGwaG9lBY4;G2a1UV&6mg@ zq*wzHbfs9T`s2vSJTKr2VJZ!3qAmD~!;a%6#TY0p!(eeaPPxnQfxQ6F*z8ynXTxHX z6-9ar?Aj<~YE8)1no$)IhZ_wk_`NwBJ7e7FuvVcrz6t$t3vniPAwDtJ;9gx85>!Us zb$x-0i84qmdz4}gM9@`Ysq#;wC-ZWViI6c|MiX9kW}&OR2!oZSI8#=FzdQ4AM|?7} zBTO)=bx^R(@;ufPDMPIchczscYi8SwdFYI;L7$}=z2;^dGS=eP;TbUanNb%6NgtzF z0~DlqX!K-WF5(TnT8n#ZN%&ty0nXMGbAl*?s>~KdglT#8{e|QwhZ%9FE(7~b)#!<8 zLAS95yLII#RK;>aJ&*Cm7(rVp)_??wq>9a>C-ZUka6F#fU!`VfJ zI8v2|x)=)^x%hR13H3pbYVy&oU4p*wHax0wL#vqhwqV)>IZOt8R-cQr&4oBr=|p;@ zfmdg`P_S(JjXE2>;Y^ac9h;Q}P*0Fa(;_HYugAx=IXJi2jYHK=#A^K;NrC}o^lDWm z&ZyhaqiV;!iX2XyXT6{c(rdKXS(=V>OWZiZGAP3k$@>xC7jhvlGzY=EBKl*5|}U^M1hhjfOR??aC7LDwg7)tO+)`dEEY>3UWmo@kO(f z3ChO<*@@HMA^S>_!i2qw2K367;Yn#Rlw*efpb1j3>BB1}X*jnmAMaIV!5Hqn6a1mj z%9_^+SsT6zZ9}yzX>@zg1XaaF;nb2mj=K2qY5PNjMzI%VW#|oEj=zOgkKRNEMUb4$ zonE7knPtzbrD@z`)bj^wLSV zD3Zr>8FWIj6cw^WZd*_UIid_W*_w-USPK40CJ(udX-3Sk}BOEl02TdORm7GlUnEZj3}XyYD&8I_GSYZ*6}BZ-REb_!9Q+{fM`H z?YjFR^;jnoLJ&=AACWFVFB9~fxQttJ6;#j-wpa`<&BvbFY`=$gZ-TZx{v_^Ozk#=X z-@g6OM2uf06haWuBC~OVI_I~7doUoBAhNy1mBKZzXBXJG(aiJWli^L!*2kZK)8$52 z*Z9DxnYrA>h0VNXcJ$aWB&DWv`-Kuj>qJjUN;pBA z=I0A3NIlfkHk?6uX8#}5O(lr>n~u)IGo>f}+`c>O1GpEogcM}ZXmNtwEYgIPWO-hEGJF+8y!ZY;cy<5!`WqK5 zh&t%kLMu5znNmnlHre_n{Hms>#PR`1~51{cu|7rvCmOS8@?7T zN0BT(ID(d@#GvndGfRoo^M1rc_#?<16VIFBd&6Hr_sE>wlIsESLhgPjD1vAmsDn*f zsk2^_<@>Cj7T77Ev8>;?iC1rg$F@DetMAFKx88;^I*xa&H!KPRK9n|cORj4|bGU6m z5k#zaBy#=Gp+*PdeeA61W{>{bW1%M%p7lo1Mn>`KdqQ$#KE8K!t2_(cq07)IS&Vr3 z@MJq^f|B%lE{jOgMn{rgebM(pvciM|vL;T@25H{t_MizO?##4uL;lf4PPn7}pPNk= z)Ym+saHB`I3Pub6V%^Q zfHmm}e$UZ@g+&>OkHYHFr&@|Vih5WT{B9uVf(Uxk<9Ci^oL%h3V6z*y+2Z|v;vqCK zOwPa6wcIOshgFNAyqOD>WR($Hw0StLUxeO>R_tLJ zddI7$S ztw(<>YfhF$IAW^DCLNm-`j|Cixgbb>F&U)XGjsn%50%CjLU29skWO zMt^=K_r>AB6ki;kPO8U0V@vU%AsfY-ST4gn&+RYVCz&!8uAMV)3B?*>v0Uw`VX|@s z<4277B8hx2rJ|4@*V4J@X{}YGA1XJ*46Te#8oE0sZD?a`=Fo3rvxe@sWDebKvJJIH zCJZ^l%|kk+mi|Dz^90I;m#0Gavlu1kW2Kn=Ds<57AJ3X)nZ@6nLT@U$Tino7cy(soJ$&tfnuSU nf>6lz<-kmtDKllJToUE~$TRiq^+!Cv00000NkvXXu0mjfz=U9} diff --git a/nym-connect/mobile/src-tauri/gen/android/nym_connect/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png b/nym-connect/mobile/src-tauri/gen/android/nym_connect/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png deleted file mode 100644 index 483ace7edf07a44107647556acaa9b456d19d60b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5427 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 diff --git a/nym-connect/mobile/src-tauri/gen/android/nym_connect/app/src/main/res/mipmap-hdpi/ic_launcher_round.png b/nym-connect/mobile/src-tauri/gen/android/nym_connect/app/src/main/res/mipmap-hdpi/ic_launcher_round.png deleted file mode 100644 index 59b2f1db44735d7ab131ea026b6094a06f2d760f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5381 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 diff --git a/nym-connect/mobile/src-tauri/gen/android/nym_connect/app/src/main/res/mipmap-mdpi/ic_launcher.png b/nym-connect/mobile/src-tauri/gen/android/nym_connect/app/src/main/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index 307fb6aab9ad92c9226bcee44ed2b92183b46e86..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1757 zcmV<31|s>1P)iQE+SBX0u}jxo+FfjSZ^z1tWrJ-%rt5%cH2xtFHb6z=4}&Nd zO^oq}vT;$s2*h82Ccri~6QUrOaod0mCIE}l81z#IhF^FB-$i;53hP$bg4LQs^HP$(o*0;X-uAr!aq%v*pU zh}jZ3y+cCMw;0@=V%)-9Fefg@@ywgaRM^E-gy{saOz{;%?KwZkQ0AR11ffb2lk~8! z;(rpk5=8=T41Ig*qlTfpTSXk#-zK`a*25>ok(SHPG9UT<)btI@;e0yFehJ7Boh1y`%< z;H1?7`z#f(JJ$xE8LhB8yA<~3RKp3=Vi?F>2DePj(380oI<&7rj>5q682}##v0QO) zeoBgt%Qet^iAf@nN+MT`aw~^|MyZDN<~%r8=YS!X6AqS^fhWrZW{m-KN)5otbjk$8eyHP1T>_E*G32ddM?*M;|d7#2BZuM^{Mc)!v=Sk z)xy4VE7YVd1O*jHcp@lBHQ4Da=+Z5MVM7c2q;WwZmG1Q)P?jU$<>cg4F4sVdKmc}L zlaUT*msY|+y#qWsMxdf39t?^R(n?$4qIM_e$dQ^>YPPqykBu7NN1Qey};cR0C^w*a|zE0005D(;0Y0#r=f#kc2!VWl$RBH03m|b023Po%aHd5e;-6eCuSxE~RWvQ_V*lf*(q1GyJWTx>N@}qV2 zTKM{#Z+Y1I@h7~p8#Z>Z`M6$iz8?;`5w1x*&@QbIE`V03P+wCi9C8(h?R`uJA>_s5 z$B)_H4<0;#d`oda+2bcpviZ1PurELGK5;`+M7|-JVW1?fl;tK$n&owE&DA;#zberA?TC!PaWDy|RGk zG#J4GaGJZJF0s!Z2FBGgOHPFiAsQX4v1bCJ_p!Uh>mU*@~2zNs^H?LUv-V0F3rcTiD4w zGBN@~Lqib@hUYtU`y5_HAxLciK- zP**`E+Lcp^wq@DS&a86OnQccWjdkd9&T@3q*o=D9m!fu!6&Wc#&u0MqA2_7SyYuEH zQ$DUC1z^LUZ)Oo9vP&#i@_Qo;RLqsp$=m@pnJlosr~-PdP8hJcVbIzD14Z@Fo$rEu z*>>nKlz>y64JvA!9uEP`C7F;NV7R^L<2bJ2%o&M^iTY$hdXSJ(qhdMruUJOi^FAjN zzBWZt#ptUkl0yt9Jbdsd2`c~_E`XJivu4e)&Ye5gF?))D+h#b}Fc%-E3Ca5n7ffU@ zhrvrz49o#@iI_asW+r51y)dN+Ghv_Ebg=&c%E(`W=MtoJ00000NkvXXu0mjfZ~8Aj diff --git a/nym-connect/mobile/src-tauri/gen/android/nym_connect/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png b/nym-connect/mobile/src-tauri/gen/android/nym_connect/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png deleted file mode 100644 index e634763b4228461b0c4c5c494d37e6022ffec34e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2753 zcmaJ@c{mhm7snLFP!eV`$di2;+)TFYSxXqpFt%&5HJYr^GBl*2Fk=}aTf(@)Fp?#V zVPq#U$)L@Z5F!d=>r?lBe}3N|=RN0npR+x`-+9h+?mO672?D6ZE@T7jCbZz@ful0_`q0dbKX zn~#W$m>#2q0UfeWzx-zII{78&!yI&BH@rG^*l_c#g#}NXiHtD+q5q+LcDOApYLuy7 ziOz`w)JdFr<=orruWR0tgPc~L-D+@kY+JgZOawdjgTZpMDS?)p`oQy;c}5A`vsDfM zW(8@7t9X2QDe{(AhXRlEupOnY#Rg-H_quxB5aH31?QEq}r0I}X8#BvGc>>O?W4A2< zM0u429Uuqa*uA3s?v2XiqWm>=WktGurafP^>LWpT+)y2PA9qKnxT(O!-E~~7zVeD? zWFcQYz#|Xj0IA}SP(-i|NVLSYiT6AGA=_V-iG{c?;KC3DTUJy%m6?G;uPJNvD$-)~ zxz-ffq(5_ANITU^CvR5HGc?>3gHr2|iwn)v$mtZx8IfbEU-WB<@HCXZWL>G=+bm~V59R& z>SJ1uC#3hJ;GJkWP1!Ntd^mmzU^?e9+0_t6Wf&5ev7o#QO$C3fo>?j8Saw)Sk*ac{ zuZ5C;WYQrMBT4JN`g1d(x?AEyA0G4qhO#D2cH{(p@qx8jyu#q7Q@h~TE8PrE)4+sx z8H+s13!z-4oAEMq9k?<>$I~XFQOvy^@u=ywK2@lwDut#u#zN4$> z+6Gd19Z&o9hQmPoA`lCjq#&(9>kWnut#+)7m06QMSU}7?8GzoG`Y`n^_i>9S#M-43G!;#XG zWqCeT;90Nyd(H`;b2JxxbmgV)U3(>Y0f3k*G-n&dGr0(q4DJ?8*;nY{kVLyDr&~ph zA-oIiy-P=DN0_MY^9jqY2#6+nGIRhY(Ws)^9Bh38Evx~3ZaMS7Tq?NQ6yGp)6+1^86+Q}YgD$%fG62MeF`3|oLNp6 zkbhz;o`e&W_IRIa!Br$aL~UpjgZtE{KQ}_ADEnN8CY~lZRKpAs^W=YOg~9|BmQCcIB3y)vQqM} zN78=!I_{Q~A+3)I(Y+u!%-@L=xl$a)ig)5H4m5Ti!^skBz;Et9bJhFV9rftI`Op;f zyF03#3*8@=-tCX3j4Z-~gM6?{kuX1e#<2t^j*zYPQ<_~Vy&qj5U50-hmf)=lg2<%2 zth1cQbDmy4cPVA9o-A6Asw2b`r0qi^qLqO_`rEE(1=YWyDgw%feW97>uu>Xd8a*tXKag?i1KiZn!D& zX*bI|m75pnQMJ{4$jU7Pn7l%K_xK>7xU7kG6H>Qa^ex(_z#5lD7VnPOH;0Hblu*h4_AsDJP)?Q&Q<7yep}VM*pf@rQ5;!K%pEn6)vsjioU#Z&gu6(ZnRicgxR^4<-3*ui{sjgP^D-PxTjS$sLV-!$jB z6^4fH)RN&SD^Wq=ZfT$M{^^0LM>S!{CVx-^f2%cQ=i-L_Hup34;I16VoYDzOxCC0NdXzA?xb*PNaUrEMUWzlT(S8uoiM&W(_a#PDUbbBJzF_tIShFc@AsQbQ5|=_O-#2 zWaHJqB>1?05`1PeTWK^O6m|B6c8NxOb>wv;?Pfv!s!UUSbA{k7uTW%#kT{akFr*Ok zs+~ZpRI>GDI^TX38^ksRwEZ-`{BgcW<%mS@@v$-Qk{{RJMo%E5pLSmL@Uyzfeseo4 zjea@XsDGwzKVb`jM2 zqtf!bF;%{{A2h&+vT*6Gjpu<9go^=3qSrl<_FhYfQNA`*`Re`ucjcMLunqS4TE0W} Vq;%h-4o*`p8w)%0YBQfZ{{dDXCT9Qu diff --git a/nym-connect/mobile/src-tauri/gen/android/nym_connect/app/src/main/res/mipmap-mdpi/ic_launcher_round.png b/nym-connect/mobile/src-tauri/gen/android/nym_connect/app/src/main/res/mipmap-mdpi/ic_launcher_round.png deleted file mode 100644 index 37a8d502c816c311a411b1fea2d11c5fcf7403b2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2775 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 Ye`O;&88BhdfB*mh07*qoM6N<$g6^2_>;M1& diff --git a/nym-connect/mobile/src-tauri/gen/android/nym_connect/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png b/nym-connect/mobile/src-tauri/gen/android/nym_connect/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png deleted file mode 100644 index 580c767c542e32404ef013033b9a9a39643f7d94..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8196 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 HCI`uARI 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 Hu0mjfVx7ws diff --git a/nym-connect/mobile/src-tauri/gen/android/nym_connect/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/nym-connect/mobile/src-tauri/gen/android/nym_connect/app/src/main/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index 65cefcb1963b4062142389ea078ed10cd81f2347..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 7664 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 DA@h_v diff --git a/nym-connect/mobile/src-tauri/gen/android/nym_connect/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png b/nym-connect/mobile/src-tauri/gen/android/nym_connect/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png deleted file mode 100644 index 312d818bbe76c3c58c6947ecddabc906fe441175..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 15273 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 diff --git a/nym-connect/mobile/src-tauri/gen/android/nym_connect/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png b/nym-connect/mobile/src-tauri/gen/android/nym_connect/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png deleted file mode 100644 index 5070e583a0482cf1902201b3ea4c51c81440afe9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 12925 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 Zd9WUlq0l)&o_{L=s48l`s+YHp_3w diff --git a/nym-connect/mobile/src-tauri/gen/android/nym_connect/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/nym-connect/mobile/src-tauri/gen/android/nym_connect/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index 18252905f84787dd51dccc08148c663439ebfaf7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 11075 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 NSwUUCT+Slo{{aNteX{@n diff --git a/nym-connect/mobile/src-tauri/gen/android/nym_connect/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png b/nym-connect/mobile/src-tauri/gen/android/nym_connect/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png deleted file mode 100644 index 514ec36652ee118e7a4e9e923e31a3b356305347..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 24100 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 - - - \ No newline at end of file diff --git a/nym-connect/mobile/src-tauri/gen/android/nym_connect/app/src/main/res/values/colors.xml b/nym-connect/mobile/src-tauri/gen/android/nym_connect/app/src/main/res/values/colors.xml deleted file mode 100644 index 0018ee4a8a..0000000000 --- a/nym-connect/mobile/src-tauri/gen/android/nym_connect/app/src/main/res/values/colors.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - #1d2125 - #424242 - #4caf50 - #1b5e20 - #FF000000 - #FFFFFFFF - \ No newline at end of file diff --git a/nym-connect/mobile/src-tauri/gen/android/nym_connect/app/src/main/res/values/ic_launcher_background.xml b/nym-connect/mobile/src-tauri/gen/android/nym_connect/app/src/main/res/values/ic_launcher_background.xml deleted file mode 100644 index a3b9258141..0000000000 --- a/nym-connect/mobile/src-tauri/gen/android/nym_connect/app/src/main/res/values/ic_launcher_background.xml +++ /dev/null @@ -1,4 +0,0 @@ - - - #1D2125 - \ No newline at end of file diff --git a/nym-connect/mobile/src-tauri/gen/android/nym_connect/app/src/main/res/values/strings.xml b/nym-connect/mobile/src-tauri/gen/android/nym_connect/app/src/main/res/values/strings.xml deleted file mode 100644 index 74cf5d6f34..0000000000 --- a/nym-connect/mobile/src-tauri/gen/android/nym_connect/app/src/main/res/values/strings.xml +++ /dev/null @@ -1,3 +0,0 @@ - - Nym Connect - \ No newline at end of file diff --git a/nym-connect/mobile/src-tauri/gen/android/nym_connect/app/src/main/res/values/themes.xml b/nym-connect/mobile/src-tauri/gen/android/nym_connect/app/src/main/res/values/themes.xml deleted file mode 100644 index 894b25a6c9..0000000000 --- a/nym-connect/mobile/src-tauri/gen/android/nym_connect/app/src/main/res/values/themes.xml +++ /dev/null @@ -1,19 +0,0 @@ - - - - \ No newline at end of file diff --git a/nym-connect/mobile/src-tauri/gen/android/nym_connect/build.gradle.kts b/nym-connect/mobile/src-tauri/gen/android/nym_connect/build.gradle.kts deleted file mode 100644 index 8c6fe5848c..0000000000 --- a/nym-connect/mobile/src-tauri/gen/android/nym_connect/build.gradle.kts +++ /dev/null @@ -1,25 +0,0 @@ -// Top-level build file where you can add configuration options common to all sub-projects/modules. -buildscript { - repositories { - google() - mavenCentral() - } - dependencies { - classpath("com.android.tools.build:gradle:7.3.1") - classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:1.6.10") - // NOTE: Do not place your application dependencies here; they belong - // in the individual module build.gradle files - } -} - -allprojects { - repositories { - google() - mavenCentral() - } -} - -tasks.register("clean").configure { - delete("build") -} - diff --git a/nym-connect/mobile/src-tauri/gen/android/nym_connect/buildSrc/build.gradle.kts b/nym-connect/mobile/src-tauri/gen/android/nym_connect/buildSrc/build.gradle.kts deleted file mode 100644 index 45981f7511..0000000000 --- a/nym-connect/mobile/src-tauri/gen/android/nym_connect/buildSrc/build.gradle.kts +++ /dev/null @@ -1,23 +0,0 @@ -plugins { - `kotlin-dsl` -} - -gradlePlugin { - plugins { - create("pluginsForCoolKids") { - id = "rustPlugin" - implementationClass = "net.nymtech.RustPlugin" - } - } -} - -repositories { - google() - mavenCentral() -} - -dependencies { - compileOnly(gradleApi()) - implementation("com.android.tools.build:gradle:7.3.1") -} - diff --git a/nym-connect/mobile/src-tauri/gen/android/nym_connect/buildSrc/src/main/java/net/nymtech/nym_connect/kotlin/BuildTask.kt b/nym-connect/mobile/src-tauri/gen/android/nym_connect/buildSrc/src/main/java/net/nymtech/nym_connect/kotlin/BuildTask.kt deleted file mode 100644 index 5f63ba21c5..0000000000 --- a/nym-connect/mobile/src-tauri/gen/android/nym_connect/buildSrc/src/main/java/net/nymtech/nym_connect/kotlin/BuildTask.kt +++ /dev/null @@ -1,54 +0,0 @@ -package net.nymtech - -import java.io.File -import org.gradle.api.DefaultTask -import org.gradle.api.GradleException -import org.gradle.api.logging.LogLevel -import org.gradle.api.tasks.Input -import org.gradle.api.tasks.InputDirectory -import org.gradle.api.tasks.PathSensitive -import org.gradle.api.tasks.PathSensitivity -import org.gradle.api.tasks.TaskAction - -open class BuildTask : DefaultTask() { - @InputDirectory - @PathSensitive(PathSensitivity.RELATIVE) - var rootDirRel: File? = null - - @Input - var target: String? = null - - @Input - var release: Boolean? = null - - @TaskAction - fun build() { - val rootDirRel = rootDirRel ?: throw GradleException("rootDirRel cannot be null") - val target = target ?: throw GradleException("target cannot be null") - val release = release ?: throw GradleException("release cannot be null") - val nodeExecutable = System.getenv("NODE_TAURI_CLI") - val home = (System.getenv("HOME") ?: "") - val cargoHome = (System.getenv("CARGO_HOME") ?: "$home/.cargo") - val rustExecutable = "$cargoHome/bin/cargo-tauri" - val tauriCli = when { - !nodeExecutable.isNullOrEmpty() && File(nodeExecutable).isFile() -> nodeExecutable - File(rustExecutable).isFile() -> rustExecutable - else -> throw GradleException("couldn't find tauri-cli executable") - } - println("gradle Rust plugin, using tauri cli executable: $tauriCli") - project.exec { - workingDir(File(project.projectDir, rootDirRel.path)) - executable(tauriCli) - args(listOf("android", "android-studio-script")) - if (project.logger.isEnabled(LogLevel.DEBUG)) { - args("-vv") - } else if (project.logger.isEnabled(LogLevel.INFO)) { - args("-v") - } - if (release) { - args("--release") - } - args(listOf("--target", target)) - }.assertNormalExitValue() - } -} \ No newline at end of file diff --git a/nym-connect/mobile/src-tauri/gen/android/nym_connect/buildSrc/src/main/java/net/nymtech/nym_connect/kotlin/RustPlugin.kt b/nym-connect/mobile/src-tauri/gen/android/nym_connect/buildSrc/src/main/java/net/nymtech/nym_connect/kotlin/RustPlugin.kt deleted file mode 100644 index 5e8dcdb3ac..0000000000 --- a/nym-connect/mobile/src-tauri/gen/android/nym_connect/buildSrc/src/main/java/net/nymtech/nym_connect/kotlin/RustPlugin.kt +++ /dev/null @@ -1,59 +0,0 @@ -package net.nymtech - -import org.gradle.api.DefaultTask -import org.gradle.api.GradleException -import org.gradle.api.Plugin -import org.gradle.api.Project -import java.io.File -import java.util.* - -const val TASK_GROUP = "rust" - -open class Config { - var rootDirRel: String? = null - var targets: List? = null - var arches: List? = null -} - -open class RustPlugin : Plugin { - private lateinit var config: Config - - override fun apply(project: Project) { - config = project.extensions.create("rust", Config::class.java) - project.afterEvaluate { - if (config.targets == null) { - throw GradleException("targets cannot be null") - } - if (config.arches == null) { - throw GradleException("arches cannot be null") - } - for (profile in listOf("debug", "release")) { - val profileCapitalized = profile.capitalize(Locale.ROOT) - val buildTask = project.tasks.maybeCreate( - "rustBuild$profileCapitalized", - DefaultTask::class.java - ).apply { - group = TASK_GROUP - description = "Build dynamic library in $profile mode for all targets" - } - for (targetPair in config.targets!!.withIndex()) { - val targetName = targetPair.value - val targetArch = config.arches!![targetPair.index] - val targetArchCapitalized = targetArch.capitalize(Locale.ROOT) - val targetBuildTask = project.tasks.maybeCreate( - "rustBuild$targetArchCapitalized$profileCapitalized", - BuildTask::class.java - ).apply { - group = TASK_GROUP - description = "Build dynamic library in $profile mode for $targetArch" - rootDirRel = config.rootDirRel?.let { File(it) } - target = targetName - release = profile == "release" - } - buildTask.dependsOn(targetBuildTask) - project.tasks.findByName("preBuild")?.mustRunAfter(targetBuildTask) - } - } - } - } -} diff --git a/nym-connect/mobile/src-tauri/gen/android/nym_connect/gradle.properties b/nym-connect/mobile/src-tauri/gen/android/nym_connect/gradle.properties deleted file mode 100644 index cd0519bb2a..0000000000 --- a/nym-connect/mobile/src-tauri/gen/android/nym_connect/gradle.properties +++ /dev/null @@ -1,23 +0,0 @@ -# Project-wide Gradle settings. -# IDE (e.g. Android Studio) users: -# Gradle settings configured through the IDE *will override* -# any settings specified in this file. -# For more details on how to configure your build environment visit -# http://www.gradle.org/docs/current/userguide/build_environment.html -# Specifies the JVM arguments used for the daemon process. -# The setting is particularly useful for tweaking memory settings. -org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8 -# When configured, Gradle will run in incubating parallel mode. -# This option should only be used with decoupled projects. More details, visit -# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects -# org.gradle.parallel=true -# AndroidX package structure to make it clearer which packages are bundled with the -# Android operating system, and which are packaged with your app"s APK -# https://developer.android.com/topic/libraries/support-library/androidx-rn -android.useAndroidX=true -# Kotlin code style for this project: "official" or "obsolete": -kotlin.code.style=official -# Enables namespacing of each library's R class so that its R class includes only the -# resources declared in the library itself and none from the library's dependencies, -# thereby reducing the size of the R class for that library -android.nonTransitiveRClass=true \ No newline at end of file diff --git a/nym-connect/mobile/src-tauri/gen/android/nym_connect/gradle/wrapper/gradle-wrapper.jar b/nym-connect/mobile/src-tauri/gen/android/nym_connect/gradle/wrapper/gradle-wrapper.jar deleted file mode 100644 index e708b1c023ec8b20f512888fe07c5bd3ff77bb8f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 59203 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

    K7UpUK$>l@ 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 E2O(~oYXATM diff --git a/nym-connect/mobile/src-tauri/gen/android/nym_connect/gradle/wrapper/gradle-wrapper.properties b/nym-connect/mobile/src-tauri/gen/android/nym_connect/gradle/wrapper/gradle-wrapper.properties deleted file mode 100644 index de8c362b62..0000000000 --- a/nym-connect/mobile/src-tauri/gen/android/nym_connect/gradle/wrapper/gradle-wrapper.properties +++ /dev/null @@ -1,6 +0,0 @@ -#Tue May 10 19:22:52 CST 2022 -distributionBase=GRADLE_USER_HOME -distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-bin.zip -distributionPath=wrapper/dists -zipStorePath=wrapper/dists -zipStoreBase=GRADLE_USER_HOME diff --git a/nym-connect/mobile/src-tauri/gen/android/nym_connect/gradlew b/nym-connect/mobile/src-tauri/gen/android/nym_connect/gradlew deleted file mode 100755 index 4f906e0c81..0000000000 --- a/nym-connect/mobile/src-tauri/gen/android/nym_connect/gradlew +++ /dev/null @@ -1,185 +0,0 @@ -#!/usr/bin/env sh - -# -# Copyright 2015 the original author or authors. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - -############################################################################## -## -## Gradle start up script for UN*X -## -############################################################################## - -# Attempt to set APP_HOME -# Resolve links: $0 may be a link -PRG="$0" -# Need this for relative symlinks. -while [ -h "$PRG" ] ; do - ls=`ls -ld "$PRG"` - link=`expr "$ls" : '.*-> \(.*\)$'` - if expr "$link" : '/.*' > /dev/null; then - PRG="$link" - else - PRG=`dirname "$PRG"`"/$link" - fi -done -SAVED="`pwd`" -cd "`dirname \"$PRG\"`/" >/dev/null -APP_HOME="`pwd -P`" -cd "$SAVED" >/dev/null - -APP_NAME="Gradle" -APP_BASE_NAME=`basename "$0"` - -# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' - -# Use the maximum available, or set MAX_FD != -1 to use that value. -MAX_FD="maximum" - -warn () { - echo "$*" -} - -die () { - echo - echo "$*" - echo - exit 1 -} - -# OS specific support (must be 'true' or 'false'). -cygwin=false -msys=false -darwin=false -nonstop=false -case "`uname`" in - CYGWIN* ) - cygwin=true - ;; - Darwin* ) - darwin=true - ;; - MINGW* ) - msys=true - ;; - NONSTOP* ) - nonstop=true - ;; -esac - -CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar - - -# Determine the Java command to use to start the JVM. -if [ -n "$JAVA_HOME" ] ; then - if [ -x "$JAVA_HOME/jre/sh/java" ] ; then - # IBM's JDK on AIX uses strange locations for the executables - JAVACMD="$JAVA_HOME/jre/sh/java" - else - JAVACMD="$JAVA_HOME/bin/java" - fi - if [ ! -x "$JAVACMD" ] ; then - die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME - -Please set the JAVA_HOME variable in your environment to match the -location of your Java installation." - fi -else - JAVACMD="java" - which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. - -Please set the JAVA_HOME variable in your environment to match the -location of your Java installation." -fi - -# Increase the maximum file descriptors if we can. -if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then - MAX_FD_LIMIT=`ulimit -H -n` - if [ $? -eq 0 ] ; then - if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then - MAX_FD="$MAX_FD_LIMIT" - fi - ulimit -n $MAX_FD - if [ $? -ne 0 ] ; then - warn "Could not set maximum file descriptor limit: $MAX_FD" - fi - else - warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" - fi -fi - -# For Darwin, add options to specify how the application appears in the dock -if $darwin; then - GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" -fi - -# For Cygwin or MSYS, switch paths to Windows format before running java -if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then - APP_HOME=`cygpath --path --mixed "$APP_HOME"` - CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` - - JAVACMD=`cygpath --unix "$JAVACMD"` - - # We build the pattern for arguments to be converted via cygpath - ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` - SEP="" - for dir in $ROOTDIRSRAW ; do - ROOTDIRS="$ROOTDIRS$SEP$dir" - SEP="|" - done - OURCYGPATTERN="(^($ROOTDIRS))" - # Add a user-defined pattern to the cygpath arguments - if [ "$GRADLE_CYGPATTERN" != "" ] ; then - OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" - fi - # Now convert the arguments - kludge to limit ourselves to /bin/sh - i=0 - for arg in "$@" ; do - CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` - CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option - - if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition - eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` - else - eval `echo args$i`="\"$arg\"" - fi - i=`expr $i + 1` - done - case $i in - 0) set -- ;; - 1) set -- "$args0" ;; - 2) set -- "$args0" "$args1" ;; - 3) set -- "$args0" "$args1" "$args2" ;; - 4) set -- "$args0" "$args1" "$args2" "$args3" ;; - 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; - 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; - 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; - 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; - 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; - esac -fi - -# Escape application args -save () { - for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done - echo " " -} -APP_ARGS=`save "$@"` - -# Collect all arguments for the java command, following the shell quoting and substitution rules -eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" - -exec "$JAVACMD" "$@" diff --git a/nym-connect/mobile/src-tauri/gen/android/nym_connect/gradlew.bat b/nym-connect/mobile/src-tauri/gen/android/nym_connect/gradlew.bat deleted file mode 100644 index 107acd32c4..0000000000 --- a/nym-connect/mobile/src-tauri/gen/android/nym_connect/gradlew.bat +++ /dev/null @@ -1,89 +0,0 @@ -@rem -@rem Copyright 2015 the original author or authors. -@rem -@rem Licensed under the Apache License, Version 2.0 (the "License"); -@rem you may not use this file except in compliance with the License. -@rem You may obtain a copy of the License at -@rem -@rem https://www.apache.org/licenses/LICENSE-2.0 -@rem -@rem Unless required by applicable law or agreed to in writing, software -@rem distributed under the License is distributed on an "AS IS" BASIS, -@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -@rem See the License for the specific language governing permissions and -@rem limitations under the License. -@rem - -@if "%DEBUG%" == "" @echo off -@rem ########################################################################## -@rem -@rem Gradle startup script for Windows -@rem -@rem ########################################################################## - -@rem Set local scope for the variables with windows NT shell -if "%OS%"=="Windows_NT" setlocal - -set DIRNAME=%~dp0 -if "%DIRNAME%" == "" set DIRNAME=. -set APP_BASE_NAME=%~n0 -set APP_HOME=%DIRNAME% - -@rem Resolve any "." and ".." in APP_HOME to make it shorter. -for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi - -@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" - -@rem Find java.exe -if defined JAVA_HOME goto findJavaFromJavaHome - -set JAVA_EXE=java.exe -%JAVA_EXE% -version >NUL 2>&1 -if "%ERRORLEVEL%" == "0" goto execute - -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. - -goto fail - -:findJavaFromJavaHome -set JAVA_HOME=%JAVA_HOME:"=% -set JAVA_EXE=%JAVA_HOME%/bin/java.exe - -if exist "%JAVA_EXE%" goto execute - -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. - -goto fail - -:execute -@rem Setup the command line - -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar - - -@rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* - -:end -@rem End local scope for the variables with windows NT shell -if "%ERRORLEVEL%"=="0" goto mainEnd - -:fail -rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of -rem the _cmd.exe /c_ return code! -if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 -exit /b 1 - -:mainEnd -if "%OS%"=="Windows_NT" endlocal - -:omega diff --git a/nym-connect/mobile/src-tauri/gen/android/nym_connect/settings.gradle b/nym-connect/mobile/src-tauri/gen/android/nym_connect/settings.gradle deleted file mode 100644 index e7b4def49c..0000000000 --- a/nym-connect/mobile/src-tauri/gen/android/nym_connect/settings.gradle +++ /dev/null @@ -1 +0,0 @@ -include ':app' diff --git a/nym-connect/mobile/src-tauri/icons/128x128.png b/nym-connect/mobile/src-tauri/icons/128x128.png deleted file mode 100644 index 12daccf30f0068ccae0773eebfcee80b98e1fbd6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 11741 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 G5cz*z?MI9N diff --git a/nym-connect/mobile/src-tauri/icons/128x128@2x.png b/nym-connect/mobile/src-tauri/icons/128x128@2x.png deleted file mode 100644 index d98b024d2634f4be6e915eab1e6b25f59fffee73..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 25778 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 diff --git a/nym-connect/mobile/src-tauri/icons/32x32.png b/nym-connect/mobile/src-tauri/icons/32x32.png deleted file mode 100644 index 5463a8ff1ce4c3c6be61ccfaab4e23ae0fc9a90f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2394 zcmV-g38nUlP)3&26~6X(AFs1{!#187kK+}LcQE$YGh-VY@5UZ4cmq3uBqYI&lB!9g{3!XspOk=9 z1X2|$Dq5EUp{S&-svwjWsvw2Z2$Pl+wUi~nae~3vv%Ih8+!;H@300f&q@#Q1&U^R! z&bjBDdyWuG9(36SIvoO=9Ad7^CeU81Li+}R?iMj+t5c)Cxe<=m-KcCni_)fBN@25G z$#3x>tMv}d9p{nS{TB4j4n%fnAch1)4@sBGExH0|6btf=4|;7v=_Uf*4&m*#2jZz= zfxZSY-&C7~w#Hr9&~RiNKyQB2zz(*1G9a>(-;qyb9WQ zi&tn(ZlN=IgY{-5jKyG9LP&(c^J0?EE3IldA*;1uJU){Tx>#bRZMoR(_q+sn+!B?>r1$^5kf+ikl} zHI^YjV|Ho%XZ~2gmsvv3N0E^3-XCr!0)vMkwh1xSU#Z5{>T7K|i`pxigiMyD-i?Ld zOV^kGw=mZvSibfx3r7-Lp+uXHdC@aNR#++Uw5@Tnt3oCjj&nABl-8(3YK)5;#OTeS zH94v^=~y0y{BE?)BxPZ~G(02eTmSb8`&MYY$^Bk%6Io$oo4_;n-G>|{?D=jjn<*(v zS!pRkdS)?nNm&v!J|zcf<~2x2&4oF;6xzfm5MHV0{yo+3~#m5`G@Hbqn}?s4iR(G`}2N8x$B{Xt{($YDIxw(0%nW8B8?yEn5 zv;RrC!J0BVL})xN{`n(Zp8Oa;I`Fn!FIiUy5vak{tJiS)H^0XxWUYfgK1}|Qj{Em# z@w15&`1I2|c>X)DB9I8gBxNcY`fNN9y$TcXSTnvN(A+*CH?62{!Y_}XlBVCj<0ZIU zE*}tSTHoY^m&oz+!@UROQG`aP$fNt|k)t?z>{s~U!iU({+ArVX@9*B7!E-xbStMX4 z0<)q=;Ul=ylKs^Yv~>6gtgmTCZ9|uw|Mquh@Fkg%39wed>SP>$=ht}fU=F6NQt9C# zk;(Yy^?%_o5g6M(F0D6l^d0FF>#Q5;I6Xw*dy+s@QYH!(jsRmd=XVsXMxf5(4Ubx( zY;5h5N5IY)614(-#TC`_JtwEY+_oTQcz;!QG#)Z zvG?Z}RYRYgE#sQQ%S@W*6j1@62)ul==&l`koQb67AP8>nZnTM zc`dS%v*Bq2#-x$jD277Nk;^&EzO7# zu`6Yw^3BueyA*jm{C{{%D(4JYywIiOJ{`h^%E&kuBpMDyM`kX9x%lv<;>9eskVcoM zU{1G7Q}EJv7t*hGNm^b=#SxT>b9K3x32;)mHjpLoVjW*bdymvn5fW!|J=U9W*^Rhx z8SPecT`+hO;#s;4w^1#)wc6qtw$4>jIEHA=vc7wa*D)5ZV>M}S(Pew50=l>+sN&My zSCG6=%S7Hd*SZ#=TC?l_s)oKna~`!)d!y@EXg!0|RlJ=FlKw#99s0t3f>o)>B^;{Qv{3RKe=7AWk5%)G%8L2$~?DdjD z=?`)KYS>32<9`8PG`rRhJ{n!hm9d8k(Q>kYzdY{~oQnMjynXIAunYy8Jb$q&r`Yy4XCnX}nnj#@|HP6lt_GK{oW`O0%H%ZL4q$q02rgVYrb zJH%{vjRn26m(JMMqbMz#OLTXbznIYZt)f{b{^kS>9^nIaTlCr9W1+RI`E?@T*POw} zWK)90?uc8n9*Ty6T5iu&=ywQeOv2!--!t3f!2aTLl%(b$RBv_*t&+>+Id#_Z80g_nd8kRC zt-b)QjfdxJ>u268vtk$J^-ijI3X^k`jKnOM6S7c{n4`2L72vhRwK#2XU`E|WNybTW z?xau~6~3m_sq(dB?Ig}ebjS?a;gBu5dy_o?9n_w8HW0Zsfeno-v7vbzY|Y0}vGKOM zbR!4=?uRh{4Hof!5~6H=u>b%7 M07*qoM6N<$f{2BYfB*mh diff --git a/nym-connect/mobile/src-tauri/icons/Square107x107Logo.png b/nym-connect/mobile/src-tauri/icons/Square107x107Logo.png deleted file mode 100644 index 468b6cf29b46074201b5a5865660fa8ba1e3f9b3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 9407 zcmV;wBtYAVP)}1tG1uMxad%DlM*>kM$# zJphin4+$1RwGXvQABHP@s4n+msN5IUmeq%_tU<-44Pji;s9|Z7h9%8_i^+<~7Lv^; zD{R(q9$9{Kn3Sbzxy>OpyD6;BYR2$PKWfwcNU)G#A!G&iL3-e6Qr1NTYcY;(VOR>r z)7H{rMTM9wyYIEDjFH3|gW3N{d=dNLnDs`Ie2CK9Z*;PGxIW$hol#(7s9G~6{{LFtRyfMO@PZA;J{KJd{kNoI~RH3?+ZP!t*8c`o9BjS3S979z8hYg zTLXWaTMKW`t%E&z^{{^qxyM{TU``Nlc2L8aWYdFTnCcIyll|dvRsgkhf|g3~l7k3I z!RHCq*#v6_3T3q@ptM*Blr0)s+gAWkfZSYjjSh;H)LdgFRaps^t}ve`AL=~Ufwy)S z!9s$CVK)JCc~x?QD!P0cx*7oO^{{7oExb_bgxiZ&z}a(_K-;WhsGL>=`IGY@Gcym8 zGjee}S(3@dk@4s0nR6f~D<76kDgytMVmM>!61ZVn1w1vq26j%P^G^)`PHqLvYSAz~ z7*U0M|s|8a#`j z-g6iDJRwpRYTjB@J+&&iK{6kpryf3ccwtMK1Fk771OM!WFo&RwrG$@9&p}ICHrmJ% zXn(8$WI{$ROe83MjXzH|;3a0{U|dEvDrCG(p!d@==Yl6|A)KGJ3?9v@fsfNkwJ8KH znJkIGB?eV3o`6|ekYJIuK0@X-K_g&omM^8spoJg>$#{Eqr2;{>jbI6A1T2?e{hVMS zDGN292URZvtJTow15y?|S8j)M3reAM+C1O{wi1*$GTu+2#7`wlGmQ6B1^_gWr5Kjl z=eMlJcmii78=aYh`I!Z9a{3Z@FvSh~#*xcRXakIE)xwq*HLSE~1PTcXLwl60LINVQ z8QX{*-)d?#AhETQnu2aCg9E>N4ESrVBv|{gi6Aw2K&|(v*iZ}DPzUe3+;G#PWl%Y@ z5XPqKfLJvt2Z2J0LE0X&nSK{JR|H_j98zx%S~Ih86oFfqJ`XOPupG80kgHVM$=ZN` zF|@D&jO{K^+NPUaq4l^2hHWkd>^=#~hOI8KPr&LasCb=}g#-)L#+nc|5~OA?>~K5b z{Gw8rJ$WuzQ|ZYz0ZNI60*3?UkSs&LMhb1yGIVfb2$&^vHfChx!G`1|ur=OiKx+q; zplM30rbBDnOLn>mQXCzl=+-8-#|NZNg3@7Fr<0+@0f8e}N#qiLM#@6Uzff&*?OEB#+cn9h@KRho0qYP-QkwNDw!5rk3c)IaR`+&N7HUCvSoOQZ*isAO zrW&}uv15(NnxV2*lO$SGSCK}Lq%g|VL=Zq_d4=n)!jTJ@~GJt(T)-3_i z){4FRUl6D+r*1khD|R~+0jk~gHG+lY3NajXg-BU|tsZ#RQ3(~Z3I(Kb{UO0|1!@Rr zv=5f7*_fAJ0KZIdnOvg|Oo!HfJt{%N6}H=Y-D2kmgX(tbrUSF_7CI<@>Tumcz>r~u zT3jl&*MJ&u!v)19kVJvQ=(LR7-1S0Gjb4g{JN7V{o1dCi_TgU*m9h8n-1Cj~YF;*C^viHL3ap<7Bov8%t zMRJ9xbvQ#>JGq6f8u(RJIb>z!!6;Hwde(6eI2?;W#H2Eg9LeD7v6V*f(N1B$1lF;K ztYpN1;cB}MYn6k6*%%u1I5P;=E93?--03_(ks)BG8?G!Z6_8}!^p%1%h{{s+00b4v z%T|+HJfO5PG*p%L5N`?AKC)%ez&ejUFa`w?Saut4VU;5ldK|BiDz! zE5vXYfg)g9rxUv7EfAp6GQWauZ}`A4o%U^%k1a_pf?ZY)@4F0Oodm4&W3qe$lI^Gh z!?kt^%YtjF$px~i?3>6PVz`Gu^$@5oC$tvK7ofh9`O{%QV_@+FtT?q0c3Oi5u#S)c zYzJ*K3`o|a0t?m=q>Yt2tTn7E;SO)1Eov+6AS<)7f76#HUO{^QrHKU76B~W z3+y>^jS`gQh=AdG0<^ALw>9=S1_o>FUt>_DER|pZu6Dwy3m2Ldg(ozqL4a{ttSV&z z>=%XSE-(SRo%1hhld6j*C7BUnhvLUom0!*x!$a#@K`R9Y`6IN?DJ z0*r%>jtQj#7KpM~r_94n!AQW-WdQ3q6!%vxVOelJZ8uaW!P@HWq%I7ts#bBm6Mp3` zgHfq^E%^!W`i23FqeKqtFHEW+U_C(jmj*hl5GY*>3{ciXQkU8N5i~2qqV3PgpQ4`y)U$ zV_VOlw6)b?iVQ(rRSi`+^MtC{{hg3e-(dpd>Zv5!PD`H)J48s|P5D=6hyxFTbP4LP zY{P~H()|fg_A&JSb%v!ftc?!1VCkYrd_LW&UNj!!DJqXxQ%1lvhbr zU1@*{F{o!7%SG*ZpZ*Oo7>5DOO8M8#W1IwRCAmWmHiWu18IUYP!ZJWv4N!B)_LCvy zTUvN+HB^xM8%^#nHI`2uu{#`3+o{4G28BD+l+LgLENJ9iHa-99K1itE3+v|*Rh{gB z>#Iw^k`l|Oj#h2GRM)!Xr6Sm%yekYktiO=jtf2Hnf{me6wTKm^t*ZvyV254Zl`v~+ zo;mu9fjZL8IeK)+4Vdu6sCo%Zy2Evb8#IEA?r2a(eq{xH|1LvSDy!&$u8`DKe@su}@x)f?*4^bnq5J*lgzQ_&UHt@Kta z>D!3}YnP!a?fsrgm_2o_2-j0$syfzr7d{i;V5qB$!68|h0m>?1iDnQ(Rb*?;dDTt! zQn1EUb{)NH?r!wIgx8MR)I>9q2UT! z3|KaX6lbXF46_S7cWH6V1&*M)WT4^g4dYeA72aut@K&=6d_b>Sva9>oR72_P0!T=U zDQhI_4U>(A%GYcGvnzay_Q#S%@~m_M^`YScwJrYgSOhr2Cc}6exx!l$UD6dAQDIND z8J5p*ff~EOYbqA&E-+SMJL0a8VRa@i=@S+9Txf=MxltDw!i{$D&ntqslt>f^2OB|{ zHhC_lO&Y}LyHwf^)R9KMPc@Dk==hYZyaCTYoQ^pIp5NH#^A7s`0RLgA>+yHPIgk0e z1Gz$3-YQ6&2OnFwb4`z`>B@Q#l$8-!4;ckvbq^JU=S+(#2oIVNhJvsFSz(>aZbM0 zc^6!PQ|SBgMgk57i?7M|xZ#vtS;#~Z{2fEIfi8FEa`qRa^`8oro1%FpwkXw>}qUT#0ggS$YMJS)Op6TsoT+;wf@c>bMPReaMgzw%ntv3vJ^g1Pg` zgd)qU>IXdEC6|96Eu%BZwd4=@5Xk$h@wE^5`!m0Ffq2rV%vyl&?S6mA`~Q!#FA~SI zE9-kbR(aO+LVWk#T|=Jd8)sc8yoYqH1HqDg<-b0@Tm~5oih*@7tOk(5#VHOrdr2`^ zCPw-K4i1ZCU+HW@P17(O4x_5-#xH))_W9=r#QVGM{x#ahOp4@3W>{OE-zJWc$G-LU zyEr?qRD7?D$`DVs{9aX6vH!^@|Hi`N%E(hXkXvCTJo~5T#qkFY91zB@M;?0$6UI*y zi9Bg~5pI9|P4Rgs6cYE$|A$SbefgVT|K<xCEp9QmXC;wNLBL5l;mO03npH>KaEj-SvCu{^6zhziI zmWAO1YxMd+E({;6K5!sdG7(gKl)e%3LIGQ{r7`qTz|_8+wl4ue-`D=`NctwIiKYF;%m>y zU5szP^KO)a_?#RV$%o}oVX=)PVB6xFjq;X=2tQ+GZIkU+JG&|&bJ83!*U`kogmuS% z-7Egg&Ykb|yV;&Kr$wIZ;lhgK3!Y%am6@N`Q%^rfxV?<2^Dr@a1{N=I;y%NTnHB$? zi>``N_F%BK{rM$q>)O~)l@C1lu%WEEQE5=y&|x|@Y~*qGiy)>i@ZrMZp1VBkb@M2n z2r>v3yF+AUy(M66v}=EFTLFD0tvX~__uTs%RFrY}!|QL2B!axzKR)~fW$x2^-PSN* z>G{xH@h5L5H`Z4%|9tBmoH~19q|El;zrL^EbzlD5YliE~?GwfIhxPI+uL=dS63uGa zx$8ZlB36@LSkD(_HZB%O7bwqh_O~w;MTR4QC7Sy0P4E~IAqN^gz{=XIm$B@yjmlW# zGom_i9x^P>_?5UcT)M&=iST5?@^(da112O-kGQzuz~U_jC!B~M8Hs3q>aYFLE%bnl z#io|kk^3?XW z)6V$zVZ)LU;cbcTey~(?8&*ruU2bMX? zV>ia`tZ(GS(PvqO7oU3iPo}^|PoM`kJt9K&z6H_2V7>W|e~Rl|bc2^5nXutyQZOgEpX%evDh+zGK zU{T^QFn{ZshzjIW{{H(PV73wM@p*;=t2cr5a9I|l%>fL%DVZn|BA;_vrBA>z7sw9= z>y0;di2ueaiYVAP@1vwPRvkY$d`z>UWV6a2+WZ))d_11}oy(617LQ53!30YxD{NFX z!L!Xr2Ns93GI9QwGVH!j`?t=$L_EFxgbo*0B<~`3*3olvq#Q&J4>eOc${bW&f75M7 z%|remH4g*3!6eF;X3Z%P4}e)UAAS5U%*kIW&c*rFwLiQiqCR;H*RCi^0-krll}8Ng z#sQS|sf6_+%fdc9@#&K|IqV=!2M-~L9V{Iuzhk45fXp0~AYgZPfm_w=({BE<=*W|z11 zs3?o`te-~OwR&Og)bs#D`&+As28Zm+J{(y5G;`#{RY$wY_4Rd!{;2fRaV9Q*V=gF5m&NsPmO)fC6fBuPUU2buBj=RgbCj!CQm|-I6@K#XfA=e2&B$3K z0<3e+yX=Ty@u0iAP0^r)^^z=Sz1>*>Tql@(6kzG?J9^d6&9~kWRd>cO4n9~RTMq$N z-zudWZ@E3{w=z+-Zu?7AGs!?U9A&w@E&a-M&r_k9uidrk)PDKZum0yg@t@dLpMBn? zM-;jzQaS5^(RI<~EP}OJ)?jd3R?ZM@S%(5ks$e;V>*nyCU;Foej-}-ud`7yGVZxHG zkOLGBRAjy)-Jnn{1vaAIaDZC!;lPr$6I_1#$Bwt69t6H`uFe{$9Qh3@2y?pdwYjWy zxUjg6kZUlu#5YB1FbGzNVcpu>QKkx(WfwqV+R=d}6PCmH%~T-nd-K=+@a9N8;-SEj z=N09lC!UG=J%`eHg|huA@354m|HnL$=)b$~p?>++nhj^*qmMlu1(ClitEdwpJ=bR* z2`pLrzC-jO)awQ*8hn#sofT=)-w@rTe<-!?gTdl0yM^x6r<%fTT>3XWC;yIHw+{=J zQR|*WE^uyd(|*{1wTWQ`L5>+d*#T#iECi*uJqw4^p2bhy{?9)bH+1J+_eQ~z36oCw z%&V_$H`p-gcKLv+?hF`;(vk+*C}+69Lls7hf%0A0N~AzVG{ke(&>0&D@kD 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 FV1n|G)#m^J diff --git a/nym-connect/mobile/src-tauri/icons/Square142x142Logo.png b/nym-connect/mobile/src-tauri/icons/Square142x142Logo.png deleted file mode 100644 index ce58b768ca72e65a1c0430e9a12e28ed2a5f721b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 12553 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 Mikb>#a<2pb2Q%zTT>t<8 diff --git a/nym-connect/mobile/src-tauri/icons/Square150x150Logo.png b/nym-connect/mobile/src-tauri/icons/Square150x150Logo.png deleted file mode 100644 index b6bc59dfcfd5a45a5fbf6fc364e5ffe47b0dfe92..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 13324 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 bTmN3X6S771o(Fjk0H7$ZCRZv03;e$TurQ4| diff --git a/nym-connect/mobile/src-tauri/icons/Square284x284Logo.png b/nym-connect/mobile/src-tauri/icons/Square284x284Logo.png deleted file mode 100644 index 26311d46047a6a8556342e55999ea585174d8f10..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 28970 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 diff --git a/nym-connect/mobile/src-tauri/icons/Square44x44Logo.png b/nym-connect/mobile/src-tauri/icons/Square44x44Logo.png deleted file mode 100644 index e161bd7c4af923c48ed2388ba9e125b41e05ba0b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3527 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 B0onin diff --git a/nym-connect/mobile/src-tauri/icons/Square71x71Logo.png b/nym-connect/mobile/src-tauri/icons/Square71x71Logo.png deleted file mode 100644 index 45b5126bf601d5ec540ef18429b4b76030cc75dd..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5694 zcmV-E7QyL>P)8r!&6ydBWhxD1Rt|EL~OiymM|Xsct$F9F)cSeD0^Z zt8R5wf9rS7J-6!KBM2v9VJ#j3d>(C` zM?g5^WLHy%s}fD$Z4Sn{#t zVadUgS=S??bB-I&Dl0-d$wErWLirwIJHB9%r1a*EGY{B;eeRn-zCQ^xC6nI z4lAeI%s3?a-2eRof-aW;3)Ir(GJ)T1;i_r(ka8T7&$CTz@⋙lqQ7YZkBe}xx|Oc z=S%0#sATh}&SV+s`7AzlI>e^rfh8pu1S-}XTr0(d-)VbnY97R6nUt2#8q#O6^V6%? zmUM@-C#6+{30)Gz_W+s|h^8)C7P_87AoDfFV6hCVsOP(f1U(33VygnC+hu`nrvQEz znN*qF?tV^eaRan^u{5(UU2bvn?D?`kx0J((w&sD!nhRqP$an-ZF>MA*!jg=|8lfbs z`>EbI0*R1Jskso1B{Q{v^`um?$0jt&2Vy%#5EcSL0a54zS?J!1Ac`~~ECkZR?*K}m zQGi~jQu-PN*5gDlE`k-q{H~kD4wr=f2cWY_-s^FSE6S=^QDz~FLLgM=&m|lF^u)*o zCMeJ#(thLd^m~>x1chZ*N-?`NzMkzf6Qo`lAt0pgqqx2fVVG4wQMix8(aOmIgD^M{ z0u*R8v4u`EEOZi>YPrj`Q}Vk2y1a0()g`X1sAAJ56@V!@7vfVCAmK0!08Xf=$8m@g zQ}K7zGBynrm7W86DTQ!nd;>cobP*(wP)V}T^EQGgB^Zo@(%|&+^6xd#0|=yFh11t) zhDCNoJqw*bk-D7>)dSG&32tkc&&sD35fqk)f5NJoM93dHStV63Y(m;}NW!%hO9FmI zNUS=!1YVBuqJnyZq#{Y^K@}|=A}BIfQLjmZ147_iDrV3jz#?po2C(!y%+QbPfRog- zO6+k0^t$1Y-zoldRt3ab^H?1ElJ6ACC?SnOCB>!Xu}c%@K~U)CDiRSA!ns9JO@IKI zX|0tL12be7*q~j3vk1YEf`;5CRL>J)ZzDjzTl~=1AlGCUgDELjcdXwFl;c1mQzK9U zs>q&H3LjfKP(=Yr_;#|uHaTj4Wp(Q9WHM@P75f;Q|M!L5xoj9MXVN>7~`ogg6cLy2kA*?9c?LQE3^ z8qff()XaK28%^m+pDF;HW`*W;6s0%Og@H8>5Kpq!4Fwg6U#v#wA<{L5D+%=fx`%7 zCP7gY6af${K^W)Q35*Gr;pb(x7+N)LH#T)v%?uo24aaPRks@pH`G%bFH*WXC#;iPMVteD-z}d z`5#mduKTW1;aHTOe4hr!t`?hFHK#IhiA{nZIbd7!Tt)$2Qdl48j4Uc3t;C~=y%ybq zG0*@8iGf21WV#B-teDwiMKx@xYEt-FUTS*|O$-hWHHhWeg%D>|`bgilPjtEg(j0Uw zc3NvONJKC}#k~3+Hp1cN73zd-4w201#}0XAT_rP3JcIqrC>*pI&1==zMr^$nF;OwE zDkC5b4p$BOnQgGl4tx9S#c7lCArWJmq_YJ|7@#D@w4lIR3}2XfL~6ZIJzK)zsJ_NF zTU8BPQH!cc!j5G6Sz=Bjm=~#sdh&OVb&z_{#QLxY-(RRW<$kknQs`?|k+1o3QG-~N zRlpJAxI(}4x!8oT5#E@2v+vr;u-aLBYQ={YbszM4!+mayeQ z)i`uG=!n;lBi|tqqs%| zq^MzhN*>H>@Ig!G0M;LXf?2hAd>*)(d%)M;$G6REXo1A!9BK_i{uHfgd#$a7AK#4i z-M${&mJ8)ojnL{hoF5BaPjOj2KgOsSx*;>m>!EGIAiqw%J|15>+s=PaSD6|yN2lk4 zXJQ3C&XAQJdw(0jSxNq8PoOTCkuigf5A!!9MY@9(a7R#g(Gyic(lHlOMui@y?ApidL*N5&)? zhP`_~)3-nK+=~#KI7Pom@-y`P*=;ZCbw2&{U;H@1k_>CtJ;=8OgCd^$D9EzRzyIy; z{{v}LW^#4X&7pPr)S2*qAMe)d2SpJ?EVTUkuGQSUbU10l9Z~}HPEmaV$FHq>4lLu_)20;jbywgVWq(lFf8iz9=zeF-{SWAhdFS1o@WmHj>fgzXNp&fx9`aW< zSEml|v(NXzyYKzCem!^GwI(7QEg)3l9r8Qz7AEfL}y#jHZ%5d9S^ z@6^B3UCPKRL4Pxy`=6Kp@P+}-4Pc4O(9P#Be|=Bi_ukGA_;&JBv^M^UxBv1kuTSPe z;Mb~fXnAx{_#l;;HAjx=6XCAX2vRMy@kl z`$%xqUOKFdw;`M%g0qL8JXnoNzdIueu-lhtAW#@7nbbn37x0o9`(( zW&Ag_rKfLs2*?rOXq`~FHld!Wa1Ih2hRR_V%qBRw$MnP_!P)fSqx^0!UUq@*q-kmN zc5xHnPZTdX6x&mjMywLXWiC8Mw*ZhRK69|_KVYd3P`YEG6G zH--x2xP2Y{!>uB=dfEAN;K9w0k4iZL!nr!3j^Hqa!&K!s;Nm&uqgKxPjgRmP$;g@o zfB)!zhZTj*HMLBnqLF-#_GfRu<37DLF|Kd@^}T#Q)ic&N!r6#7hklb zz7i1*{T@ZH6oio(?Ec3mA+6J{-FqMt&4PkXEvSqHhm=5OO$yeQ7hZgYtJWAs+`D=M z)=NM$eRfoEr~`U)n9tcot<~-Ivq#-p@xTmQ1#-S5?=QdHAJTG=gB%Htu?0HsXFun4 zH1qiJ`IqVeoo3n*;XJ&R|LNuDU94~W=cjw2Y>p!&mKr+eXIL+ORB*J`YVB}q^*+Tp zJ1EZC?8Z39I=VP#-3Ejcox+<=iZ(Yr^q9WyuNZ^87GhS{hrm(Plh6A~W!NP+aNwXG zOcs~bai^-O#t6sgR3E_e3Fai2UE_wYzB-`zszZn1uK!r8`?CG}zk(wCJu>r0w?1u% zb5?(2obyaLHOIdFk8kn)6h=H|gfoJ2sEor1 z`Ms!4Eka&LeXAR(Z}mxEU2xJQ{wyb=a*P-EoAnQILI!QpENR_pL4`I05RFqwkR#rWdo=h1X%6;@aCtsxI9G&v{ zm1QG7&mmtxcYz9hHa-(cQq@EI=x)X)Dr@>q-HGt$HQJg$@Oo-}4NUmMv+(neRnMKQ7B#<5N>E39byhVEA z7y8t1baf-0Khkf6|JL!YU313t2K0IEjQNwmH$k{ie}=qDd!~~)=?C=;nS)8dRw~Tj ztTeuT@bR|~%k5H6NeQ$3aNa&>uZ3ah__@Zn5B06*6$iIGU7&4u89jk0xF22wpm;9E7uQH0ccq z^`MMq=fmAD)<%;aGZq<^HVAB)Ls8IjRMNS2Gn&_*X&iEJ&?efa7X!vVXD%QE7)yFC zYnfO`>AwKtENu#vgS4%_tX%OWZH}}D+jyujs z&4+iQ+GwDFtj>M#k818ihK9co*Qj7D z=zE0Y!cpcy=<}@vxzo#M>tWf%=UWZBf-kt{5g2Y_r=<^4p=ds4jH(zFj5eL<9uQ%{@Y2_bVl9SF!yvxGPDIi7uPX;-UVYRsF7&3vvt@B5Slit3{WJhwi# zYNFa)Q>(O>Pag`%$|Ts{dknMlOy7PItU5awwAsP924!~eN^N$qw7@A|QC`XCVbN=y z(_wZnjqRfV?*=|ArYC5a9efSwiczLdvu^_?(!6pkCf4cE=9Mp!x}Aslyz(AT@HKm# zT$^1C=Hy&HxZ7$-weVfeE9WU6R4CMmG@}_gl1ef1ZI+cdhR-X<=6a!Dop^5womc*E zPP`{LYBmDCHe@82DR;ScXc-B_Hg|B^pGCxCOb9}sDf@sK%_kOHqH1diTRqms4w(FW zg1wUPqZ|85Jr}d#WFHvEG@blh7NJ>CoLXua%7S8so<=PT%5=Hi{jebm3LN%$f=^f1 zNrN-WS@xs?7L%f6L5aq7;)!KJ;q@u|i8)nC8$d;zeVb70uNx_%}#w;kL9PC~9 zWGPVm1jqcnrHNr7`Ez)R7-RmND%t1RA~t!BLX%g?pX2e#pEbC}XDaKZOA6*NPu47! zlTpBPUXbY#EOSaO&zU2rYs#GyjS%8el?*C*X@#sQql{gaQNx~3cS`$G+C`YqEkk^_ zmOtl^EOb49fXYr;{+#g4OW?u_Y4!5VO9;no$h=f2d%ZV_Ztq7D&%D$O(5Pfyf_k6) zd5u?mr_vdGy2K`}DV#5_oIX#!Hg_(&I%h8XMRpClIeVVGHoIPaCfgysm+cMio7O5q zmS2Xc|Gk*kzD~;SxLlgr>0_DxOi1e#AceBMEf5fn klI>0CG8wbIovh{m0V1QZSPF({QUCw|07*qoM6N<$g75|dX#fBK diff --git a/nym-connect/mobile/src-tauri/icons/Square89x89Logo.png b/nym-connect/mobile/src-tauri/icons/Square89x89Logo.png deleted file mode 100644 index 386c8bf1191ef69f472a2191a38be414402210f9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 7511 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%0A5=QlF!?KyxY%R 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]`~OPx$&v?Oqo%#4m^v>39w|i37EWr- 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 ?81%mHB8!%Ex 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

    2hCFsHI)*k<<%Jz5Pz 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 I2ll1^0g!omyZ`_I diff --git a/nym-connect/mobile/src-tauri/icons/icon.ico b/nym-connect/mobile/src-tauri/icons/icon.ico deleted file mode 100644 index 6164f98f9cc6a4a53f0d5f8ce20ef179dafd1f39..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 40889 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ⅅQ( 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%0A^WcmxXkUtX;j*faAaO(FC5H{# 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 a*YeQ2QS2Caq|Gqc!TV|gt-yag^}hh+GmxzS diff --git a/nym-connect/mobile/src-tauri/icons/icon.png b/nym-connect/mobile/src-tauri/icons/icon.png deleted file mode 100644 index fa552c309af717e94676269720116adabf3f744f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 61553 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 diff --git a/nym-connect/mobile/src-tauri/icons/tray_icon.png b/nym-connect/mobile/src-tauri/icons/tray_icon.png deleted file mode 100644 index afcb965219b76ab9ca18e89489d08e028245602f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 56946 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 diff --git a/nym-connect/mobile/src-tauri/src/build.rs b/nym-connect/mobile/src-tauri/src/build.rs deleted file mode 100644 index 261851f6b6..0000000000 --- a/nym-connect/mobile/src-tauri/src/build.rs +++ /dev/null @@ -1,3 +0,0 @@ -fn main() { - tauri_build::build(); -} diff --git a/nym-connect/mobile/src-tauri/src/config/mod.rs b/nym-connect/mobile/src-tauri/src/config/mod.rs deleted file mode 100644 index ba0d2ebf13..0000000000 --- a/nym-connect/mobile/src-tauri/src/config/mod.rs +++ /dev/null @@ -1,160 +0,0 @@ -use crate::{ - error::{BackendError, Result}, - state::State, -}; -use nym_client_core::{client::key_manager::KeyManager, config::Config as BaseClientConfig}; -use nym_config_common::NymConfig; -use nym_credential_storage::ephemeral_storage::EphemeralStorage; -use nym_crypto::asymmetric::identity; -use nym_socks5_client_core::config::{Config as Socks5Config, Socks5}; -use std::path::PathBuf; -use std::sync::Arc; -use tap::TapFallible; -use tokio::sync::RwLock; - -static SOCKS5_CONFIG_ID: &str = "nym-connect"; - -pub fn socks5_config_id_appended_with(gateway_id: &str) -> Result { - use std::fmt::Write as _; - let mut id = SOCKS5_CONFIG_ID.to_string(); - write!(id, "-{gateway_id}")?; - Ok(id) -} - -#[tauri::command] -pub async fn get_config_id(state: tauri::State<'_, Arc>>) -> Result { - state.read().await.get_config_id() -} - -#[tauri::command] -pub fn get_config_file_location() -> Result { - Err(BackendError::CouldNotGetConfigFilename) -} - -#[derive(Debug)] -pub struct Config { - pub socks5: Socks5Config, -} - -impl Config { - pub fn new>(id: S, provider_mix_address: S) -> Self { - Config { - socks5: Socks5Config::new(id, provider_mix_address), - } - } - - #[allow(unused)] - pub fn new_with_port>(id: S, provider_mix_address: S, port: u16) -> Self { - Config { - socks5: Socks5Config::new(id, provider_mix_address).with_port(port), - } - } - - pub fn get_config(&self) -> &Socks5Config { - &self.socks5 - } - - pub fn get_socks5(&self) -> &Socks5 { - self.socks5.get_socks5() - } - - #[allow(unused)] - pub fn get_socks5_mut(&mut self) -> &mut Socks5 { - self.socks5.get_socks5_mut() - } - - pub fn get_base(&self) -> &BaseClientConfig { - self.socks5.get_base() - } - - pub fn get_base_mut(&mut self) -> &mut BaseClientConfig { - self.socks5.get_base_mut() - } - - pub async fn init( - service_provider: &str, - chosen_gateway_id: &str, - ) -> Result<(Config, KeyManager)> { - log::info!("Initialising..."); - - let service_provider = service_provider.to_owned(); - let chosen_gateway_id = chosen_gateway_id.to_owned(); - - // The client initialization was originally not written for this use case, so there are - // lots of ways it can panic. Until we have proper error handling in the init code for the - // clients we'll catch any panics here by spawning a new runtime in a separate thread. - let (config, keys) = std::thread::spawn(move || { - tokio::runtime::Runtime::new() - .expect("Failed to create tokio runtime") - .block_on( - async move { init_socks5_config(service_provider, chosen_gateway_id).await }, - ) - }) - .join() - .map_err(|_| BackendError::InitializationPanic)??; - - log::info!("Configuration saved 🚀"); - Ok((config, keys)) - } -} - -pub async fn init_socks5_config( - provider_address: String, - chosen_gateway_id: String, -) -> Result<(Config, KeyManager)> { - log::trace!("Initialising client..."); - let mut config = Config::new(SOCKS5_CONFIG_ID, &provider_address); - - if let Ok(raw_validators) = std::env::var(nym_config_common::defaults::var_names::NYM_API) { - config - .get_base_mut() - .set_custom_nym_apis(nym_config_common::parse_urls(&raw_validators)); - } - - let nym_api_endpoints = config.get_base().get_nym_api_endpoints(); - - let chosen_gateway_id = identity::PublicKey::from_base58_string(chosen_gateway_id) - .map_err(|_| BackendError::UnableToParseGateway)?; - - let mut key_manager = nym_client_core::init::new_client_keys(); - - // Setup gateway and register a new key each time - let gateway = nym_client_core::init::register_with_gateway::( - &mut key_manager, - nym_api_endpoints, - Some(chosen_gateway_id), - false, - ) - .await?; - - config.get_base_mut().set_gateway_endpoint(gateway); - - print_saved_config(&config); - - let address = *key_manager.identity_keypair().public_key(); - log::info!("The address of this client is: {}", address); - - Ok((config, key_manager)) -} - -fn print_saved_config(config: &Config) { - log::info!( - "Saved configuration file to {:?}", - config.get_config().get_config_file_save_location() - ); - log::info!("Gateway id: {}", config.get_base().get_gateway_id()); - log::info!("Gateway owner: {}", config.get_base().get_gateway_owner()); - log::info!( - "Gateway listener: {}", - config.get_base().get_gateway_listener() - ); - log::info!( - "Service provider address: {}", - config.get_socks5().get_provider_mix_address() - ); - log::info!( - "Service provider port: {}", - config.get_socks5().get_listening_port() - ); - log::info!("Client configuration completed."); -} diff --git a/nym-connect/mobile/src-tauri/src/error.rs b/nym-connect/mobile/src-tauri/src/error.rs deleted file mode 100644 index 58f2252ed1..0000000000 --- a/nym-connect/mobile/src-tauri/src/error.rs +++ /dev/null @@ -1,86 +0,0 @@ -use nym_client_core::error::ClientCoreError; -use serde::{Serialize, Serializer}; -use thiserror::Error; - -#[allow(unused)] -#[derive(Error, Debug)] -pub enum BackendError { - #[error("{source}")] - ReqwestError { - #[from] - source: reqwest::Error, - }, - #[error("I/O error: {source}")] - IoError { - #[from] - source: std::io::Error, - }, - #[error("string formatting error: {source}")] - FmtError { - #[from] - source: std::fmt::Error, - }, - #[error("tauri error: {source}")] - TauriError { - #[from] - source: tauri::Error, - }, - #[error("{source}")] - TauriApiError { - #[from] - source: tauri::api::Error, - }, - #[error("{source}")] - SerdeJsonError { - #[from] - source: serde_json::Error, - }, - #[error("{source}")] - ClientCoreError { - #[from] - source: ClientCoreError, - }, - - #[error("could not send disconnect signal to the SOCKS5 client")] - CoundNotSendDisconnectSignal, - #[error("no service provider set")] - NoServiceProviderSet, - #[error("no gateway provider set")] - NoGatewaySet, - #[error("initialization failed with a panic")] - InitializationPanic, - #[error("could not get config id before gateway is set")] - CouldNotGetIdWithoutGateway, - #[error("could not initialize without gateway set")] - CouldNotInitWithoutGateway, - #[error("could not initialize without service provider set")] - CouldNotInitWithoutServiceProvider, - #[error("could not get file name")] - CouldNotGetFilename, - #[error("could not get config file location")] - CouldNotGetConfigFilename, - #[error("could not load existing gateway configuration")] - CouldNotLoadExistingGatewayConfiguration(std::io::Error), - #[error("unable to open a new window")] - NewWindowError, - #[error("unable to parse the specified gateway")] - UnableToParseGateway, - - #[error("HTTP get request failed: {status_code}")] - RequestFail { - url: reqwest::Url, - status_code: reqwest::StatusCode, - }, -} - -impl Serialize for BackendError { - fn serialize(&self, serializer: S) -> std::result::Result - where - S: Serializer, - { - serializer.collect_str(self) - } -} - -// Local crate level Result alias -pub(crate) type Result = std::result::Result; diff --git a/nym-connect/mobile/src-tauri/src/events.rs b/nym-connect/mobile/src-tauri/src/events.rs deleted file mode 100644 index fb9186913f..0000000000 --- a/nym-connect/mobile/src-tauri/src/events.rs +++ /dev/null @@ -1,71 +0,0 @@ -use std::sync::Arc; - -use nym_client_core::error::ClientCoreStatusMessage; -use nym_task::manager::TaskStatus; -use tauri::async_runtime::RwLock; - -use crate::{ - state::{GatewayConnectivity, State}, - tasks, -}; - -#[derive(Clone, serde::Serialize)] -struct Payload { - title: String, - message: String, -} - -impl Payload { - fn new(title: String, message: String) -> Self { - Self { title, message } - } -} - -pub fn emit_event(event: &str, title: &str, msg: &str, window: &tauri::Window) { - if let Err(err) = window.emit(event, Payload::new(title.into(), msg.into())) { - log::error!("Failed to emit tauri event: {err}"); - } -} - -pub fn emit_status_event(event: &str, msg: &T, window: &tauri::Window) { - if let Err(err) = window.emit(event, Payload::new("SOCKS5 update".into(), msg.to_string())) { - log::error!("Failed to emit tauri event: {err}"); - } -} - -pub async fn handle_task_status( - task_status: &TaskStatus, - state: &Arc>, - window: &tauri::Window, -) { - match task_status { - TaskStatus::Ready => { - { - let mut state_w = state.write().await; - state_w.mark_connected(window); - } - - emit_status_event("socks5-connected-event", task_status, window); - tasks::start_connection_check(state.clone(), window.clone()); - } - } -} - -pub async fn handle_client_status_message( - client_status_message: &ClientCoreStatusMessage, - state: &Arc>, - window: &tauri::Window, -) { - // TODO: use this instead once we change on the frontend too - let _event_name = match client_status_message { - ClientCoreStatusMessage::GatewayIsSlow | ClientCoreStatusMessage::GatewayIsVerySlow => { - "socks5-gateway-status" - } - }; - - if let Ok(connectivity) = GatewayConnectivity::try_from(client_status_message) { - state.write().await.set_gateway_connectivity(connectivity); - } - - emit_status_event("socks5-status-event", client_status_message, window); -} diff --git a/nym-connect/mobile/src-tauri/src/lib.rs b/nym-connect/mobile/src-tauri/src/lib.rs deleted file mode 100644 index 6be3d9950d..0000000000 --- a/nym-connect/mobile/src-tauri/src/lib.rs +++ /dev/null @@ -1,88 +0,0 @@ -// Remove me after we've tidied up -#![allow(dead_code)] -#![allow(unused_imports)] - -use nym_config_common::defaults::setup_env; -use std::sync::Arc; -use tauri::{App, Manager}; -use tokio::sync::RwLock; - -pub mod config; -mod error; -mod events; -mod logging; -mod models; -pub mod operations; -mod state; -mod tasks; - -pub use state::State; - -#[cfg(mobile)] -mod mobile; -#[cfg(mobile)] -pub use mobile::*; - -pub type SetupHook = Box Result<(), Box> + Send>; - -#[derive(Default)] -pub struct AppBuilder { - setup: Option, -} - -impl AppBuilder { - pub fn new() -> Self { - Self::default() - } - - #[must_use] - pub fn setup(mut self, setup: F) -> Self - where - F: FnOnce(&mut App) -> Result<(), Box> + Send + 'static, - { - self.setup.replace(Box::new(setup)); - self - } - - pub fn run(self) { - setup_env(None); - - println!("Starting up***"); - - // As per breaking change description here - // https://github.com/tauri-apps/tauri/blob/feac1d193c6d618e49916ad0707201f43d5cdd36/tooling/bundler/CHANGELOG.md - if let Err(error) = fix_path_env::fix() { - log::warn!("Failed to fix PATH: {error}"); - } - - let setup = self.setup; - tauri::Builder::default() - .manage(Arc::new(RwLock::new(State::default()))) - .invoke_handler(tauri::generate_handler![ - crate::config::get_config_file_location, - crate::config::get_config_id, - crate::operations::connection::connect::get_gateway, - crate::operations::connection::connect::get_service_provider, - crate::operations::connection::connect::set_gateway, - crate::operations::connection::connect::set_service_provider, - crate::operations::connection::connect::start_connecting, - crate::operations::connection::disconnect::start_disconnecting, - crate::operations::connection::status::get_connection_health_check_status, - crate::operations::connection::status::get_connection_status, - crate::operations::connection::status::get_gateway_connection_status, - crate::operations::connection::status::start_connection_health_check_task, - crate::operations::directory::get_services, - crate::operations::directory::get_gateways_detailed, - crate::operations::export::export_keys, - ]) - .setup(move |app| { - if let Some(setup) = setup { - logging::setup_logging(app.app_handle())?; - (setup)(app)?; - } - Ok(()) - }) - .run(tauri::generate_context!()) - .expect("error while running tauri application"); - } -} diff --git a/nym-connect/mobile/src-tauri/src/logging.rs b/nym-connect/mobile/src-tauri/src/logging.rs deleted file mode 100644 index 3a6ee89e62..0000000000 --- a/nym-connect/mobile/src-tauri/src/logging.rs +++ /dev/null @@ -1,147 +0,0 @@ -use fern::colors::{Color, ColoredLevelConfig}; -use serde::Serialize; -use serde_repr::{Deserialize_repr, Serialize_repr}; -use std::str::FromStr; -use tauri::Manager; -use time::{format_description, OffsetDateTime}; - -fn formatted_time() -> String { - // if we fail to obtain local time according to local offset, fallback to utc - let _now = OffsetDateTime::now_local().unwrap_or_else(|_| OffsetDateTime::now_utc()); - - // if we're running it in the unit test, use the hardcoded value - #[cfg(test)] - let _now = OffsetDateTime::from_unix_timestamp(1666666666).unwrap(); - - // the unwraps are fine as we know this description is correct - // note: the reason for this very particular format is a very simple one - // it's what we've always been using since we copied it from the example, - // so feel free to update it to whatever - let format = - format_description::parse("[[[year]-[month]-[day]][[[hour]:[minute]:[second]]").unwrap(); - _now.format(&format).unwrap() -} - -pub fn setup_logging(app_handle: tauri::AppHandle) -> Result<(), log::SetLoggerError> { - let colors = ColoredLevelConfig::new() - .trace(Color::Magenta) - .debug(Color::Blue) - .info(Color::Green) - .warn(Color::Yellow) - .error(Color::Red); - let base_config = fern::Dispatch::new() - .level(global_level()) - .filter_lowlevel_external_components() - .show_operations(); - - let stdout_config = fern::Dispatch::new() - .format(move |out, message, record| { - out.finish(format_args!( - "{} {:5} {} > {}", - formatted_time(), - colors.color(record.level()), - record.target(), - message, - )) - }) - .chain(std::io::stdout()); - - let tauri_event_config = fern::Dispatch::new() - .format(move |out, message, record| { - out.finish(format_args!( - "{}[{}] {}", - formatted_time(), - record.target(), - message, - )) - }) - .chain(fern::Output::call(move |record| { - let msg = LogMessage { - message: record.args().to_string(), - level: record.level().into(), - }; - app_handle.emit_all("log://log", msg).unwrap(); - })); - - base_config - .chain(stdout_config) - .chain(tauri_event_config) - .apply() -} - -trait FernExt { - fn show_operations(self) -> Self; - fn filter_lowlevel_external_components(self) -> Self; -} - -impl FernExt for fern::Dispatch { - fn show_operations(self) -> Self { - if ::std::env::var("RUST_TRACE_OPERATIONS").is_ok() { - self.level_for("nym_connect::operations", log::LevelFilter::Trace) - } else { - self - } - } - - fn filter_lowlevel_external_components(self) -> Self { - self.level_for("hyper", log::LevelFilter::Warn) - .level_for("tokio_reactor", log::LevelFilter::Warn) - .level_for("reqwest", log::LevelFilter::Warn) - .level_for("mio", log::LevelFilter::Warn) - .level_for("want", log::LevelFilter::Warn) - .level_for("sled", log::LevelFilter::Warn) - .level_for("tungstenite", log::LevelFilter::Warn) - .level_for("tokio_tungstenite", log::LevelFilter::Warn) - .level_for("rustls", log::LevelFilter::Warn) - .level_for("tokio_util", log::LevelFilter::Warn) - } -} - -fn global_level() -> log::LevelFilter { - if let Ok(s) = ::std::env::var("RUST_LOG") { - log::LevelFilter::from_str(&s).unwrap_or(log::LevelFilter::Info) - } else { - log::LevelFilter::Info - } -} - -#[derive(Debug, Serialize, Clone)] -struct LogMessage { - message: String, - level: LogLevel, -} - -// Serialize to u16 instead of strings. -#[derive(Debug, Clone, Deserialize_repr, Serialize_repr)] -#[repr(u16)] -enum LogLevel { - Trace = 1, - Debug, - Info, - Warn, - Error, -} - -impl From for LogLevel { - fn from(level: log::Level) -> Self { - match level { - log::Level::Trace => LogLevel::Trace, - log::Level::Debug => LogLevel::Debug, - log::Level::Info => LogLevel::Info, - log::Level::Warn => LogLevel::Warn, - log::Level::Error => LogLevel::Error, - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn log_formatting() { - let expected_chrono_formated = "[2022-10-25][02:57:46]".to_string(); - let new_time_based = formatted_time(); - assert_eq!(new_time_based, expected_chrono_formated) - } -} diff --git a/nym-connect/mobile/src-tauri/src/main.rs b/nym-connect/mobile/src-tauri/src/main.rs deleted file mode 100644 index 48484ea1dd..0000000000 --- a/nym-connect/mobile/src-tauri/src/main.rs +++ /dev/null @@ -1,10 +0,0 @@ -#![cfg_attr( - all(not(debug_assertions), target_os = "windows"), - windows_subsystem = "windows" -)] - -use nym_connect::AppBuilder; - -fn main() { - AppBuilder::new().run(); -} diff --git a/nym-connect/mobile/src-tauri/src/mobile.rs b/nym-connect/mobile/src-tauri/src/mobile.rs deleted file mode 100644 index d231976e68..0000000000 --- a/nym-connect/mobile/src-tauri/src/mobile.rs +++ /dev/null @@ -1,4 +0,0 @@ -#[tauri::mobile_entry_point] -fn main() { - super::AppBuilder::new().run(); -} diff --git a/nym-connect/mobile/src-tauri/src/models/mod.rs b/nym-connect/mobile/src-tauri/src/models/mod.rs deleted file mode 100644 index 70b408dc7b..0000000000 --- a/nym-connect/mobile/src-tauri/src/models/mod.rs +++ /dev/null @@ -1,116 +0,0 @@ -use core::fmt; - -use serde::{Deserialize, Serialize}; - -use crate::state::GatewayConnectivity; - -#[cfg_attr(test, derive(ts_rs::TS))] -#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug)] -pub struct ConnectResult { - pub address: String, -} - -#[cfg_attr(test, derive(ts_rs::TS))] -#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug)] -pub struct DisconnectResult { - pub success: bool, -} - -#[cfg_attr(test, derive(ts_rs::TS))] -#[cfg_attr(test, ts(rename_all = "lowercase"))] -#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug, Default)] -#[serde(rename_all = "camelCase")] -pub enum ConnectionStatusKind { - #[default] - Disconnected, - Disconnecting, - Connected, - Connecting, -} - -#[cfg_attr(test, derive(ts_rs::TS))] -#[cfg_attr(test, ts(rename_all = "lowercase"))] -#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug)] -#[serde(rename_all = "camelCase")] -pub enum GatewayConnectionStatusKind { - Good, - Bad, - VeryBad, -} - -impl From for GatewayConnectionStatusKind { - fn from(conn: GatewayConnectivity) -> Self { - match conn { - GatewayConnectivity::Good => GatewayConnectionStatusKind::Good, - GatewayConnectivity::Bad { .. } => GatewayConnectionStatusKind::Bad, - GatewayConnectivity::VeryBad { .. } => GatewayConnectionStatusKind::VeryBad, - } - } -} - -#[cfg_attr(test, derive(ts_rs::TS))] -#[cfg_attr(test, ts(rename_all = "lowercase"))] -#[derive(Serialize, Deserialize, Clone, Copy, PartialEq, Eq, Debug, Default)] -pub enum ConnectivityTestResult { - #[default] - NotAvailable, - Success, - Fail, -} - -impl fmt::Display for ConnectionStatusKind { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match *self { - ConnectionStatusKind::Disconnected => write!(f, "Disconnected"), - ConnectionStatusKind::Disconnecting => write!(f, "Disconnecting"), - ConnectionStatusKind::Connected => write!(f, "Connected"), - ConnectionStatusKind::Connecting => write!(f, "Connecting"), - } - } -} - -pub const APP_EVENT_CONNECTION_STATUS_CHANGED: &str = "app:connection-status-changed"; - -#[cfg_attr(test, derive(ts_rs::TS))] -#[derive(Clone, Serialize)] -pub struct AppEventConnectionStatusChangedPayload { - pub status: ConnectionStatusKind, -} - -#[cfg_attr(test, derive(ts_rs::TS))] -#[derive(Clone, Debug, Serialize, Deserialize)] -pub struct DirectoryService { - pub id: String, - pub description: String, - pub items: Vec, -} - -#[derive(Debug, Clone, Deserialize, Serialize)] -pub struct HarbourMasterService { - pub service_provider_client_id: String, - pub gateway_identity_key: String, - pub ip_address: String, - pub last_successful_ping_utc: String, - pub last_updated_utc: String, - pub routing_score: f32, -} - -#[cfg_attr(test, derive(ts_rs::TS))] -#[derive(Clone, Debug, Serialize, Deserialize)] -pub struct DirectoryServiceProvider { - pub id: String, - pub description: String, - /// Address of the network requester in the form "." - /// e.g. DpB3cHAchJiNBQi5FrZx2csXb1mrHkpYh9Wzf8Rjsuko.ANNWrvHqMYuertHGHUrZdBntQhpzfbWekB39qez9U2Vx@2BuMSfMW3zpeAjKXyKLhmY4QW1DXurrtSPEJ6CjX3SEh - pub address: String, - /// Address of the gateway, e.g. 2BuMSfMW3zpeAjKXyKLhmY4QW1DXurrtSPEJ6CjX3SEh - pub gateway: String, -} - -#[derive(Debug, Clone, Deserialize, Serialize)] -pub struct PagedResult { - pub page: u32, - pub size: u32, - pub total: i32, - pub items: Vec, -} diff --git a/nym-connect/mobile/src-tauri/src/operations/connection/connect.rs b/nym-connect/mobile/src-tauri/src/operations/connection/connect.rs deleted file mode 100644 index 1d83748698..0000000000 --- a/nym-connect/mobile/src-tauri/src/operations/connection/connect.rs +++ /dev/null @@ -1,68 +0,0 @@ -use crate::{ - error::{BackendError, Result}, - models::ConnectResult, - tasks, State, -}; -use std::sync::Arc; -use tokio::sync::RwLock; - -#[tauri::command] -pub async fn start_connecting( - state: tauri::State<'_, Arc>>, - window: tauri::Window, -) -> Result { - log::trace!("Start connecting"); - let (msg_receiver, exit_status_receiver) = { - let mut state_w = state.write().await; - state_w.start_connecting(&window).await? - }; - - // Setup task for checking status - let state = state.inner().clone(); - tasks::start_disconnect_listener(state.clone(), window.clone(), exit_status_receiver); - tasks::start_status_listener(state, window.clone(), msg_receiver); - - Ok(ConnectResult { - address: "PLACEHOLDER".to_string(), - }) -} - -#[tauri::command] -pub async fn get_service_provider(state: tauri::State<'_, Arc>>) -> Result { - let guard = state.read().await; - guard - .get_service_provider() - .clone() - .ok_or(BackendError::NoServiceProviderSet) -} - -#[tauri::command] -pub async fn set_service_provider( - service_provider: Option, - state: tauri::State<'_, Arc>>, -) -> Result<()> { - log::trace!("Setting service_provider: {:?}", &service_provider); - let mut guard = state.write().await; - guard.set_service_provider(service_provider); - Ok(()) -} - -#[tauri::command] -pub async fn get_gateway(state: tauri::State<'_, Arc>>) -> Result { - let guard = state.read().await; - guard - .get_gateway() - .clone() - .ok_or(BackendError::NoGatewaySet) -} - -#[tauri::command] -pub async fn set_gateway( - gateway: Option, - state: tauri::State<'_, Arc>>, -) -> Result<()> { - log::trace!("Setting gateway: {:?}", &gateway); - let mut guard = state.write().await; - guard.set_gateway(gateway); - Ok(()) -} diff --git a/nym-connect/mobile/src-tauri/src/operations/connection/disconnect.rs b/nym-connect/mobile/src-tauri/src/operations/connection/disconnect.rs deleted file mode 100644 index b75bf49410..0000000000 --- a/nym-connect/mobile/src-tauri/src/operations/connection/disconnect.rs +++ /dev/null @@ -1,20 +0,0 @@ -use crate::error::Result; -use crate::models::ConnectResult; -use crate::State; -use std::sync::Arc; -use tokio::sync::RwLock; - -#[tauri::command] -pub async fn start_disconnecting( - state: tauri::State<'_, Arc>>, - window: tauri::Window, -) -> Result { - log::trace!("Start disconnecting"); - let mut guard = state.write().await; - - guard.start_disconnecting(&window).await?; - - Ok(ConnectResult { - address: "PLACEHOLDER".to_string(), - }) -} diff --git a/nym-connect/mobile/src-tauri/src/operations/connection/health_check.rs b/nym-connect/mobile/src-tauri/src/operations/connection/health_check.rs deleted file mode 100644 index 3f401933a1..0000000000 --- a/nym-connect/mobile/src-tauri/src/operations/connection/health_check.rs +++ /dev/null @@ -1,26 +0,0 @@ -use serde::{Deserialize, Serialize}; - -static HEALTH_CHECK_URL: &str = "https://nymtech.net/.wellknown/connect/healthcheck.json"; - -#[derive(Serialize, Deserialize, Debug)] -struct ConnectionSuccess { - status: String, -} - -pub async fn run_health_check() -> bool { - log::info!("Running network health check"); - match crate::operations::http::socks5_get::<_, ConnectionSuccess>(HEALTH_CHECK_URL).await { - Ok(res) if res.status == "ok" => { - log::info!("Healthcheck success!"); - true - } - Ok(res) => { - log::error!("Healthcheck failed with status: {}", res.status); - false - } - Err(err) => { - log::error!("Healthcheck failed: {err}"); - false - } - } -} diff --git a/nym-connect/mobile/src-tauri/src/operations/connection/mod.rs b/nym-connect/mobile/src-tauri/src/operations/connection/mod.rs deleted file mode 100644 index 171d07cd83..0000000000 --- a/nym-connect/mobile/src-tauri/src/operations/connection/mod.rs +++ /dev/null @@ -1,4 +0,0 @@ -pub mod connect; -pub mod disconnect; -pub mod health_check; -pub mod status; diff --git a/nym-connect/mobile/src-tauri/src/operations/connection/status.rs b/nym-connect/mobile/src-tauri/src/operations/connection/status.rs deleted file mode 100644 index 7710524fb5..0000000000 --- a/nym-connect/mobile/src-tauri/src/operations/connection/status.rs +++ /dev/null @@ -1,44 +0,0 @@ -use crate::error::Result; -use crate::tasks; -use std::sync::Arc; - -use tokio::sync::RwLock; - -use crate::models::{ConnectionStatusKind, ConnectivityTestResult, GatewayConnectionStatusKind}; -use crate::state::State; - -#[tauri::command] -pub async fn get_connection_status( - state: tauri::State<'_, Arc>>, -) -> Result { - let state = state.read().await; - Ok(state.get_status()) -} - -#[tauri::command] -pub async fn get_gateway_connection_status( - state: tauri::State<'_, Arc>>, -) -> Result { - let mut state_w = state.write().await; - let gateway_connectivity = state_w.get_gateway_connectivity(); - Ok(gateway_connectivity.into()) -} - -#[tauri::command] -pub async fn get_connection_health_check_status( - state: tauri::State<'_, Arc>>, -) -> Result { - let state = state.read().await; - Ok(state.get_connectivity_test_result()) -} - -// Start a connection check task. This should return with an event within one minute, and update -// the state. -// Trying to run multiple concurrent connection checks probably works but is not supported. -#[tauri::command] -pub fn start_connection_health_check_task( - state: tauri::State<'_, Arc>>, - window: tauri::Window, -) { - tasks::start_connection_check(state.inner().clone(), window); -} diff --git a/nym-connect/mobile/src-tauri/src/operations/directory/mod.rs b/nym-connect/mobile/src-tauri/src/operations/directory/mod.rs deleted file mode 100644 index 0e58035274..0000000000 --- a/nym-connect/mobile/src-tauri/src/operations/directory/mod.rs +++ /dev/null @@ -1,92 +0,0 @@ -use itertools::Itertools; - -use crate::error::Result; -use crate::models::{ - DirectoryService, DirectoryServiceProvider, HarbourMasterService, PagedResult, -}; -use nym_api_requests::models::GatewayBondAnnotated; -use nym_contracts_common::types::Percent; - -static SERVICE_PROVIDER_WELLKNOWN_URL: &str = - "https://nymtech.net/.wellknown/connect/service-providers.json"; - -static HARBOUR_MASTER_URL: &str = "https://harbourmaster.nymtech.net/v1/services/?size=100"; - -static GATEWAYS_DETAILED_URL: &str = - "https://validator.nymtech.net/api/v1/status/gateways/detailed"; - -#[tauri::command] -pub async fn get_services() -> Result> { - log::trace!("Fetching services"); - let services_res = reqwest::get(SERVICE_PROVIDER_WELLKNOWN_URL) - .await? - .json::>() - .await?; - log::trace!("Received: {:#?}", services_res); - - log::trace!("Fetching gateways"); - let gateway_res = reqwest::get(GATEWAYS_DETAILED_URL) - .await? - .json::>() - .await?; - log::trace!("Received: {:#?}", gateway_res); - - // TODO: get paged - log::trace!("Fetching active services"); - let active_services = reqwest::get(HARBOUR_MASTER_URL) - .await? - .json::>() - .await?; - log::trace!("Active: {:#?}", active_services); - - let mut filtered: Vec = vec![]; - - for service in &services_res { - let items: _ = service - .items - .clone() - .into_iter() - .filter(|sp| { - active_services - .items - .iter() - .any(|active| active.service_provider_client_id == sp.address) - }) - .collect_vec(); - log::trace!("service = {} has {} items", service.id, items.len()); - filtered.push(DirectoryService { - id: service.id.clone(), - description: service.description.clone(), - items, - }) - } - - let perf_threshold = Percent::from_percentage_value(90).unwrap(); - - // Use only services that are active AND have a performance of >= 90% - let services_with_good_performance: Vec = filtered - .iter_mut() - .fold(vec![], |mut acc, sp| { - acc.append(&mut sp.items); - acc - }) - .into_iter() - .filter(|sp| { - gateway_res.iter().any(|gateway| { - gateway.gateway_bond.gateway.identity_key == sp.gateway - && gateway.performance >= perf_threshold - }) - }) - .collect(); - - Ok(services_with_good_performance) -} - -#[tauri::command] -pub async fn get_gateways_detailed() -> Result> { - let res = reqwest::get(GATEWAYS_DETAILED_URL) - .await? - .json::>() - .await?; - Ok(res) -} diff --git a/nym-connect/mobile/src-tauri/src/operations/export.rs b/nym-connect/mobile/src-tauri/src/operations/export.rs deleted file mode 100644 index dea876fdd8..0000000000 --- a/nym-connect/mobile/src-tauri/src/operations/export.rs +++ /dev/null @@ -1,91 +0,0 @@ -use std::{ffi::OsStr, fs, sync::Arc}; -use tokio::sync::RwLock; - -use crate::{ - error::{BackendError, Result}, - state::State, -}; -use nym_client_core::client::key_manager::KeyManager; -use nym_client_core::config::persistence::keys_paths::ClientKeysPaths; -use nym_crypto::asymmetric::identity; - -pub async fn get_identity_key( - state: &tauri::State<'_, Arc>>, -) -> Result> { - let config = { - let state = state.read().await; - state.load_socks5_config()? - }; - - let paths = ClientKeysPaths::new_from_config(config.get_base()); - let key_manager = KeyManager::load_keys(&paths)?; - let identity_keypair = key_manager.identity_keypair(); - - Ok(identity_keypair) -} - -/// Export the gateway keys as a JSON string blob -#[tauri::command] -pub async fn export_keys(state: tauri::State<'_, Arc>>) -> Result { - let config = { - let state = state.read().await; - state.load_socks5_config()? - }; - - // Get key paths - let ack_key_file = config.get_base().get_ack_key_file(); - let gateway_shared_key_file = config.get_base().get_gateway_shared_key_file(); - - let pub_id_key_file = config.get_base().get_public_identity_key_file(); - let priv_id_key_file = config.get_base().get_private_identity_key_file(); - - let pub_enc_key_file = config.get_base().get_public_encryption_key_file(); - let priv_enc_key_file = config.get_base().get_private_encryption_key_file(); - - // Read file contents - let ack_key = fs::read_to_string(ack_key_file.clone())?; - let gateway_shared_key = fs::read_to_string(gateway_shared_key_file.clone())?; - - let pub_id_key = fs::read_to_string(pub_id_key_file.clone())?; - let priv_id_key = fs::read_to_string(priv_id_key_file.clone())?; - - let pub_enc_key = fs::read_to_string(pub_enc_key_file.clone())?; - let priv_enc_key = fs::read_to_string(priv_enc_key_file.clone())?; - - let ack_key_file = ack_key_file - .file_name() - .map(OsStr::to_string_lossy) - .ok_or(BackendError::CouldNotGetFilename)?; - let gateway_shared_key_file = gateway_shared_key_file - .file_name() - .map(OsStr::to_string_lossy) - .ok_or(BackendError::CouldNotGetFilename)?; - let pub_id_key_file = pub_id_key_file - .file_name() - .map(OsStr::to_string_lossy) - .ok_or(BackendError::CouldNotGetFilename)?; - let priv_id_key_file = priv_id_key_file - .file_name() - .map(OsStr::to_string_lossy) - .ok_or(BackendError::CouldNotGetFilename)?; - let pub_enc_key_file = pub_enc_key_file - .file_name() - .map(OsStr::to_string_lossy) - .ok_or(BackendError::CouldNotGetFilename)?; - let priv_enc_key_file = priv_enc_key_file - .file_name() - .map(OsStr::to_string_lossy) - .ok_or(BackendError::CouldNotGetFilename)?; - - // Format and return as json - let json = serde_json::json!({ - ack_key_file: ack_key, - gateway_shared_key_file: gateway_shared_key, - pub_id_key_file: pub_id_key, - priv_id_key_file: priv_id_key, - pub_enc_key_file: pub_enc_key, - priv_enc_key_file: priv_enc_key, - }); - - Ok(serde_json::to_string_pretty(&json)?) -} diff --git a/nym-connect/mobile/src-tauri/src/operations/help/log.rs b/nym-connect/mobile/src-tauri/src/operations/help/log.rs deleted file mode 100644 index 5b31ea023e..0000000000 --- a/nym-connect/mobile/src-tauri/src/operations/help/log.rs +++ /dev/null @@ -1,31 +0,0 @@ -use crate::error::BackendError; -use tauri::Manager; - -#[cfg(desktop)] -#[tauri::command] -pub fn help_log_toggle_window(app_handle: tauri::AppHandle) -> Result<(), BackendError> { - if let Some(current_log_window) = app_handle.windows().get("log") { - log::info!("Closing log window..."); - if let Err(e) = current_log_window.close() { - log::error!("Unable to close log window: {:?}", e); - } - return Ok(()); - } - - log::info!("Creating log window..."); - match tauri::WindowBuilder::new(&app_handle, "log", tauri::WindowUrl::App("log.html".into())) - .title("Nym Connect Logs") - .build() - { - Ok(window) => { - if let Err(e) = window.set_focus() { - log::error!("Unable to focus log window: {:?}", e); - } - Ok(()) - } - Err(e) => { - log::error!("Unable to create log window: {:?}", e); - Err(BackendError::NewWindowError) - } - } -} diff --git a/nym-connect/mobile/src-tauri/src/operations/help/mod.rs b/nym-connect/mobile/src-tauri/src/operations/help/mod.rs deleted file mode 100644 index 52dafe6d0f..0000000000 --- a/nym-connect/mobile/src-tauri/src/operations/help/mod.rs +++ /dev/null @@ -1,2 +0,0 @@ -pub mod log; -pub mod storage; diff --git a/nym-connect/mobile/src-tauri/src/operations/help/storage.rs b/nym-connect/mobile/src-tauri/src/operations/help/storage.rs deleted file mode 100644 index ab47ffa529..0000000000 --- a/nym-connect/mobile/src-tauri/src/operations/help/storage.rs +++ /dev/null @@ -1,20 +0,0 @@ -use crate::error::BackendError; -use serde::Serialize; -use tauri::Manager; - -#[derive(Debug, Serialize, Clone)] -struct ClearStorageEvent { - kind: String, -} - -#[tauri::command] -pub fn help_clear_storage(app_handle: tauri::AppHandle) -> Result<(), BackendError> { - log::info!("Sending event to clear local storage..."); - - let event = ClearStorageEvent { - kind: "local_storage".to_string(), - }; - app_handle.emit_all("help://clear-storage", event)?; - - Ok(()) -} diff --git a/nym-connect/mobile/src-tauri/src/operations/http.rs b/nym-connect/mobile/src-tauri/src/operations/http.rs deleted file mode 100644 index 5cfe3e1f53..0000000000 --- a/nym-connect/mobile/src-tauri/src/operations/http.rs +++ /dev/null @@ -1,37 +0,0 @@ -use crate::error::{BackendError, Result}; -use serde::de::DeserializeOwned; -use tap::TapFallible; - -pub async fn socks5_get(url: U) -> Result -where - U: reqwest::IntoUrl + std::fmt::Display, - T: DeserializeOwned, -{ - log::info!(">>> GET {url}"); - let proxy = reqwest::Proxy::all("socks5h://127.0.0.1:1080")?; - let client = reqwest::Client::builder() - .proxy(proxy) - .timeout(std::time::Duration::from_secs(20)) - .build()?; - - let resp = client.get(url).send().await.tap_err(|err| { - log::error!("<<< Request send error: {err}"); - })?; - - if resp.status().is_client_error() || resp.status().is_server_error() { - log::error!("<<< {}", resp.status()); - return Err(BackendError::RequestFail { - url: resp.url().clone(), - status_code: resp.status(), - }); - } - - let response_body = resp.text().await.tap_err(|err| { - log::error!("<<< Request error: {err}"); - })?; - log::info!("<<< {response_body}"); - - Ok(serde_json::from_str(&response_body).tap_err(|err| { - log::error!("<<< JSON parsing error: {err}"); - })?) -} diff --git a/nym-connect/mobile/src-tauri/src/operations/mod.rs b/nym-connect/mobile/src-tauri/src/operations/mod.rs deleted file mode 100644 index 7e8f09657b..0000000000 --- a/nym-connect/mobile/src-tauri/src/operations/mod.rs +++ /dev/null @@ -1,5 +0,0 @@ -pub mod connection; -pub mod directory; -pub mod export; -pub mod help; -pub mod http; diff --git a/nym-connect/mobile/src-tauri/src/state.rs b/nym-connect/mobile/src-tauri/src/state.rs deleted file mode 100644 index 36ec356779..0000000000 --- a/nym-connect/mobile/src-tauri/src/state.rs +++ /dev/null @@ -1,262 +0,0 @@ -use std::time::Duration; - -use ::nym_config_common::NymConfig; -use futures::SinkExt; -use nym_client_core::client::key_manager::KeyManager; -use nym_client_core::error::ClientCoreStatusMessage; -use tap::TapFallible; -use tauri::Manager; - -use nym_socks5_client_core::{ - config::Config as Socks5Config, Socks5ControlMessage, Socks5ControlMessageSender, -}; -use tokio::time::Instant; - -use crate::{ - config::{self, socks5_config_id_appended_with, Config}, - error::{BackendError, Result}, - models::{ - AppEventConnectionStatusChangedPayload, ConnectionStatusKind, ConnectivityTestResult, - APP_EVENT_CONNECTION_STATUS_CHANGED, - }, - tasks::{self, ExitStatusReceiver}, -}; - -// The client will emit messages if the connection to the gateway is poor (or the gateway can't -// keep up with the messages we are sendind). If no messages about this has been received for a -// certain duration then we assume it's all good. -const GATEWAY_CONNECTIVITY_TIMEOUT_SECS: u64 = 20; - -#[derive(Clone, Copy, Default)] -pub enum GatewayConnectivity { - #[default] - Good, - Bad { - when: Instant, - }, - VeryBad { - when: Instant, - }, -} - -impl TryFrom<&ClientCoreStatusMessage> for GatewayConnectivity { - type Error = BackendError; - - fn try_from(value: &ClientCoreStatusMessage) -> Result { - let conn = match value { - ClientCoreStatusMessage::GatewayIsSlow => GatewayConnectivity::Bad { - when: Instant::now(), - }, - ClientCoreStatusMessage::GatewayIsVerySlow => GatewayConnectivity::VeryBad { - when: Instant::now(), - }, - }; - Ok(conn) - } -} - -#[derive(Default)] -pub struct State { - /// The current connection status - status: ConnectionStatusKind, - - /// The service provider - service_provider: Option, - - /// The gateway used. Note that this is also used to create the configuration id - gateway: Option, - - /// Channel that is used to send command messages to the SOCKS5 client, such as to disconnect - socks5_client_sender: Option, - - /// The client will periodically report connectivity to the gateway it's connected to. Unless - /// we get a status message from the client we assume it's good. - gateway_connectivity: GatewayConnectivity, - - /// The latest end-to-end connectivity test result. The first test is initiated on connection - /// established. Additional tests can be triggered. - connectivity_test_result: ConnectivityTestResult, -} - -impl State { - pub fn new() -> Self { - State { - status: ConnectionStatusKind::Disconnected, - service_provider: None, - gateway: None, - socks5_client_sender: None, - gateway_connectivity: GatewayConnectivity::Good, - connectivity_test_result: ConnectivityTestResult::NotAvailable, - } - } - - pub fn get_gateway_connectivity(&mut self) -> GatewayConnectivity { - self.gateway_connectivity = match self.gateway_connectivity { - c @ (GatewayConnectivity::Bad { when } | GatewayConnectivity::VeryBad { when }) => { - if Instant::now() > when + Duration::from_secs(GATEWAY_CONNECTIVITY_TIMEOUT_SECS) { - GatewayConnectivity::Good - } else { - c - } - } - current => current, - }; - self.gateway_connectivity - } - - pub fn set_gateway_connectivity(&mut self, gateway_connectivity: GatewayConnectivity) { - self.gateway_connectivity = gateway_connectivity - } - - pub fn get_connectivity_test_result(&self) -> ConnectivityTestResult { - self.connectivity_test_result - } - - pub fn set_connectivity_test_result( - &mut self, - connectivity_test_result: ConnectivityTestResult, - ) { - self.connectivity_test_result = connectivity_test_result; - } - - pub fn get_status(&self) -> ConnectionStatusKind { - self.status.clone() - } - - fn set_state(&mut self, status: ConnectionStatusKind, window: &tauri::Window) { - log::info!("{status}"); - self.status = status.clone(); - window - .emit_all( - APP_EVENT_CONNECTION_STATUS_CHANGED, - AppEventConnectionStatusChangedPayload { status }, - ) - .tap_err(|err| log::warn!("{err}")) - .ok(); - } - - pub fn get_service_provider(&self) -> &Option { - &self.service_provider - } - - pub fn set_service_provider(&mut self, provider: Option) { - self.service_provider = provider; - } - - pub fn get_gateway(&self) -> &Option { - &self.gateway - } - - pub fn set_gateway(&mut self, gateway: Option) { - self.gateway = gateway; - } - - /// The effective config id is the static config id appended with the id of the gateway - pub fn get_config_id(&self) -> Result { - self.get_gateway() - .as_ref() - .ok_or(BackendError::CouldNotGetIdWithoutGateway) - .and_then(|gateway_id| socks5_config_id_appended_with(gateway_id)) - } - - pub fn load_socks5_config(&self) -> Result { - let id = self.get_config_id()?; - let config = Socks5Config::load_from_file(&id) - .tap_err(|_| log::warn!("Failed to load configuration file"))?; - Ok(config) - } - - /// Start connecting by first creating a config file, followed by starting a thread running the - /// SOCKS5 client. - pub async fn start_connecting( - &mut self, - window: &tauri::Window, - ) -> Result<(nym_task::StatusReceiver, ExitStatusReceiver)> { - self.set_state(ConnectionStatusKind::Connecting, window); - - let res = self.init_config().await; - match res { - Ok(_) => {} - Err(e) => { - log::error!("Failed to initialize: {e}"); - - // Wait a little to give the user some rudimentary feedback that the click actually - // registered. - tokio::time::sleep(Duration::from_secs(1)).await; - self.set_state(ConnectionStatusKind::Disconnected, window); - return Err(e); - } - }; - let (config, keys) = res.unwrap(); - - // Kick off the main task and get the channel for controlling it - self.start_nym_socks5_client(config, keys) - } - - /// Create a configuration file - async fn init_config(&self) -> Result<(Config, KeyManager)> { - let service_provider = self - .get_service_provider() - .as_ref() - .ok_or(BackendError::CouldNotInitWithoutServiceProvider)?; - let gateway = self - .get_gateway() - .as_ref() - .ok_or(BackendError::CouldNotInitWithoutGateway)?; - log::trace!(" service_provider: {:?}", service_provider); - log::trace!(" gateway: {:?}", gateway); - - config::Config::init(service_provider, gateway).await - } - - /// Spawn a new thread running the SOCKS5 client - fn start_nym_socks5_client( - &mut self, - config: Config, - keys: KeyManager, - ) -> Result<(nym_task::StatusReceiver, ExitStatusReceiver)> { - let id = self.get_config_id()?; - let (control_tx, msg_rx, exit_status_rx, used_gateway) = - tasks::start_nym_socks5_client(&id, config, keys)?; - self.socks5_client_sender = Some(control_tx); - self.gateway = Some(used_gateway.gateway_id); - Ok((msg_rx, exit_status_rx)) - } - - /// Once the SOCKS5 client is operational, the status listener would call this - pub fn mark_connected(&mut self, window: &tauri::Window) { - log::trace!("state::mark_connected"); - self.set_state(ConnectionStatusKind::Connected, window); - } - - /// Disconnect by sending a message to the SOCKS5 client thread. Once it has finished and is - /// disconnected, the disconnect handler will mark it as disconnected. - pub async fn start_disconnecting(&mut self, window: &tauri::Window) -> Result<()> { - log::trace!("state::start_disconnecting"); - self.set_state(ConnectionStatusKind::Disconnecting, window); - - // Send shutdown message - match self.socks5_client_sender { - Some(ref mut sender) => sender - .send(Socks5ControlMessage::Stop) - .await - .map_err(|err| { - log::warn!("Failed trying to send disconnect signal: {err}"); - BackendError::CoundNotSendDisconnectSignal - }), - None => { - log::warn!( - "Trying to disconnect without being able to talk to the SOCKS5 client, \ - is it running?" - ); - Err(BackendError::CoundNotSendDisconnectSignal) - } - } - } - - /// Once the SOCKS5 client has stopped, this should be called by the disconnect handler to mark - /// the state as disconnected. - pub fn mark_disconnected(&mut self, window: &tauri::Window) { - self.set_state(ConnectionStatusKind::Disconnected, window); - } -} diff --git a/nym-connect/mobile/src-tauri/src/tasks.rs b/nym-connect/mobile/src-tauri/src/tasks.rs deleted file mode 100644 index cb00d6f9c9..0000000000 --- a/nym-connect/mobile/src-tauri/src/tasks.rs +++ /dev/null @@ -1,205 +0,0 @@ -use futures::{channel::mpsc, StreamExt}; -use nym_client_core::{ - client::key_manager::KeyManager, - config::{ClientCoreConfigTrait, GatewayEndpointConfig}, - error::ClientCoreStatusMessage, -}; -use nym_task::manager::TaskStatus; -use std::sync::Arc; -use tap::TapFallible; -use tokio::sync::RwLock; - -use nym_config_common::NymConfig; -use nym_socks5_client_core::NymClient as Socks5NymClient; -use nym_socks5_client_core::{config::Config as Socks5Config, Socks5ControlMessageSender}; - -use crate::{ - config::Config, - error::Result, - events::{self, emit_event, emit_status_event}, - models::{ConnectionStatusKind, ConnectivityTestResult}, - operations, - state::State, -}; - -pub type ExitStatusReceiver = futures::channel::oneshot::Receiver; - -/// Status messages sent by the SOCKS5 client task to the main tauri task. -#[derive(Debug)] -pub enum Socks5ExitStatusMessage { - /// The SOCKS5 task successfully stopped - Stopped, - /// The SOCKS5 task failed to start - Failed(Box), -} - -/// The main SOCKS5 client task. It loads the configuration from file determined by the `id`. -pub fn start_nym_socks5_client( - id: &str, - config: Config, - keys: KeyManager, -) -> Result<( - Socks5ControlMessageSender, - nym_task::StatusReceiver, - ExitStatusReceiver, - GatewayEndpointConfig, -)> { - log::info!("Loading config from file: {id}"); - let used_gateway = config.get_base().get_gateway_endpoint().clone(); - - let socks5_client = Socks5NymClient::new_with_keys(config.socks5, Some(keys)); - log::info!("Starting socks5 client"); - - // Channel to send control messages to the socks5 client - let (socks5_ctrl_tx, socks5_ctrl_rx) = mpsc::unbounded(); - - // Channel to send status update messages from the background socks5 task to the frontend. - let (socks5_status_tx, socks5_status_rx) = mpsc::channel(128); - - // Channel to signal back to the main task when the socks5 client finishes, and why - let (socks5_exit_tx, socks5_exit_rx) = futures::channel::oneshot::channel(); - - // Spawn a separate runtime for the socks5 client so we can forcefully terminate. - // Once we can gracefully shutdown the socks5 client we can get rid of this. - // The status channel is used to both get the state of the task, and if it's closed, to check - // for panic. - std::thread::spawn(|| { - let result = tokio::runtime::Runtime::new() - .expect("Failed to create runtime for SOCKS5 client") - .block_on(async move { - socks5_client - .run_and_listen(socks5_ctrl_rx, socks5_status_tx) - .await - }); - - if let Err(err) = result { - log::error!("SOCKS5 proxy failed: {err}"); - socks5_exit_tx - .send(Socks5ExitStatusMessage::Failed(err)) - .expect("Failed to send status message back to main task"); - return; - } - - log::info!("SOCKS5 task finished"); - socks5_exit_tx - .send(Socks5ExitStatusMessage::Stopped) - .expect("Failed to send status message back to main task"); - }); - - Ok(( - socks5_ctrl_tx, - socks5_status_rx, - socks5_exit_rx, - used_gateway, - )) -} - -pub fn start_connection_check(state: Arc>, window: tauri::Window) { - log::debug!("Starting connection check handler"); - tokio::spawn(async move { - if state.read().await.get_status() != ConnectionStatusKind::Connected { - log::error!("SOCKS5 connection status check failed: not connected"); - return; - } - - log::info!("Running connection health check"); - if operations::connection::health_check::run_health_check().await { - state - .write() - .await - .set_connectivity_test_result(ConnectivityTestResult::Success); - emit_event( - "socks5-connection-success-event", - "SOCKS5 success", - "SOCKS5 connection health check successful", - &window, - ); - } else if state.read().await.get_status() == ConnectionStatusKind::Connected { - state - .write() - .await - .set_connectivity_test_result(ConnectivityTestResult::Fail); - log::error!("SOCKS5 connection health check failed"); - emit_event( - "socks5-connection-fail-event", - "SOCKS5 error", - "SOCKS5 connection health check failed", - &window, - ); - } else { - log::debug!("SOCKS5 connection status check cancelled: not connected"); - } - - log::debug!("Connection check handler exiting"); - }); -} - -/// The status listener listens for non-exit status messages from the background socks5 proxy task. -pub fn start_status_listener( - state: Arc>, - window: tauri::Window, - mut msg_receiver: nym_task::StatusReceiver, -) { - log::info!("Starting status listener"); - - tokio::spawn(async move { - while let Some(msg) = msg_receiver.next().await { - log::info!("SOCKS5 proxy sent status message: {}", msg); - - if let Some(task_status) = msg.downcast_ref::() { - events::handle_task_status(task_status, &state, &window).await; - } else if let Some(client_status_message) = - msg.downcast_ref::() - { - events::handle_client_status_message(client_status_message, &state, &window).await; - } else { - emit_status_event("socks5-status-event", &msg, &window); - } - } - log::info!("Status listener exiting"); - }); -} - -/// The disconnect listener listens to the channel setup between the socks5 proxy task and the main -/// tauri task. Primarily it listens for shutdown messages, and updates the state accordingly. -pub fn start_disconnect_listener( - state: Arc>, - window: tauri::Window, - exit_status_receiver: ExitStatusReceiver, -) { - log::trace!("Starting disconnect listener"); - tokio::spawn(async move { - match exit_status_receiver.await { - Ok(Socks5ExitStatusMessage::Stopped) => { - log::info!("SOCKS5 task reported it has finished"); - emit_event( - "socks5-event", - "SOCKS5 finished", - "SOCKS5 task reported it has finished", - &window, - ); - } - Ok(Socks5ExitStatusMessage::Failed(err)) => { - log::info!("SOCKS5 task reported error: {err}"); - emit_event( - "socks5-event", - "SOCKS5 error", - &format!("SOCKS5 failed: {err}"), - &window, - ); - } - Err(_) => { - log::info!("SOCKS5 task appears to have stopped abruptly"); - emit_event( - "socks5-event", - "SOCKS5 error", - "SOCKS5 stopped abruptly. Please try reconnecting.", - &window, - ); - } - } - - let mut state_w = state.write().await; - state_w.mark_disconnected(&window); - }); -} diff --git a/nym-connect/mobile/src-tauri/tauri.conf.json b/nym-connect/mobile/src-tauri/tauri.conf.json deleted file mode 100644 index c968ee8367..0000000000 --- a/nym-connect/mobile/src-tauri/tauri.conf.json +++ /dev/null @@ -1,74 +0,0 @@ -{ - "package": { - "productName": "nym-connect", - "version": "1.1.12" - }, - "build": { - "distDir": "../dist", - "devPath": "http://localhost:9000", - "beforeDevCommand": "", - "beforeBuildCommand": "" - }, - "tauri": { - "bundle": { - "active": true, - "targets": "all", - "identifier": "net.nymtech.connect", - "icon": [ - "icons/32x32.png", - "icons/128x128.png", - "icons/128x128@2x.png", - "icons/icon.icns", - "icons/icon.ico" - ], - "resources": [], - "externalBin": [], - "copyright": "Copyright © 2021-2023 Nym Technologies SA", - "category": "Business", - "shortDescription": "Browse the internet privately using the Nym Mixnet", - "longDescription": "", - "deb": { - "depends": [] - }, - "macOS": { - "frameworks": [], - "minimumSystemVersion": "", - "exceptionDomain": "", - "signingIdentity": null, - "entitlements": null - }, - "windows": { - "certificateThumbprint": null, - "digestAlgorithm": "sha256", - "timestampUrl": "" - } - }, - "updater": { - "active": false - }, - "allowlist": { - "shell": { - "open": true - }, - "clipboard": { - "writeText": true - }, - "notification": { - "all": true - } - }, - "windows": [ - { - "title": "NymConnect", - "width": 240, - "height": 635, - "resizable": false, - "decorations": false, - "transparent": true - } - ], - "security": { - "csp": "default-src blob: data: filesystem: ws: wss: http: https: tauri: 'unsafe-eval' 'unsafe-inline' 'self' img-src: 'self'" - } - } -} diff --git a/nym-connect/mobile/src/assets/help-step-four.png b/nym-connect/mobile/src/assets/help-step-four.png deleted file mode 100644 index 55079628923c10eaecf9243a6b99f8124d84d916..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8530 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 fPTkx*p?>VHb2K*s*> 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 QH2?qr07*qoM6N<$g8v&Kn*aa+ diff --git a/nym-connect/mobile/src/assets/help-step-three.png b/nym-connect/mobile/src/assets/help-step-three.png deleted file mode 100644 index 932b6c117166ee324f7a2111fc857ac1acce3a83..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 18990 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�t3F0PA2oun6An$m>7S{la;2+sY51dti~ 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 diff --git a/nym-connect/mobile/src/assets/help-step-two-legacy.png b/nym-connect/mobile/src/assets/help-step-two-legacy.png deleted file mode 100644 index f2910c98cf06b4d401e010be1e8ec43871171d3f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 35631 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ð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 M07*qoM6N<$f_|#uPXGV_ diff --git a/nym-connect/mobile/src/components/AppVersion.tsx b/nym-connect/mobile/src/components/AppVersion.tsx deleted file mode 100644 index 4ced230efa..0000000000 --- a/nym-connect/mobile/src/components/AppVersion.tsx +++ /dev/null @@ -1,15 +0,0 @@ -import React from 'react'; -import { Box } from '@mui/material'; -import { useClientContext } from 'src/context/main'; - -export const AppVersion = () => { - const { appVersion } = useClientContext(); - - return ( - - - Version {appVersion} - - - ); -}; diff --git a/nym-connect/mobile/src/components/AppWindowFrame.tsx b/nym-connect/mobile/src/components/AppWindowFrame.tsx deleted file mode 100644 index 90e01fffa7..0000000000 --- a/nym-connect/mobile/src/components/AppWindowFrame.tsx +++ /dev/null @@ -1,48 +0,0 @@ -import React from 'react'; -import { Box } from '@mui/material'; -import { useLocation } from 'react-router-dom'; -import { useClientContext } from 'src/context/main'; -import { CustomTitleBar } from './CustomTitleBar'; - -export const AppWindowFrame: FCWithChildren = ({ children }) => { - const location = useLocation(); - const { userDefinedGateway, setUserDefinedGateway, userDefinedSPAddress, setUserDefinedSPAddress } = - useClientContext(); - - // defined functions to be used when moving away from pages - const onBack = () => { - switch (location.pathname) { - case '/menu/settings/gateway': - return () => { - // when the user moves away from the settings page and the gateway is not valid - // set isActive to false - if (!userDefinedGateway?.gateway) { - setUserDefinedGateway((current) => ({ ...current, isActive: false })); - } - }; - case '/menu/settings/service-provider': - return () => { - // when the user moves away from the settings page and the sp is not valid - // set isActive to false - if (!userDefinedSPAddress?.address) { - setUserDefinedSPAddress((current) => ({ ...current, isActive: false })); - } - }; - default: - return undefined; - } - }; - - return ( - - - {children} - - ); -}; diff --git a/nym-connect/mobile/src/components/ConnectionButton.tsx b/nym-connect/mobile/src/components/ConnectionButton.tsx deleted file mode 100644 index 1dd72f78df..0000000000 --- a/nym-connect/mobile/src/components/ConnectionButton.tsx +++ /dev/null @@ -1,168 +0,0 @@ -import React from 'react'; -import { ConnectionStatusKind } from '../types'; - -const getStatusFillColor = (status: ConnectionStatusKind, hover: boolean, isError: boolean): string => { - if (isError && hover) { - return '#21D072'; - } - if (isError) { - return '#40475C'; - } - - switch (status) { - case 'disconnected': - if (hover) { - return '#FFFF33'; - } - return '#FFE600'; - case 'connecting': - case 'disconnecting': - return '#FFE600'; - default: - // connected - if (hover) { - return '#E43E3E'; - } - return '#21D072'; - } -}; - -const getStatusText = (status: ConnectionStatusKind, hover: boolean): string => { - switch (status) { - case 'disconnected': - return 'Connect'; - case 'connecting': - return 'Connecting'; - case 'disconnecting': - return 'Connected'; - default: - // connected - if (hover) { - return 'Disconnect'; - } - return 'Connected'; - } -}; - -export const ConnectionButton: FCWithChildren<{ - status: ConnectionStatusKind; - disabled?: boolean; - busy?: boolean; - isError?: boolean; - onClick?: (status: ConnectionStatusKind) => void; -}> = ({ status, disabled, isError, onClick }) => { - const [hover, setHover] = React.useState(false); - - const handleClick = React.useCallback(() => { - if (disabled === true) { - return; - } - if (onClick) { - onClick(status); - } - }, [status, disabled]); - - const statusText = getStatusText(status, hover); - const statusTextColor = isError ? '#40475C' : '#FFF'; - const statusFillColor = getStatusFillColor(status, hover, Boolean(isError)); - - return ( - - !disabled && setHover(true)} - onMouseLeave={() => !disabled && setHover(false)} - > - - - - - - - - - - {status === 'connected' && hover ? ( - - ) : ( - - )} - - {statusText} - - - - - - - - - - - - - - - - - - - - - - - - - - ); -}; diff --git a/nym-connect/mobile/src/components/ConnectionStats.tsx b/nym-connect/mobile/src/components/ConnectionStats.tsx deleted file mode 100644 index f17833e66d..0000000000 --- a/nym-connect/mobile/src/components/ConnectionStats.tsx +++ /dev/null @@ -1,47 +0,0 @@ -import React from 'react'; -import { Box, Typography } from '@mui/material'; -import prettyBytes from 'pretty-bytes'; - -export interface ConnectionStatsItem { - label: string; - rateBytesPerSecond: number; - totalBytes: number; -} - -const FONT_SIZE = '14px'; - -export const ConnectionStats: FCWithChildren<{ - stats: ConnectionStatsItem[]; -}> = ({ stats }) => ( - -

    - {stats.map((stat) => ( - - {stat.label} - - ))} -
    -
    - {stats.map((stat) => ( - - {formatRate(stat.rateBytesPerSecond)} - - ))} -
    -
    - {stats.map((stat) => ( - - {formatTotal(stat.totalBytes)} - - ))} -
    - -); - -export function formatRate(bytesPerSecond: number): string { - return `${prettyBytes(bytesPerSecond)}/s`; -} - -export function formatTotal(totalBytes: number): string { - return prettyBytes(totalBytes); -} diff --git a/nym-connect/mobile/src/components/ConnectionStatus.tsx b/nym-connect/mobile/src/components/ConnectionStatus.tsx deleted file mode 100644 index 4ee49c5e14..0000000000 --- a/nym-connect/mobile/src/components/ConnectionStatus.tsx +++ /dev/null @@ -1,134 +0,0 @@ -import React, { useState } from 'react'; -import { Box, CircularProgress, Typography } from '@mui/material'; -import { DateTime } from 'luxon'; -import { ErrorOutline, InfoOutlined } from '@mui/icons-material'; -import { ConnectionStatusKind, GatewayPerformance } from '../types'; -import { ServiceProvider } from '../types/directory'; -import { GatwayWarningInfo, ServiceProviderInfo } from './TooltipInfo'; -import { InfoModal } from './InfoModal'; - -const FONT_SIZE = '14px'; -const FONT_STYLE = 'normal'; - -const ConnectionStatusContent: FCWithChildren<{ - status: ConnectionStatusKind; - serviceProvider?: ServiceProvider; - gatewayError: boolean; -}> = ({ status, serviceProvider, gatewayError }) => { - const [showSpInfo, setShowSpInfo] = useState(false); - const [showGatewayWarn, setShowGatewayWarn] = useState(false); - - if (gatewayError) { - return ( - <> - {serviceProvider && ( - setShowGatewayWarn(false)} - > - - - )} - setShowGatewayWarn(true)} - > - - - Gateway has issues - - - - ); - } - switch (status) { - case 'connected': - return ( - <> - {serviceProvider && ( - setShowSpInfo(false)}> - - - )} - setShowSpInfo(true)} - justifyContent="center" - sx={{ cursor: 'pointer' }} - > - - - Connected to Nym Mixnet - - - - ); - case 'disconnected': - return ( - - Connect to the mixnet - - ); - case 'disconnecting': - return ( - - - - Disconnecting... - - - ); - case 'connecting': - return ( - - - - Connecting... - - - ); - - default: - return null; - } -}; - -export const ConnectionStatus: FCWithChildren<{ - status: ConnectionStatusKind; - gatewayPerformance?: GatewayPerformance; - connectedSince?: DateTime; - serviceProvider?: ServiceProvider; -}> = ({ status, serviceProvider, gatewayPerformance }) => { - const color = status === 'connected' || status === 'disconnecting' ? '#21D072' : 'white'; - - return ( - - - - ); -}; diff --git a/nym-connect/mobile/src/components/ConntectionTimer.tsx b/nym-connect/mobile/src/components/ConntectionTimer.tsx deleted file mode 100644 index c7b341ecfe..0000000000 --- a/nym-connect/mobile/src/components/ConntectionTimer.tsx +++ /dev/null @@ -1,28 +0,0 @@ -import React, { useEffect } from 'react'; -import { Stack, Typography } from '@mui/material'; -import { DateTime } from 'luxon'; - -export const ConnectionTimer = ({ connectedSince }: { connectedSince?: DateTime }) => { - const [duration, setDuration] = React.useState(); - useEffect(() => { - const intervalId = setInterval(() => { - if (connectedSince) { - setDuration(DateTime.now().diff(connectedSince).toFormat('hh:mm:ss')); - } - }, 500); - return () => { - clearInterval(intervalId); - }; - }, [connectedSince]); - - return ( - - - Connection time - - - {duration || '00:00:00'} - - - ); -}; diff --git a/nym-connect/mobile/src/components/CopyToClipboard.tsx b/nym-connect/mobile/src/components/CopyToClipboard.tsx deleted file mode 100644 index 7a48662190..0000000000 --- a/nym-connect/mobile/src/components/CopyToClipboard.tsx +++ /dev/null @@ -1,69 +0,0 @@ -import React, { useEffect, useState } from 'react'; -import { Button, IconButton, Tooltip } from '@mui/material'; -import { Check, ContentCopy } from '@mui/icons-material'; -import { clipboard } from '@tauri-apps/api'; - -export const CopyToClipboard = ({ - text = '', - light, - iconButton, -}: { - text?: string; - light?: boolean; - iconButton?: boolean; -}) => { - const [copied, setCopied] = useState(false); - - const handleCopy = async (textToCopy: string) => { - try { - if (clipboard) { - await clipboard.writeText(textToCopy); - } else { - await navigator.clipboard.writeText(textToCopy); - } - setCopied(true); - } catch (e) { - console.log(`failed to copy: ${e}`); - } - }; - - useEffect(() => { - let timer: NodeJS.Timeout; - if (copied) { - timer = setTimeout(() => { - setCopied(false); - }, 2000); - } - return () => clearTimeout(timer); - }, [copied]); - - if (iconButton) - return ( - - handleCopy(text)} - size="small" - sx={{ - color: (theme) => (light ? theme.palette.common.white : theme.palette.nym.background.dark), - }} - > - {!copied ? : } - - - ); - - return ( - - ); -}; diff --git a/nym-connect/mobile/src/components/CustomTitleBar.tsx b/nym-connect/mobile/src/components/CustomTitleBar.tsx deleted file mode 100644 index 9fb13c7cec..0000000000 --- a/nym-connect/mobile/src/components/CustomTitleBar.tsx +++ /dev/null @@ -1,67 +0,0 @@ -import React from 'react'; -import { ArrowBack, Menu } from '@mui/icons-material'; -import { Box, IconButton, Typography } from '@mui/material'; -// TODO since the structure refactor for NC this import fails -// import { NymWordmark } from '@nymproject/react/logo/NymWordmark'; -import { useNavigate } from 'react-router-dom'; - -const customTitleBarStyles = { - titlebar: { - background: '#1D2125', - display: 'flex', - justifyContent: 'space-between', - alignItems: 'center', - padding: '16px', - paddingBottom: '0px', - borderTopLeftRadius: '12px', - borderTopRightRadius: '12px', - }, -}; - -const CustomButton = ({ Icon, onClick }: { Icon: React.JSXElementConstructor; onClick: () => void }) => ( - - - -); - -const MenuIcon = () => { - const navigate = useNavigate(); - return navigate('/menu')} />; -}; - -const ArrowBackIcon = ({ onBack }: { onBack?: () => void }) => { - const navigate = useNavigate(); - const handleBack = () => { - onBack?.(); - navigate(-1); - }; - return ; -}; - -const getTitle = (path: string) => { - if (path.includes('settings')) return 'Settings'; - if (path !== '/') { - const title = path.split('/').slice(-1); - return ( - - {title} - - ); - } - - // TODO return ; - return ( - - NYM - - ); -}; - -export const CustomTitleBar = ({ path = '/', onBack }: { path?: string; onBack?: () => void }) => ( - - {/* set width to keep logo centered */} - {path === '/' ? : } - {getTitle(path)} - - -); diff --git a/nym-connect/mobile/src/components/Error.tsx b/nym-connect/mobile/src/components/Error.tsx deleted file mode 100644 index f582536b6b..0000000000 --- a/nym-connect/mobile/src/components/Error.tsx +++ /dev/null @@ -1,17 +0,0 @@ -import React from 'react'; -import { FallbackProps } from 'react-error-boundary'; -import { Alert, AlertTitle, Button } from '@mui/material'; - -export const ErrorFallback = ({ error, resetErrorBoundary }: FallbackProps) => ( -
    - - {error.name} - {error.message} - - - Stack trace - {error.stack} - - -
    -); diff --git a/nym-connect/mobile/src/components/ErrorFallback.tsx b/nym-connect/mobile/src/components/ErrorFallback.tsx deleted file mode 100644 index f582536b6b..0000000000 --- a/nym-connect/mobile/src/components/ErrorFallback.tsx +++ /dev/null @@ -1,17 +0,0 @@ -import React from 'react'; -import { FallbackProps } from 'react-error-boundary'; -import { Alert, AlertTitle, Button } from '@mui/material'; - -export const ErrorFallback = ({ error, resetErrorBoundary }: FallbackProps) => ( -
    - - {error.name} - {error.message} - - - Stack trace - {error.stack} - - -
    -); diff --git a/nym-connect/mobile/src/components/ExperimentalWarning.tsx b/nym-connect/mobile/src/components/ExperimentalWarning.tsx deleted file mode 100644 index 59595d389e..0000000000 --- a/nym-connect/mobile/src/components/ExperimentalWarning.tsx +++ /dev/null @@ -1,13 +0,0 @@ -import React from 'react'; -import { Box, Typography } from '@mui/material'; - -export const ExperimentalWarning = () => ( - - - This is experimental software. - - - Do not rely on it for strong anonymity (yet). - - -); diff --git a/nym-connect/mobile/src/components/Growth/content/assets/matrix.webp b/nym-connect/mobile/src/components/Growth/content/assets/matrix.webp deleted file mode 100644 index 2b1517166cd4d85be71b1a6589c01984414f0bbd..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1170192 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 z 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
    wMBm^P_g77SaVR4mfsM zH|k{_DadZOb}!H2ScHF!w4w16lrkJ1L}ixHefuodRY=9rROHRJOy%$Ty6IPpaiKQb zJu7|ZuC6bV3+WPo#s4YW#`EVgZG@c}FLS2zIlMr9Tomq)z6X%^G6j@vdJ)x>DqbSj|TOPJ^9%0f>WYvT2Iz86L$gqaY^9R18y@==R`uB*m7 zN@Tuz7}~!1W&_PGIo5Z4g_MhZM1=k$pgA;@`iQ?993}hROx>$N?&!bcEuPM2Q%^0w z-)3#GZXJSs^*za$dG?GgZ0?(C`H$DFFsuB&<3wc+c@P=rS^VGgX$di!U+$b8Y5=QM zTgTXr^M1?ww4@G=FUM=`Ei$Ib!p8FzSXtrZjkK>S`9bP!Xs>#RKA-|9TvxP6sePukET+@Fw0(@%GIvWu2@$_|f+ z%AU1}mvd+{T5lCOZQgVeG%V|{MpH(u2&fE*Dm?OhAXM&W1JFCm6Lb3R@VECPviBCe z^fB^Jq zKpYW^-1b4b8SG7+?eBC2g)=CUtv7Kxs$3mT>m}%?*UhUn)mr&v)-r8w8)BL7b9Ew9 zk1OkExlQm1PUoTG4CG(*X>fV#7uyiTIz1|IKV_s*ldCfy&`CO>xH}%dIr@Vz1)7~! z8G92>-ftvY$EVMVexxD!_l7S|EDpH6b8-tR<#w#`y=Vr;PWl&9fNC2p1{Rw4+{1G! z7ResC)Q2(gOZS6LL$zT{V05?MMsW4EXStonP0o?P<^?V3&gO1bsiD|I*wUo1@P`5< z_zt2uHXf$>9*~!y2w&)`m5H85(ugXxT6og&moJaT_P!({rm%SsqLa7TQ9!*cx?^EO zw>o@VsxY+CdcF?-r9Jk=_(El(lpwB(XpcCx;jw>g&>ge!eI;Eu#isEPYYfAg*jxRE zUU0^!TZu1An%lOv$k9fB5Fx?5N7sgPH1@&fc*JO~b?E=WL{L4{48X6RS?WUZW2fh} z4W1$nW&BQiM((I#eV|kQN4CQ64R{JPWrNOhh`cPL{|gN~2)jrp1adB%MOA^-;=QhM zXXytx91r>Hz>Il$6>v$Z238u3gDQK=_i7Vf81K^zMPAf5B=3pYXHcBJC$v0Lu@p3T zdHb&hVMt=v*JgHUJ0bD`Ul+)|{EX^8jUAV^aTY$Eo}8Qe{7deNGAiWQNfDLeJoX4; z&0h5e1INwlX*bq+Xy8C76#1vhS92-0{5*}!zfkDXbEnp8f zFW1TV!+`EW@VL2GFK=V#dm%4lvNt_mBtk1y33);CMqO9T$$_ku z&em7a{#3SgbkDDlOC=1H1V5Mbe}i4Q$a1Y|;`P(rBCf=TzDi{Te(-lXrlAmqO*p#o zIltkwIh!845XbG^tB5uh#hQ7T(-MF}Pvt_Zd4pTUm#tMvl(0$ugZ(o+-t;}ZZr%9Z zW9_Qn|MRGY>$7jrR@mXJd3*NZbM^R?<4?dg?<-|C(IGH26TYfPk<&$PQG=_dZv5=n zAS+DfBi{E~$h3|c)%6@D9@f@J5R>LTo@0Up;_%8lLomVQ@ zXmd6d;^+#!9z=H#1+};#c!#pH_NB92%l~AWW!IK`J_lg1N1O=gm>)V&HSx87=v~n$ z&jo5!XsKHHwzt3Yb!G703BX@{nWAO|JxDYf)o?I|hV$i2CcB*=_NVgK(bX$zvNfR= z7R5q@&bvhKULWkC$yPm!WL@fL5A0M?{QsSWi-q51MskJa!{hLlzYbiUMtEek<=XA} z9FoM|_~td|mFjhb$HU@lGUl2u+=vdSl9uk<`=qhb7qFWE2`KS%eIpRKSw^avh;?zv z=&$UaU3Tccs8~`27T?3+aecfQFh!Xc{*KxHFytpc!Egm9vm0Xh`3j)87y3!WO(<2s zJY(46kt${xoMx~)lq}85(=4H*n&na_{wrO`zBy-{Fh7(|Hb9k7L8C5l2eHBvfLJwI z;@cEAs^G4O7sr7ukIL>%KMC~FVMFFf4eSPjs|@@=l^S>jyaxJ^e=#c~2F+EwQA}*C z{k`%CI{PU>hM>iD{zE3&5z}g#V`ssLK1hn%JRGX42zBc*43FM?PF{NfoX{jOQn#(G zxI)to#Bi91lFjXi2T{oUGbIQ)BY9jq%ADdC@Ep))*pQZ?oMknJ1Qo~Tj+GV1s0lHv z18iMi%Jgi~uo_RHa#Q(h$#`H9bgK=FAlm9jd7sTC8?E%=R2rQ*&o{smJ0+obVAZ!0 zh+a7ZqSBihcWDlzMH*)OWE6OW;MBb@>QH!KlFx|(1Cp#ZIk7=ccjhsEP*ucz1&c^ihsRQ?~MJ+@O`)(r8!32t`eMxQt&74$|*)nUoa=L2R;j($#nc zh;c|*^`LYwZcro!Y#jJ0(5`y>1V>*bq-uufCC^xwyRoKBp=NzXi~2lP?7h(6>D0Ca z7yLsXaF6iJ?WNHY@3B-;Qr(UN=SJL4gJ7;R$1FR@%Me*cPGPw*V#1aekTxGyhpraL z>X=Jo(16c~nAQOVukRA}r;%P6D{LjwWy*L5j-=`)Pcs6`GtwA-mzDe`0oL)**8K*> zRW_Q()ehP&(tSJBS$<|y?B*#;jp%mj$qb*8+$bJjL4ZU@v5vK14Yt7&-xc*iAM=+I z_4eawn73;5&i#uRSM(I^T!3Np<}(XJT=w}lUt+$~ja#l)7=n%_G@1Zl(-i<)Wte&Z)2BWiNk~7G%ChDrfdJahvI-MGETCLq>|4 zUX$ob5m6d}zX~Uq388|Z$?36dSJ0QR=@81>9d3hKd>o-FquX#7JpGrDPW3WNrioO* zmK#CZ^qu34xP6D~S(of#9bN9#>d}Lol1q8m&E}vB=%R6Xp}f%_R!X1O=970iR<40p z`kYkYJv$#clOJ_yxk@eGDo7`N+gt9#S{TEF^!6~VFX~`yJdCpM$`3|sbK^5`Skp}_ zf&=rKvo$>kRqtTc7wXdH#DIjG>ESF1tH?d`P8u(s`y>HLPn;Iq@B(D_1y8cMv3_Gg zk8X(SL%%>+KjEXmWoG>?p7we5SmI`8Ow-;4R1w_n3_s^JDlWm&(7cu%@t6ax zqW=@nqBAi$%VPyOx1|!{FnP}H!jz(K>ZRHkU(mJdHm8tE_x`q4CH?ppozwS@%>J3# z+W19uZFt?0(N!hAFHbLzroY??7lwa(M>bq`%bXASjj}U(%8$|ycO+-UNBkx?SQC}A zYhDadscgzQa1&pSnI_}Lc+xCXL`jz`;_^e4=VL%2?7lq-b89Z;d$WIPFP;Hf_v#|K$KyP&gnAI{*N*4gsA3 zD&PSQ0X|VAjzt28Nwxq0pq6&q&aLS`X2)k`mdT{2l%u(7_G8nHW*+BtFUnrHg&@4kWlhx_;WZ^|!0|3Lf4_y3wd8rP5bkMVnu{`d1s`(N%q z;BM7_Yw;BJ@B5!&NJF@Hzkl@oJ^xGo&!*pJf9ZRHezgCt_8-J+*1d!OsQ-QcvG;HF zWA$6=_xHc~Uc%q7U-x?~`ZF2o)U7ACnq2w)ih%eU;QAhyn)^kM)D_O9W57?Svyk?U z(8UVZ4>!+*>OezZCGBhqfCi5GC_fUIq^J6cwl_NKy2x0~04jC|n8*#gmxa0IaKcP* z!r9V=ppj2n8TQB*LRKU1*y-4M`OF$w-;yOFa`5c?NPEC!q+C{JrVN{<^JX)4;XMnU z6xaU`SUC43`i*1ub3i)lkSW0M1kGS8BC3OrLZjd=zof4*<2s2>`^xO0J5{6-eY01%)PkB4=w3` z^QyN?*jO9U*bNf1b||#9u;5Sa5M-ZIo^@6K z6c+|{Mp56Sz9#&rl?K!E%(!I)xdCZmdMX)0(>~=F9bCOjmTjyy+lfB14s>&sYzJ?5 zBf?Y2kf-VG))#~bNOO3}5du{#|J5ndsI4uCYnJz4R1-?BP!G6j_R}4J$QMg2x8l&T ze=e=(jZH@-+F8T32y3C)m|Z)a!7%Cv&NDGoI}OpS{cENSdVA$^9{#y0@a8#H-{5Sq z+MagZD2@@;e(~Qhl5RBnJn}fydeHS@kL^Pn6zI@&2Y&m||FZLFRzYNR&|A@KV3t|^ zsUCl!BdyUSdqrlwc*Yn>dEO(Do)xMJOL1Z;f`l;sVSGR{JbOD`2l5cR<_~ltztPy# zeRo?Z1#RPp`^S%P8)j1$^ZftX4~!SNYT?ZbHnZ2Idk?jYpRa<5}{`F^}Ro;(Z&o(ZP)MmXsrHOoBSU&%IzhA z?oAh&S^#3F>2z$$(q`H1=r=v5^i;E(h`mNrUUjcE`T<}p7R)4@^8MaY#6ya7%BW`v zRuGMtnB9CYZj|9qA)Z@vP318Jimp2IT0f{qU}G5dK842(D`rA$3CKEOc|Url&?6=;wRf#DB)k` zHAJoLy>(|$hxyxUng86Fhao~~(gDZ-%k$}!4<`dYYGP1lF?Yn?_NruBi~P&5g~rNA zVXW0QW%d$k2m7(judi3Iw{LtW7a5(39@i?MpO zcjg;%L?aa-$}4QPig*2*qqZ^OI>S_!l^`iE(lRhVLe`o^FyE>SbUw^jDY3LD=~a0R ze!(F#_=>xQm-1G3am4d+3}qDv5Btk)Idi-8q>~7K_7`;0^DC<`a1^3%eM!V)hPabi z+0^09<%Vp|!vw1@vG z$eH$0k7i3g2eJrfmac>DWF->Y#za)H65ue2POqnMwoirtW!u98Zbg+BhquezN_`MO z9eevd9%3up&&eT8d*-Olm)&Zh%AlWGnrl_oM}Oc;&jbcFa3{nm;Z_b8u#uJC3@yu|?xcE*(VI}=z^0V{AG;9HNVzNvpxh6)> zlSw6Xps#R&RLrP0@;6QI7NtjLDz>_%Y``clN8nA3+ATfCl11{W?(rYjs za&I5D8VAK+#jefvAggiz+z<%`pAn}P*MUWHbRx`&BFZ-6`)jOf)7uNf!XXDx(WozH zVP>eq2?D;~+M97~%#B@sGIf6ZjDA&Z@^9df5qcu}Lp7N&L-UaLqMNW~JnWprSySn>Ya z`Q(jfD|uJUy5^W_RN+Kmm9h{Q#pd(A!M7rYbHQ;&He~HMbp4;l*mm3bMJX5U+zZoe z#MbKRNEFjq0$VHY$zy}-^<%;B^a4e%U*&R({7PW&wBb;hT@1%>pduok^O^ImKJqGA z*@6Jg_^&&eBQ5NO1|KA86>|t=b&SV~5(jkUdK~K8^jh)*bUk0}Y`bbjsbLe`ph}kh z62Za{%B9`E* zP{21LB=?zbPc(>#JYvHP!PzmHub8$IbJFYZ6gHP;E8^i4O0mT(*XZ5!&0vsm*XaCv z)VN=u$X zvHTg={u9^DEi)JleFg_mgYgCq)|HLbkt=OsHNF(|JN}ck?JGq_69;YLn}{@P z8w!+ByEvWvbd|9}gw^ z0RcaTsuU22<|bK5dqWQ^gCw6G@%noih^>vsj=n>XId!Q*x<3ohJpag}_6r^Pb|;e7 z*q}RYb1!v$RA`KQLE%`evJA4HA&=kiq9x6XxtYyxg?Fl)KhzSk=3e!766$A}tD6<| zQ{lI{L?50xIuR1smwkEa!{_@55uhKos6m?SYLGE>!7kvgV!HKeAbUrfW{pY<*q0Y zCuR#8EpE+4AgC}t?-*27MZvwf(L!?afdxZvlCxf`{48pGM4Z_D(C6vHd76Mm8u!1v z2a&cJz9eOEXnDryq&UW-_d;IuFB*IH>WzK6nw5$AAdIJ&VSmzG0HiTZJV!SVm4x*t zKfULFH?nI@G6sloW`yFoFO^FPqRMckT!C&20J8i??`W-YfS8ClBShe=h0Zq+Hu1$dZ$ugJ9^p>yCBvEmZYD z=d{xhGf|McBmXuUX5QAXF?e$DJ}+lV&<5$z6z4JSNq@EYJ81=L(y2Te>)17h{Cvl* zMR{>h862f|hMaE7+mJkjxwm&BQ|GSEM&W&7dj`vnpDH;R4oam>}vqj64Dt zIugS0_4fMte5Q{OG!CDy~&iRd7Y;!|RONaoCCV`b}{Lc6Dat?J5 z2-8svtfLo$hmt(a!@tE8G~G@(Ql=D`Oe(H2@@c=s>-ApQTL}`;!1LrdK-yQIa=k7* z(r813G;N}&jm5tw-4D#9qYXCh1Tqk+y#;-u6}!EyfGZg&K8QkglQ@K-yr2L82oPQR zXVZ{NXe;De6;2M?eA|~@GLOfAAY3ui07q<{Bn>VI1UfwCBwUt&#{IGArN=9=kkczI{TIy$8i0^@;t4kfcwzfGgsb#+HaS#?;6?`}E~CE0 zKOXbgF)!iL^+B(F3ZU9S1@MVa@B?wWY<8FW=TlMEE~f=oKNBeay-~AH}D%vZ|L&|Po_Ee6MI>)+utCmF2J?F7YP-a*-5arm4F&)0ifV4aK^or4848Q^MyW>uU z)>qdpyUIS6Xih81>{iwaz9Iy<(^}MKbFrn;jsN(J?;RZY2!+dK5g5D9Br?p_55J-efa4sgit#i4tLK_BC`7I^HF#zJMxAH{ ztbNiP900{uECusGO0rTcy2j_PGJ7X9%yuc6ibz2^nx^?wGrZVrxsNG(j_>6?k`dbR z0u~S=3o~YjFC(D@aL@^mA;564xKeJ!+=FA8$1A~vk1t7>#cw2)0w91w2?bzJ45VqB zu)9G8KBu)x>$b>Gu6DBvy?T36^pnO5wN7zbD35zFx}GS`9$!!U;*NYr1rHG+z~?0= zH#Dl6&zLQSIx@N9@@33prYC0TrLY7;AxhX5nypqVEWK((*`b%;lFww9C<9kmbb#pD z>qCBas!5);DA)IrDD1qf*NIZnH4$B1c+rJVHP#)RG)bpa z373yH%@Hut#*$8*&p2x-{O6-}8Kz1@nQx!}1XJQG3||Bp2EzR-7jfvo0i4#wpy%{< zj9_-V;4LR9`eX=r6`>+vx@Y0$Koc~pCYz;ZP#d})d1xeCF)Xk}F4^|Oz3kQ#BH?>MDka z=CDd$%47}!E15r3@PPr=9Mi$Q0_^%uov^(ZMr`$A7?sbgC5=}2EFr6seIUfXjDCeP&)5u=47GOVzIkH?`)|lR-B+sJ% zdN1}Ob42`}g1rxXAECz|IMl82Yi&$l@kSnK4CWCuOtnq3ol2|xdxwNK0ed>Un+nU( zO`7M_0B!5YB@^KtUjd4hH7|vTNnJ7xU?4~P&NY}fE-hcQ-4o_E)SR>*PU2~bw^g_> z!0qApn*5wmBvZ|a59NDm@s%ZdNz`L|Dd-o-glSl%T16sZGES9Qys-K+YX93rjZHF5 z4q(J*WQ9&1znf>trl61XQ8&DcuJ)H#jLvN*ggx9Z@(C<77-rlBGu(&Y&24w~pY!6l zYZsRt;kHW$MU_*QUxK?VY%X7mPvO96FmAePp7c43DR*F<-nEbwUYXAjWoOv$-XVM4 zF?i0a=X-+h!cPMshP={t3ei3Zm~Cr7Xf1hkmS?~JkWxR1tJUOxswc(P=4s@QggXA? zh;EvPPc4rNY=p9;|@*GbGIWRU-l$pd8<6B2Nh_d;1^On1kYYG@w~#wO!D zi?MMLs-&@6F8gYvk8TFIJ_nuI@6A26nOh0s<$ERMB_0QJ1&KJf6q5Iio)QyhH&w}2 z?;(>)7Yl{+lwN(k`U3fRX)vh#1pws>1?d>e)%_~7a^nQ~6&wB3sIl_U>a(NV-TC?D!(4@OR~A zfUIV53!Dq@Ddhng)2p?q&jPJx4N0rgVY%Y*S#D?Dc4ih!hk-My0H`~l)&55^*|LvJ zm=>AZfBhEWK>rWmWo)AL#8cXp6Ha;fORCifl?jGlfh#HLZO%n~)XD}QLuOWTO_70u zH{Dai54<6rYkIL@8jC&;SS(fRh7e{?2Ke{U`HGdi$lr?>Q6`-31QGl;0u`$JWb;bO z__CYvypBA@`dS>=oQB|tJkANTsm;cBJbd`kC^O&#8<&ecwtXJjVLt-xZVne`6KMm}Dqv4Wk&L02mp| zg=?@?<-O3x$FiDDBSQK2`4|bZN>ID;xU7*TAi7VG=v5tN#-YUpV1i=Fl%Qy;LS(5o zL(VP%QpZ(!nYa3qt-4N##W4s-yiUdoZ|=RQ#%^U%7#Z#Ar^~`+zN;jdE~gqHKHt`V z(CKPtY6}|=*4OY3DV;wgrli49=NpayToJ4ao6phiIdMF!om`(ODM`*(po~IZoahl& zC~WGbux(8kk>AR6DGj1l_2&?(AfsLAPfd<`L>4})V^V%p_8>6hN4|q3rSyQ*c-ryO z+1&9X&9g)GWF|2#mf@H@+C6=}KGAwQgOqf4gPBdqZGtr``FYv687l~En3q;X)Fl}l zP3IcY9a35x^Bo<3#0_of=}O(!@?ouZfv-DoK%ect2EioG+X9|=hIj#W0Sh7H_{4i) z;irT3yg{#~mV#4tUvLpc$*-?4DX8{6d^vXVh1K{!p91}=g;w8d!Pb!G zo|G`(6o_8iN&R&Zv7+~L6HH*c)5oq$Hrs5;gNe3=;=RJjb&)CWm=eAW`P5ZXcw>^q ze0yJ&6#!nV4zuUZfBWv>M?-c(PcniH1d&y9lE?)YI1em?Wh&Z|WsLh#iG4f1oOKPS zjIj%{s`EeRGJ8hLEo1QTdWGWH`J9zhJyWov!1yAR%b*8mHqd~MC74=ESFct;at0OG z@KKviz%DNvKGIe52OF71lA{?%{vJGgR>1flf=)Y`HU3svY?cQ zuKK{nU(iebUMPHi)M%3X`)mT>%5rev9N>f+fvcly(5iiXOeD1#2}(PaB< z;G7cPme!957;{&E4c{u(wI;KdZjnfc^*(0=U(kO|m!QgEoueqnIncChL)fNU*IYeG zX4PBr$uUv)H%V=SBbvpf^uT{Dllvj2bKHMc?9wJ-t+y4>OLUYi4 z2N-LUuAmP|@Te@}`m+zrXA>E$Q`?(RKQ;lyI9daPe8_|&CSaiH+U8G$4x}(KWoF--Ub$B?Y8P%kOD>ek3{bk5o7Pnnm$U8Ky4w0WhGj_Lud;IBz8^*et4 z3Y#?Rpaum^7Bf%IcB$qxP6$sRrXsv{S@pV3wrlxy>!4fZNGpw@oAWosuY!D(>D8rgr0jJj z8DwiMah<;`CqChyGfEg^6YeX@kRb%TXsZoZs%zq_VT*~%>eDk#Wslb z?^{Q`#vwn*uls-@#K;~Fr}%vmb7U~cWR8qrf<#{TaYH?*bN>guy&vka>ZD&((H+x_ zsy0YgdV8Mu{jW|)hZOw!>LJs*iBrRx5mFE)olfRK{qL8jaQRW51M?%(Z+D)UET<(2 z%n?2JblI=ua!CJCwhnaXcTJo0=Uij$7fl&W@+hzdQ2j)h{3VHIR-wXXz)mwf34U0) z3H!n#1*mpR?pZEb1HDRx=89gM)kSy`zvVYnkBwRdchsUB|^%J-&i{*dNw|LWx0L;~8vIM^Cs%H8BC5^JazvP=`#qIH10h^6jyHTD7F^Rx!N9U0RnYfRUssDpzY)9`g#KkM3rvv(RNgxedB=My_4QG|LakQ&y@++7 z-Cz7+5Y?Ju#{eDyMFl*uqT{x8nVd)&vYTTt0+4+$vFArFQMw3boE4R-?2o-c_92ZL~HMZ`U?gU5QcSO9bF z6zaW4{w9+%Y1qVGSNOT|WGVixlac0hDxc0Ek~a3H#k8x;>?mjLzFjvyeHY?l07Y7$ z;Sa!v1NLqII?l^5yh@APo%QRFqT*rEwL|nW$U59hF1)lqY355+497B5Hv0nR`y{;B zyOo%z{y`3PgK)gu{%g4Xu@`1_8o7TIPegLEgiq@d1R?Wd;OT~@^1*Kiqa_gIQLkpBr$fiOrGSacZPditFR;46Zh3lN(Vl&u>xDsHPE(%@2bG~0!dg0%Q zW6aV6V}k!pxa0gkFqZ)=wg3V$g*13Ua=MZL$)YhW+GKYLM;b+b1sznVb|6!uku;U} zLHsOnNZgns|Doh>5esd>H2A;V}FJuYcHGZPcJf+gp_`Jk&qxjUaxdsln ziv9Yas#tJ_a>?ZlrWYD@RPI*aY`+zVZAG>BFBt`Bu*M3MHEy77h7fh3_)=oR4ekX? zN!wT}gNlxilu}&xH5uA+b8lQ;PR|WyCxr4^T6coNnGaQ>FY(xX6?jl#z*J2AY(p7k zcxu(;uMPHwENz`fZO6N>aa2s?FwlO8$H-Z`3!b^-q)2-1O_TgXx$nSEOQC8uk0EJ ztadrW^L+Wpc9zBUU$O9nqDr7d;Jv?`HNikYReNvW>JL}`vlom-(X;zB2Cl9DZp8h0!vnRSY&u>-prJ8zf0jh}f}g;w_BGC`H(62gjmyV#8TTQpOFm zG+@B>tWl)?!2gTprs7k7j~_nsmdMWqks>V{Um{D#ftZ$dPwRuPR-e?%XRSIfyE1Y~ zO=36T6a>Ox(!RWfdS#zp*h{nd8Bzuqu=@PNPKI$?jaO;`<^sZ}I#uo+KAgE7VWqtu zw6xLkt8lEI;KeEX^4|HxG=^e}&j(s+C{LSsRLy5(`Z)&ahumLRuSJ-C#8Vl)x4#&m zj6-=!$^v=J8AYxr6PH1?b%w~`&dxREN|MbClbYuu`8Ai_lWtmwD=^?*?hw^g7B0Gi zBt4!4P6X%tG%8nM6$lJuswG(oM2;l{J6` z25y?D4)id3kkZ+?Wz>kX2g7WD)*2`Wz@Fvha#z-;U3=FufLo525AMVt1fI%DBy-mv%$knPQ(=FZ(K z0v|+Ep_^pnS72v=uHk6Yfp}cesFuMU+{1?3{OXFpWjFEsBmgz z#|A~I740cb(L`fowk`UWs5vZtM*tWcn=>}GzltAPZS5Eowe&2iWcRC)uBEl=NcZ@M zza<|*?=Dm;>T-`6wR*oEh97iACWxAL+vm5_Cgs(#D`Ju$81%b_h#?J2`%z5g^J+Cg z=JIAM3N|~yM!!FMzm!%MokA^|Zkr2Vh6E0qa$}jjlRey7WC)nnb50Ov1H0Sd5f;bI z!gQh>=w+TTX=>Q>CH*=#eVj-ARq?2UT|J34shd_s8U%CS6nXt;q-t?2NQPY2TGS#Yj z0U=c0Ff+g|2FtGlqjEqY^)tOInB_?Q&8yMHZkFTToG~w`X^8Exq4MLGZASn&(%pD} zTx`@aW*ARY%w&)Ph#g)H^%h8HOe21u1YJ3SdE21F`W-3ndXW$k4xC94W2K|~q6puk zq0bVKm$Tz1^)OY*ztL@|NNy7jf#9tvRI~ONq&eEw1Li_zqTnJNxIZv>BM0y||xiYf(REAplmj#BU0vq|l zo2S{GQ<$wU`(idM;=4av+Wr-T^u9B&UBLQ|{GD{YwB<-J(6)zE$vUA}!^GU<-*$_L zIj0Y0BLfDyjB(ZLOjULWKmt%n+m+wX5YLw-E%F=v(&As%EK=oJc3W4bm|xR$6BYnX zeToVo|630hJm!$KB4vMf2j;$ntJ4|wh(oq@J~Op%djO}>X`wKMFSj`84%RCS3KW(- zIWJN)E}LinUxuG-@PUddBMj%!4@R4QdTGHCP!w2g8Y?y1=gJFS{GCVd_8Hr5ALosvl2q`T$(W12vzJOreK^(q}>FV;|Yy-`=aw`}$O< zmg&PjTBJ8n3ou`3NA4$0L8xmcM?29t;I^vH8kL>ffw`BAZ|M~cZ4e(+yq7ZKqcm>LoHOe zq)&hv@+{QR#RKlpD>B(U*EJUtj_5Qta(x^&wKwXKwd3(nTKe)^Whg+dZ^z&Eb7+0r zZ8sET)|q)Z=~VK|RZasI*|0H}CaCmF=8VUK9Ij;~R|j;nVRqJ>;_ViH{Wk4!!%2Xs zM;pD1(T{owGbSo(Vc0ARJdQhzW=C0ZN%GU+3s;YcoPe*9BA_B0bd&njWgU!~Ywwkp zD1Xq8G{vp?FLl0+#H5Z{a2<&@{l>C3lGssn>JMIz8Y=Vk8iq}??C&ljz_GFgh;W$; z?I`dxu*D_>lsy3w?t6&tl>PtFu{E<|%kr&g+sJ!l0HqWjmuPvq3=b7)PM?<;s8d)E(C2_;^sH! zI6gJxaFpSrx8h+nrYld6Mh^5VJHoT|6RL{XlwF3Xl9_O)dt-&Bj1G19FZz6`hTa-b zt098gzd6p1<=um?&A+U4*NVfRt$o0PcV%o^(HQ@rgd#`Tj9ls`yaeSI%@&w8b$TC& z*|a9{*P*NXBr^B}t+Q785zz=wW1b-#=i0bZ@v9ABl3kNR|BLtU=ub&=30oM!-+sv&uUaE(|@ZE#71{~O={m|uc1@WHSPwJXos2JR9Ouk3~{a7q=tTrC1;(!U(Kbpf!Z44q4=0h z&RvMa@vr?_T4MQgHDHOxk+T*EOcB}0oW|L2vU8g$^zN|hS>(#9!C!mrvv7!@@X&{j zT_D2WUAWUu#w(IKJ`}i_Ha8f8hxeD+0>8|4sQ&r^Qda~o6$kNB*w~{>4m=54SJ98R z9wEFaLV{ok_)?6j{4L`^t`Kf5uD(rnwVN=19YakN5 znGoYU8g8Uv`UO+I=@c?A{SF#;iN7Bj97Opk;#ZC~7}8?1_dlYv-|AA7yLNW`pcKV| z@d0hSr-E-3F^!U9AEFD`YI>c1>JRmKJcUmZVvey=)CW`AV}itK2!9m+%F2yqQB!jd8y=3vllyZRE+om5UaeNz0Uk z&!6@2(~K#LlX%(z0BSP(xUmEF;AxoCY$f^9R|`E(BD=+KCWIjFds?C}3F3i(>&~{5 z05~+x-m~mbEsPcIv7t{}HI0k~&a-{0cJqA0M&Vo`q{zP3fp-x>pw2e~@(oPmSzGxj zn$gKG%a3hn1;v`W99ylY1#mfy@VAmpQEXq_!LxiDP)MTb2kpkGWW`VBp5{#oa?yM* z$nKykY4&#{tfF)K0+ECLc8Uv5vc64^=FDVmAUtDu*R(o-3&A!ET?xtq!d`)I&MAxT~$!PHJ5jW$axBfUrJB-+RSR zI73^AHso2PBbohM!#}cR^>I?;%X7~5tL6at0*GHS5zY2!bKzUcV%5bn_G@xnrgBL* zPT!D(;l*^X9YqSgf-!qmsm-^k(g*&v6_+i)I0$fprtn_suVRtmNGv;sRsqHls6rptJ1**4b;x%wH35+`I#u6$jB z&B^Z*$ZibgwnR-r#4@0O{(Wig(-qh+?2C%c6gf%;wjYugN0gW>_EatmdHoqJwDv`JxPg?DyVf4FJlk%~sFkjFXm1FZ%LvezY zz&|zz2%K&YD5Ih6UF0B}2(%R2H|Hy*=cLLK+`>SF)|cKsfWd#m5XEEU6_uE$AUTE} z&4G>S>$>EsRb=*(k@~G_@AUCj+w|7%Ti&ggN{um%oS#KE`<)(;Nl!(vChhc4%x?wYlpZCPv3L^elk7jCnNjx z&px4=hR|GIs!NkjYtrX7TS@WYQNZ99CTS~OC_xxLf6WSVg>3Y<7v^hFLy7U938~9J zy*U!6F|CHqGgxu`EHJ0q^nVyI`bS_auP43N2?NuMO)Mdpk0?7Q@A|i_um40Wban@# zwk%94x4|=Y4qOrrWzZA)yXIua8&iCLZ*3boz(<>PQExVJS2fvjlq|{z&IabU!kswH4rMIF9;i zJ$Fy+w>hpK@AQrF_nf*UTY$Q`!|t>Jui#EDol8z{Cyy7yF}A963*XLhqrHyPs6V-8TPu5Z`6mrqEmksOD)jQu6zeoeY90rT zN~VN}s?^S-=Y~v1e>oApIRfufe$(kFK`V=zObVKs{hJ_X1lzHq7@z5Jx7mg8G`@7(j1iA%shwm`}64=J!`c&7Oa$XUtaAktH5D%-}|&gV7e9Gg5BDDbDI3xCY->*RTjf;7Ul}b zSeP-VRGD-Uv9Hi~zuF47r@J5?_My~)ss?@z?~A+wJE#Q}e#kj$0KL~<XInsno`_!-LgTo;^t=v`|1w-xhyfRb#JqQ|at9c9>lWDdBL zGrqioY!dfyb&dK;k0`X|vxyn!EyEvOqDeG(9fGLU7kaW^HA$|Vx}3cJceTN3p0tr> zKdzspACBRfZ{TOmfRt9+Z%MLGt$pLXCnX))r*wy>s5GdxTjCbDA8W#@G885Rf|clB45o_jI z%sh9-HXT{(TJBbeY5@D2`Vy)saHExH@{8y*z(govy-oBI*x@|yD}Pn*XltvXCBs4P zU;u0oyJq9c&g~-H;!iyM!V|K)*hrj+T#hDiFYSRdg%>ujPu2vZMu@P~?L5(OAhyTy zfLT`&{PL*u|6qu$1k}H^lE`;O%5e+8bO)N^b6!8NX)gV#FT(|AaQ7p;=P z6=f&vW;`eb#tV{#D6v@7>xYu%opNUurEgZ3IiBf5o;k`~jz&`5~5kEF` zdr_>lG8qD)7hm0nw|gsaSb%!ON?L55!qxdk>F13hhaoEgbATndaWn_ef*ddq1KW*X zv|rvLWtBvHINwMud3c8g3N{_eEuJ;Ae{L1Q)ykG}_hyqkyxk_@*lLQHiqSn5mkc`a z<#6k23j3-zpj_8MGH>bb^#8oT7a0>5!w=t@ptx%-I|0t990nQ2@;<8mDMBN68pzy- zv49Yw7$9A$#k^;c*TE)+yh6pq3FmSdaAoJCuMP_iRfBlm2d2IFd?7@o>20R^-A>HF z%=|m8et}-k=y*b|N_DKwA3D@O{XO~HntIcN^c&%Zq!!KL3XV2$o3jA?-{e^~%zfgw z5#nw_xr6FZYIatr?ElMy+Y3+L|$DcEA=r@41i~YwUJH zEjyhCIog)?vKT2+RWYgbYkm4bd6>KpDGGmKIS<}}8M{0Rs`1#?-Y6h$W4#Alk&sy= zV^mLmzxD-RE~_1@10+XiZrfcTqs|zKFdQyvXf_l{E}z&7RJi;=H!^^3YpGrmRk^=@ z`~{vHmHVj}Hk!v&=7twt7?vx^&R;AE0=(R!;KEaX21e4QVnfOC~J-(Pd&HvubYLUXp(IYOfRSXZ(;zcZvsBNrz3}!o} z?8)B@37uhqvTmGRKk1`Bn8%a5g0;>4f;w6H&P_A{C^-;O)^A-8z{ptcSs3#?pc5Si zx|V$IN@N?1$I55iqQ2`ig^2qz=8Lcm%2A1^1(hTAf04bm@)%g0!0V*Meq=FM4&!PM zH|AQ_nL!P~g>!V{NU(+Rgzoc)r5e7TKAvI-C`ozPV}^IM*Lc8~)NZc8_+2CXC6l53 zXR|UGa?U9okYhG1PJ5Nzb1HYSFP zDH&z5DIUUgcsQ=4{_Yt!|FTM&(U)LC6?d2;Idn)5M+OWY$alxxij=cYe&Yzti1KVXdQ1}3p{ij{+;T(xhver^R{Xy07y;kUx<10>xq@ljf<^p zkfbxBDf>&k$8Vqc%NM8{%J4@Q-+m`l7$OnLVVVfJ?jzWqZ?!Q#9IeC=rx2g<3*7hV zgBJi_cZkmlag!)IzPX314e0-p7f)V1!5eAcMCzynCn%PRJho3M=~6|!judjWgW}VF zh&6&pA7cswBVI~E)K83ZG(Fh83HfB5ud{qyb#-t1&B^ouu8llJhL+X(cX$7^myejf zOiRAKq@_GrIV2%kSz!z=J{-{adj(UDq>op~YV;h+*jilB7BFS4`G4!ir@_=p@AaRB zI4Oh>wKq`~c)iqx)hTz~1EUZiYQxg(5J`eVYLv)9>dN=$_~B;jb3h7;=pJ+CXwz6b z@vG06C$oX5)5u5px|#v+ve$M{TE5c%!uq4xPQ@J;pY&s#QAXF{WqC~xDkI|~k03Gb ze^k4Yu(_k|a}>>)8P!9btwoamRMs6(MUrny$0sb8TtnRMD~R;JV*GGU?2SjB;NyLI z2Du@UMk9^QeXf8^8{kh&iBzwZ6Z~)Ubd_Rj590kxH!hJ}D*w|xhyR;5&Rwe6F%aRQ z^sH#y3j805JO@2KU_;@^VbkJ@<@d=PnGXDhs<;;*MGM_Nr4LwAvdKyD(sNz9epvJo zVPMhBlJ{oP|D41*#qBe+21GfNt&$WDqfSGPT9130Ie-8FK~7CZ*iHZd00000007?s z01W{ELjV8(R!}$~z)k=FlrsUH0V?1D4go$xao8Ypm ztepZ>uPq-4pXXGKDMJt-!~IwKtzUg*u0O49cPh2x*Xh4&{3rbn?w{j&>wgK2|9<}u z%&)g!%Kr`S542y||Ely0_eZD(*L*%eKkEPB|L%X0_3ix6`#zWbuK&C43+5MxPvt-C zdkFtr|3m+~{O7%Q{vZ8)9e)MtA=6LMYxczD`0(EX-6m^ON`SqOyLL~2a{dT3)clTv zWuAN*7%|PSab2D7eVAwfTWl2Sc%K+v;x-fM-LQhX-VykmyxVg!p~LR{+R4N-63|2q z0GVHjud&V%{`tS~J+y+Zja}y$YW5A>dq4G^$`>jL+=O%5<(u%6ydX$Uzz%WK`3{Ma;fOorCLgW4-vtuB#o7c7yn7H|DR%MGU#34NfRnlV&F7%8;vG;_ zkxiyIho1Z89?Wk9)T77ZRuMu&C4)tnLTQBSZY+e_HfJ%aUEZ!kpAPf=F;^s@41lBL z6vesp25j)R|2)Lp)u@1i2{z~*cY?yE@b1tb?+}X%{H&LHz36B0D)08rZP#0SJH*9% zmc7VvOkdT6uRHpgR?5c_WS3@QE_nGr{_yVZb8$ca86iXNnpfnn9A1=f!nN zz2M7v3-zdYy6+$w&ljQprJBemr+;=Oo(oWPUm}K zPYLRXE&01R|0fTWq9UN8zj24{f`H*Ou-@k({?avHckric0jhYgSo>l*Bye=~2~39t zE3d*i#{aqo zIF9HYTV4{L_QqV|*`Q>BW(q!`0}ABtIbPd_2R<=s6lT~;PugbK=s%|3Df|-xx?0VX zcV6hrN!;2aWXZG{@hi7^D1cDCXt@#R@yEs^p7!;?xKzu?;~D z0Sv*I2OL#4a3K2DL&l+0n|+6N(kJbg*z@kEC?B@%O5a~SlBbi>0s{qNIQc2y1XJrl zLKk|j4xWEjaB8R`NJbC^n#2FH68*X`OE>FZmhSi|xy6y)uTIa;XN(%tmB$!}Q!fO3 z-L`2s`>R;U&`HcY2{~3v^qRUH3+0$C#$)^LbA#TsmW$|jqj(XK00igS=VaaIl3tX|KyK^s+x|xXlUx~y_1apT4=hn?Bl(gt~A+7j7 zok&6de5!i~$^L;G?JGt-{VHT8=;^W|&VWm-hWko(G7?QOm{Pkdri*vw(KAt(Af=49 zKmOhYRMrBqH3@8(zfgh>T%aMgV>D8-@c)5TUeM% zGFVqAP%IOz_*Si0osMYjqqDs07T1}0#pxrvDQZzy#(i06mx7S_GH=OVRz7Wz%?C7H z>3FUyFIk$h`u*jL=xU4<62xQjDB{PjDF&ZKsK03yWXy7yTi4U-1f*)@)UylVu82gu za+D&WZ;(>G<$(@L>Y!PJS7;4=bIy{LhsS*3zND&He{YU0>j8X4&_P^`AP_9$Ol`9tBOJQM|3~w_i2sFwSsq>I9;jp<&Kl zI@7f{#i{RSI47y5g5Yz)oQa!;E7=rfM4QvPePDY~7ZGr>8iF?t+{JK7tt!0evwGGD z)5rw3($lQN7R`vpjlCoeq=IV4qpS^P@Ye{yl=W*9I6*UL0wDoel4&SZbv-1Z?i(pa zLWrI%mizB7;PZu?n_#fZH%ff?CDGL8H{=$pM!(cKi45$b|9qR|2J8yw*{(ixX1zY# zG2+~2-xf>XYB=ovzDMSn34?W&l4r&$N$>WY>FAYN&A~ z_7Cz;+9yuY#cN5I56$_C4__ea2y7e`F;XK3vZgeYvCRhPDj1C4CatwY zG=<A-4OV5*P zg{zLgg8DX9hot#I#&GGx^exp2+!CA9P@%ec2-tt&-iGD%;oPZ60092}{epI-Uz{4J z`9_dQ|0>ZD-D19MBJN;mk|fmR8HS?abh09=q;fArt;_;BdHGq!`IQ$$N?a^w$+Nq) zM!F$^E9nB~w1Z`ALPyI*X^)nAsnnKD+4saa{w2IKek&dHyc}2ot`Tl3 zv~F>sZr01zB;Y%l)USUn*X5PLd0R4O*qtgulFIsCrrK^IoJPdTR-BSh#7tf0QRW;# z%`*B|YMG*?s&UU&Z8WYY>6#~2BdJ1o{(OZ>{I2{wXuZ*O=ijMZQo;N=8mpNtkAg$~ zc&lKe|9Xu5^Ty`K(8JkPkoqGO!30Dd3Vj|JZxbcsPPp;O9>_2wD{1wzl-upxthl^) zz$qJ8en_7L`^G(jR)yL}bBCv_?=cs=?kJsm@Da$q2yyocIq`Kv0$!eCb3u4S^_t{ zl{Ph+MVpLo%eQ^$jNe~LE^n->G57$Wzqt+bdj<$-Ti&wJt3%qqFa~{} zRMm4^r$DJ0?2)k0yaXzqzmQ$0!F~Q^C8RDp`P*LB_~IoS)1WD~y@uc_y%r8uKcPbD z4ip^n+AZk2D}h*o;A4-Ch|s_4Q}LF%Wqk}vx_v9&GE0cMbelsJ*{*q7sg_sn_$-31 zi7PZ#DmB+CTY-jw2h;l%VfE@Y)MzODLk%)tfhDconWSJXbQfdL6x*B+=McT+M9?;+ zej0hN2v0#K^llQmy`O@SI%wgMf~C_G8F!xLT7ISJ9HkxGf~It^oar(e!H;??atU?LYRg@ZX)>*Ysf50cdP$s-~q&bNyixK?8>0ZM^Na z>#*Skgek8&J}OYLxCRywohyg;unzmgC^|o_2M%vaa%|<5Qj{E$Nk~P_YbB5lr_vE#)Uzg-R#{qACjxF^ezk;2_ z1^^)LPDuy%I5BD3Bn~|pkS3fE%?6Pc@#luxAz6lAGPL7^G>EG`wtU_IK9I6=?JV6G zcoOEejC^I`t0nntPN6!#QmGQBwLN@uR* zmIbV{z4xZcFGfKk0aWJ&Z2TXh^I6=N#|`&CclJDnHV^4jStbSWi3YqdzBN9zercqA zkLYT^VQi?LOgYlfiyjGzn2`2?VD8c%Wm43BU1J)f9_hP*x<2zLBq znsc>sPWT(+ofO@{{aImi{DUmpkd6<2J~H8uglzoV?Nj-i(T} zn&`AkQ;II)6I4ayyc}LrQaN{5lRRAoP}p|6!5Ds6&jk;m(@_Vdroc$X+-h~JC;izc)Dn4GG^#d!)t)?i*6o0!Jm~ z@k~aR*hw0=k%;GvVEz_xLELMDfkXe{6g*Z><`0~za;W+%>;rl!C1?73nTnh!Hd{gR|nqI8cn=WstL%2`bw$u z*=bwG?Ke$ogp$T$)M;Om);6)3xJkMdcESwLoiz#pRVQ9zXJ;cLwVhZ-X?}>9&l|$U ztpaCjh8PQxBm?zFS7E5H-`ORW{)`R768pR^s+^>ebq;8Pd7iu_wB8S-VTJFEr{Nh3 zF0KY{xl>tS&D~#9rS1g>x$Y=pPMaPi7yiv$d^wQrE;r1Ofpca|5Y*ewrrSj&!#iHP zx+`QrMyu8L(clt02bQ*2Nq{3cFl5|N;0_D zchRqt;tSAAEVC^G+7{rMCv+JLX|&Le4EgT}%wGDz^#!0CG+d z5wI0mX>n^5|H42Dabu98=vysa6ZKf|1|*0W$wX8*TRW2znfUdQ1$fB8+=8jPIGX=& z=?f@=P@@r;ROOQ+zT1m6bO`ao*=LS7T?Nc=EL3_X zbE}<3+b@E^L^qE~U0GN|kpW7xm+=~=QNg#0jVLW(j(6x8Int@8-s(>_5KBC7P9jDC zU*-7g&}}}iWx9o(L_yclDr^<}txI0%f6;bd>qx@46P*s6r79-IANh$`g&kJm%#fvBBPT(@9dy)Zf=KI|sr19fI-$W@=c z0ku<7WEseeuMbZoaSG&T&>3#BNBe~mtHJe_n9*;Aio`T>{2KCo^8NtM!s;z18ot_Q z63$Y7=v}@Fn{IT=pb9l~OtoQ=!M??J0`1*5`~1r(iGhz&YI~E7O@|T*B%#d++wPe3 z-?fKTmaJfmAVw2L5mj)x&SiBSrLTQD!mP@bgx0@j?ebjf7=ZTpK_hhUZPuCjdW8mc z&|v}`{&so4w4LFz8#~$qxJh9wC4&Tv+6n!rpBf@YuI>BG9~lPoCdE=%Qm5{!G%W|8 z`S6dGE|K$Vf}I=F)Wn%efdZ@X8#hwk1NEQW`S;|Qs8UUN{{RLi5y;8(mKE?QE7rDg zwh2{HK!*$)(3Ehxun`iU(VZh1uA8{$Xak?ol1q1F6f#6RX|77R87URhdX74Bj9hax zJy(_>IV11(XZP@))t58;1vP|oV=PrX7sDy|5S$gcY4sOn^GtM~Jd({@Ucg(JGqIrt zIJJdh(&Ng?k}^7f97QtIp;_67t`s2-E(wrOZ2G%DZwbe)gXZBsSxU|d3wOe$@!a`Z z|NYUa`1P=};S)d_{C|A;0>?B;u z%;;y)q`*9aqsL~z#s|DFc!CyY@OF5sOT^7Hy42Q&1A{9<-UdX+30Wh30@iWjD2;~> zteHz&<5QT+l43dt=G!4d zzOulSa@C5`^O^}N*^H}ImJ}9U3_4A29xcPhaHN5XLRlL_s0?$oW25npW_(eKMzl~@ zH}vB#r0Uu17`an3RiWEMZOC$~(L`~NJez$`9%U!&SuhaKpVYIv{=5OpbR2n#K)#Fx z^^Ml2reB#yICo5E{$)Pu^iP5j<;;zT{V3`PSCVO4S0c0@>Fj#g4X747z--$F5=1rP z$i&A(+)wC`1(-7x7k07V+M~+KxE@3Ge@VH|OS_^tS+D$mencTn-GDiCUWp03fSDq2 zoAO^x{WejdGxk227jhnQeYh#Oo?KK1kj)PjL+8?+yCs?TM5uoPG;!FnHzhn)qeXEX z(4Ds>?VrM|h)qL^tN+&3pot~Cb3qWt&ZZ%>o4WTT1I&MSXJYD(*Jb+&7LV50KISH@ zGw=v+vFzCHF0=~kEt$l_57F0Zp#0e;;!FM~$gyMzok2sliG@#id$tmbzJQ}CbPgTTOE1f(4 zC$5kkDSrG!=!X0o}Vp%!>lqNvm*`!OaGv?W)-=L7;CC zlr5^M&?Wjjpl%2^gs;0A=Q1uoiSG8G$;6dyF(2(ra+1C;S?Zf|-k6>s3 z04F4T$y3$~3bGWu=0*1f0xyJ<-xL{YN9PMlMp9{lbUsCIVXMQ*C*eO6vLE4%iS3yR z=Zb;D-&b0K0GPYT?$qWocr)l>owzv3wo6y>PE@{s9WYUZ1>-;4Bm}#v>{R9s4s;tW zOpg`_N1Dx7QTqNw2rf2}5wH#TtRTec(gnuGJ**KoMY2wVnbmYyH_M$euB1EKcX}T0 z3>U8vRZ~;c`VfZ>$LV}Q^K&&cv%H<7-o=tsXXRw)ATlz$ulDi&vOz~xsM#G1(AXuF zuPL`2=6%zky2Z?61BK%mqC%9CP;j*_GoapheTH$rygBVQ@D`cx#ih)n@M3VA9qxM{ z;}56VNyo!lUP^H~V+)so4OBK=^6hb^N`RLx6rQfKxqC3T4`O}337q+3NezF?fEpi4 z$vq~(WRq@+cs$Rqt`SSCsQ0+TL^#$8(O(&t#CtybrvfqZSK3Q+{_@ zh(;K}8MmqaR$l#GsR-PdmB zqgsn(3qBsy%z^?VNQ4_!dIiUb{zm(eW%uVUo$h?CRWFR72$$bL+2bVR9b;^RjU)?$Pu zI0jdj9S1ypx;O@;DM@)Z8aA3(*3OT(WKt%@&@2jL=<1nTQjYgHi;o*Q_LD!Od0`qz zA@v=7UoQY?+%M>Dv0G%;Y4@9r9KZ$Pl~AX};l9LN70q*V2seg1qr{S(E| z$*oSCX^U8wk^~L6np8T{M5z+@c);^S2heti9u7OZS7S(Fo&esPpxh3!2y!WEtFjn; zy@d~kF)gr>)y`$vzhgwsoO$})XKJrHH&i-}LxbIl5L?UnctJS57Uk0br={V>J+ade9{wz0$4aac z_xoeaXF7p{lGs7OglA-*mUp{1Q>SIM}OrG(b#kmCoKL{K>feSdD^683u>UyEwx!QPm>{_uOGK4m}!;A`u=bqk@g5C$! zOoYEmkiKi#-sC{wOD|o$jmMr3_hzTKZCvHT6b(ZBYCiyOWa2oUN#LF{h;QQ$2MY^5i0bGHbXI^X!W0` z7@D}-P6sY*fK#{;P>x@950DAFUlie|_+6q*b*f4*h=*n=ggLE1rCw|JRIT_Zg%&-F zCO^%?H>$AP#vZw#-;56^8)a(x6Rv#_%hz$y)g*Au>^*y_pH&c?8E33TSL^-A6a&H!3!R6;K;!uNPM=to0QkvKRm}lVRk{PozF_-iBc=F32dq{WrifZaSSx%fBqmXv z38AD5YirNVA)NKU5xrcs&IEZTNOSf%05ht-@(`a8T4_)6 zccZ&Tt(IcoS+jnaFmhw(kpv;YC11QXj3jewwWiOimtG4ooFe?43SkCgI4z%uq}~Pe zU0>o5_p(FP)E4*11^7^7!2(WNC?fV(1|jrc38_B?I%$^bHe@b3mFy2)cO-l?LYtQT zo1rJ-wX6Pe4sq3!*%YoB44aruKYFZn-b8;t#IG`ueex(0mNY?dUh(6nLf*@Hp(#!M ztT^E-C=vd8S<@V&H*J@7gL;>VT>{U@GJZXyN|XUk^^r(i?d)>oy^wSZFbxkX{<_bE|sDOShCEAVmkP&@ag*Y@x^z6tDW0 zRv1yj)xO8Um7Z4+8ObUB22``J8$dM>p!dT2nraY5|c?eIdhH(Ic>l-r*C8E$}8LD=MMHiRQ{Z4%T5*OuJsQ0gb%X!nJ zi;|aiX8-0_gm?KrR?y2_^5D=P3bQo!LGY|iqV3$uD*(h_ivrkbt_hJ%OTMJ9~#@>L?uZl4VMY-;O_`tF=B2S+okU6#uQ&CCsKzIAuy&@ zomRrSf)1577_@)bqf!6lRJl!VZC?Bb&EczEdK4Ic_- zfv^z^*>nMOMM}UlFDYx+TBU{^&27@+xsJ5g4^mUD%?PyD@>9{YL)v-S=&!#Nt;8k5 z6~>wgC@CDq>{xJw1E*v7jf@4hQ+2!q8jC>1vx6vkksGmhJOv_3p(U{5bv0pF#&}{~ zYFI5$&i^|+1O%ph`rGQeImyEZZ_KTvmilDd@Gh9{P#pMrc+L#j?abR)`Pb^ z%VZt=?3VXcFLaE}ya7Bt<)!?a1*7TKdxCw!xPz;SA91uh-K-;hX99XrCf%h?3M3bE z$`?`=L9c6s7%i?ccC%mkY9tT`UV?#s!(l!9asf$jEQ^uON}aO5zB@EUA?n*-fZ7*pM*aP?;O@!lps$mc> z%SvXEx#&ILykNpP(+~kN3E~*Eyp7YO!Hr4Fio?4MZTjOKI6`ECYlu z6|s(Y$v~f1f9^FVqH;a$7t5AU9rRMR7WMna%KQBfQ;3&~NMrtu6UCub-i*hi9+2mW zORkgolvQ?(S2}`P^#iK#GPD=xzFhO?^geSu;huYT%;`Ua`vf+cdkAq7_n~-3uu$1B z!@s*H=Kc_AdpyqT*4CDPhfyqe4nctgDfvH$9g&@Qtyo>a>_h!0vbZkmr(XbldtSf< zg&SE6os?%@`c4oG81VH62Cfl4l@RwA|96NG=x5`$dyxD8GmKpdgtALXZiILqeH`IH zJc*Ck_y*ZS^|b5BhW;G&-OV)nMcbfzl8?zv5wmY1!6})$?^ou(#(%usPK5a|L@Lb* zXq2N|jYiEn*6X<4LmXzj{CXgp*-J#X&XvD*ie4*|wEmJ7S(?NAO0UaHu6azmM0r(% zQ+MK(G{h5HFfc3Z+&F@}$Q01yEuucPyVDA!ufDw0KAXeoXaIB>%s2&vpS?!iTO6hk zj>$)_;9Ps8YeF5O*gYk|quJaH)9*~Ssd_nvi^9onhd}eVpb`PCdlFHdTo98QE`aXg zr-5YNBTt1A?e1CS@>Aj3mgZ$pV)ubk^+^higv(Sxg6`*($!K_t$+)*3=$bZRf!Raq z*9tE5Qlb5nP3Vp7ns5A0ZU`2I zTu=76|KMtiX=Xg&BiG{&uGPyiYIp+Ksco0K7gdJf`KEo7Z*28;N|sEeWiR#x>yUE= za)p~56{FILS1FQEJCUaz;d_%t%0vg`AU-Pi4Uyyv*4rz=_rQNbiv2eMwHg?Gv+?L$ zEGd2fd3YA3sERnIVKK7gdKoLeVKJm0so2bwb6!jv(*w?>EDl{k&Cj=OARGk@&6>&6 zUC=6`E>bg&t>>cfxT&5Tz3{p(+re-aNmp=U*}4O^FXn$&ed8iG?IQ|g965qeJY&HH z-6rH{iZX@F-5ukb_tFFyA@@oi%>PWE0rz8P=rNUBaqrsmqcE76gNk&@bifLgO5fUP3|@tu;?7GYf1 zX-71_5;}k7Y`l_~TPZVaSzuHIJ$Rk^G!n?;G)dp{ZHjqWd$(5=v-2wy+)8)?N)dIh z4@scSh=cMRT-rZnU%Q=h?@}INaFrH}taO$C>X5s|$mhV3m4vTPP94*;!&dlQxmS2D zcn428)BMRA?>E{7+|m~K&BZGdA`^U{Z`7^cer3TYrdb?T!-S7bOSU+_6 zj#OxB$LWesF`*g+1TVhz4HNV1>k0L_D+vDHwPk2r#2RxSfDx?}EVRP*a%phm6#p=@ zaTm}teJjIlN4e|>Q@lylJUl%gpwW(jP*w-H?S2I z(CltY6nlP3iCWRf&9(1fM4!209IHL_f`VUg*Y-)7(z*%Kwh&FyO4=pxWPA>3{jO3< zszt8l@93k%ZYze&nrZGJdw$lx!*@)4CK5TD4yV#x4jawj{Gu5Kyq#g!?}Yz!=R}UEf;Lkv^a5KU8pviqkge4P{7`Ei`FWGYZ#+x4Ed;qMw8T zuU1iN|~EEw|Q^u*{&Y()rjmr_0Z6}8{P zU;HA&N2j4}`J{~%P=bQiboXSx_P%DtDE=J=GZqauWSNoFm?G`Nf#2GxUh)r*xb2Ds zcu{<`4DOJ)>cJ5Nk>q_C_zpbL_J@-0zSa=<4CNeneyqf#xR-^7X+Fg|wYr0vR%|6w z)fyY=D*$PV&u+7O^JvNEwmcW(42Gn6bzL$4e99W*XY|TChlitOQB5yaS{62$m$zRG z5_U}+TJRN5&|0lI;!=POe0f`I8ddzO6M@7I9$}1tNsBOrt28Ynrx|GGbo#Q$#}^#` zlaH^?#saBn7Ob8uM0L$KMZTb&qnW5L0=j1zJ#v-x6xBpYkhw?gRkZU4k6p7~cmovE z?6Ww41KY7xpSGuV=RgB2lKV3>%=?XAv0kF$ze;f}IBzfz$+E)$(3JK&*E4J8#@q+P zoW6IOJP@*c-J9kv&Dm(LY6r(lj#G7SS?0~Au#%_AZI^$Y_S^-^-)QqY&!?NHDQ_QS zoyeh<0eZL&;sO#0HS7(OD8$dR`gEhyW-osd#YQ6>qHnSy<3jb&Rnt*Dt&>&fRmd#*uAH;rHzy9iK* z+=1Ia4cjcyaphtMPYbG_#4`+{L3@f^9vqgRBy{b>1N|4V1jZvUtU<4|fiJ(zJ9HDJ zaWUpwGgS3%7+T@d86cK(u@O;?#t0R5Sg)a>IMRoXk;=jwedY2PfHEUPqwH(lDNHns zWLEN}nY4;B{zM@fLIRUBpc9Dh(xYIP!fM*q0-7xYCxoEA6c9tZ+T*Kuln>jHuH)$I z+gVa&e$0{2l8B8d`!GN6O!@R=NDV!QptX!Q|I5s5`rhA{)z*h&R+d}~uAnLy#>PtE zHg#*K`~;NWO4Fuo-Sme4yvi~>kr~|yQr`ZW`y^6d=S^JJLn5L)&84dVe21-q6}UZcS)*P9 zlGZMLM8*mg_uy`vBg#*NpI+ZkW!(RbfkKH5ES#-!e=(ZoX}DhX@UMV1lFh^H%)_9$r>-Xlo&JeKISCbHhgEhUqF8ajKv z7y$x5!rYP;JZ!{b4(}hLF$%NEX{)RZ`RbR;T|XCVt=WT*f>i5IR zh^#6MCvGxuRQVIeW-w-}M3IlU6}^c2-+!kuSm2qSC1|(#rlrPHFdHwq7(geezI5?U z7zZYlz$}lmFqbl|FcPp8RsZO(V*1xA5Sh(bt>9E2K@*XF7QLey`I|$&R9w$Z*gy&d zD}Ua88h%CM5zcynZu1c*iNT#hghGeSpdb9(x_9I91jAzN0Euj1PU;sl%vyhstKj^H zLYa?VESGt8Qh??H&T0=LmS1ByVY$$Zdx65!HON!6P<7v^`lMP#th?a8Cbh^1RK2p~Uf z_}=6zhtAVNalJ=2Gajdr3dR>bxu@Zzc3+@?}Sb*7FQs%1E(Oy3jrwjPpFTFC`s-M579)l)&&`CGMU!=Usvw z@+y@P_Vs(WK*}hOx`c4dWFPce(E%kO)7$<`4nb!yZ`;7RvA*spY21+*gn7Fh)l6u>pCqWFT}=ug>TduaZMQ(Mde=?qzi+Jt0aO- zSHpds*b6-iDwC`!MgkMAC&`1{(Uv(=)xGkPSWZ7Gqx|X&{xjLQ{SyZE90xDH9^vdY8l&vEPIcm$8 zM;uLQ8MR1ay&@AjaLreaJ6_XhtoANSEnSaN92F<+UR&vNB7t5an2B%U5)Og%61UNL z(SD_BWM8=4zW}6H>=C6utcF;&6%#k`N&#^J;=Y$;}DP zfK20H+$e!WJkAPA{U1-8_78-6glC9aBpprzlTi1uy&IWV>H;!Jvn2zCI#(^;=d>v} zi7}c};3JZ03?rgdQqH;CYvGhf3JT?BeK(C!GRydhBgTC{H!3vHg`C2QhRTt(xvO2* zlw20_DK*~N{+&FEu*&^0B`brKsCg`04(qrpyRa~#dUZEv+&Et9;MbC4 z{xh~A3%**%()jc6p#6w|i&)dcmuXs2o!}3`ad8f+KGM~MyD$-YH?7x^SMvf3JlAKc zD`0!~v0qG7q{1)D)J4Y$f(-Ik{WixeA;brxGhKNq92ygQ!b+dlT0v(94H`aEEifPp zpYwd^)e=TURPu?I71U%zZDwTUoTcb_;rftp4jSj3=n3p4+9KOABBtb`9_E* z(6_PhN7$B@C41VoaGmc68CB(^?s9d3`M%kS$Vo+(uK$hXvEdV;z+;=P6-ZqYmS|PD zBvkxh09-w}fpW$zIV!-1j>s6&Og-Q8(^nUn0g^eRe1yEfl}}%{s|h&Qb5EA~vfFE< zLwRGfCs{3cJZKa461_j&1QP{!;~fJHS-gU%Fm_`A!{T!)i5`4uOPGzy9mOF+ZSnbv z99EC)zx*EI-U<|tG9}1=0uJDx$!UX4{@84kuzP87Bs-eO1^xWz@i`GSw)aFKpO3!u zW#QV;Ldm_^bc#=eS4&q!2`A0bqXyWbN`r3Ed@@VRI+mfWhb1sFjF zlG(UQcC|5eRXj*+`Au3Kij-{JahBdP#;UR90C!t&ASW+07W7ipuNI_Fa8uS$TJIfD zm3Wb_=TWyeU$`+lg55Lzw-0c&tlG4k#26FdT$>cW1g{=5>!dQyUxW)WGwk2?%m~m< zX7|v+weL;6WMux`xnPt?oGE^V84eDI*U;UNkCyV;I_?POiXfH+?hK}|4;eFH?0-AA zf5{+Nu08Q{N+OiLYAd>#40=Rv`6a8YsIM!rz`=T!+OOmHv7Y5I4-7J`nKqIqN{lZ1 zK+`21EA(?06M?+%(rLnEs^ZApc^{f_s9t%0=hZ?vPcnBTqVll1jtVTx>m<#?MP2A$sOPsEHAPfaS2RfL}F z9XDuCb~g~E$+b)3=AHK6WfjqA(e-phS@8qLT_?SJ>{{lzQmAc|ADnoW_-a3zuF=5I zvsTXJa`jLb-zkXCyM#q3O|<4~19iD#W!Ig)*K(RHod-H_-^wv}A#bsY^RdSzEqu`5z}1g$ueRU8#kTlZ=p;VPK9@r z%2B98f7pzPuPepGQn5QnkaMGO8J^QDojb%}%c!@>?mtGV?nE0Z=Bg}=dj=-9bKpr( zPT^sah#?GF1x<^;q(UJ(H^IMycY;Yg9L$(-+veXgiuE;3Yk1SZQzIg#7JcdV?0nVH zlYXY#kCJ|vnll799V|lAHQ74wA!^{L8++I8;Wam~v?kkrCHB#0R=^>57C9_ENIpXk za}lvV`f*-{ZHfBC7<@uJlvWf0LQn;aO&qg7QN7_Ti;>gp}X!#EqGp@?s+1;F; z&>Lnn1ik&orjx%!m=Fa!CtcCHbdu@lV^=0y_ChC+D;XTN&`w6DVbOy)ct)Wh#QdhV zhOv(G#`cTUGhe+#5T0nBWgN3!7iSQQt@k(FvQ|Vf)3T9(U`K|XlqYUT)5T`MhU+QI z$c024>cT5|=#gY#T^g=bZz&(BWSx0E{oxo!Tl}REYhS9r=xff5HdEO+9fl|q*qmPq zwm>k>Sl6u&N5qdw6%YsE(;xGT!{MK0pK+-KvYkvUaSrU`1gYI-p*C+eBx{m7!!`<` z+^EqfDp4DbPt$ZQ@gNr;^N%yhAF;WfZefR+l4F#Y`>lar0bWt?>+)wn1V~4+y-FS{ zzZA=%=HnuGN(hW5#IINjgD&^Cn(&(o%)m`PAbMKw$~c-~;~XyztEx+&c-|@y;oT}G zAEv@Ej3*YMF8H5rh;5{e?6kD36-%`_pOYjo4=4brVnpF7>5q8={E)a5Eo;k-RRCdm zfQjrk@)br(5Xx3X{W%o#YHdk7PKy;zerL#r9SWB1fyZ5t_)7!Nr@nvO*=~*>x;@eV z6-NGB)2Mic=dUiUbZlVzEy<0Hn0A?MM+!f=50<)1Cb{7jp#*AXEoO4YZ#m$@?NyQ? z!_ieQa_{&%Hh6BkQ1WA>@iOmD$k1$>uy1p7^IoB7gd!4@tm$H2s8 z5nTk$(E656b`R%|C9?zbepKhvXU6x-XwEn3R4eMn&&eXl2)Va;1?If4gUm4Oj6#Lv z?al6VdF9z5I-3AI$p((z#V0}tE{jlXeAkD2U$Qf%0-KL0Q;d~o{r5SX+<??n*86+!CcpEKR+s2)y>Pt|4DKf zMqAk5`-k>I13P*T()tLShE&c|;$h{iUVM*vTSYQ_1hmi&y308Yo)5;rZs*-$I7B3Km6 z?C0WN%kV@4jzREut?EvjPKCN40O;^Ym~KK{zy#3{2XC)CA}<25y$0W|eksEA-G_zm#XII{f9eM_1K5#|~XPO8MF|e3F1GcFc=o;SWp9);Wx;O3;_X;h@j7ylU zGCROgSsV#*9lO-@nJ{;lK94T zcjPa|iK$EiJYc{GQ2?l%p|DrL^}sYfsFbxk>*hK=1ZQpflfWAO{31;?^)<&0;_=sk z-5$x8T2OC-Pc-uj2F0Av9~<5Li8 z*IC^H!7b8-E!NqUQ$+hEVRf2LI5eqF5phk$@#2VF;9K`G!beAqH{QZ=OV@ z)r=%BrD99Si!G<43ak+i22EYQ^Z-$Jad?Ws*8AnTH%nF%S?V4EA*h)!s_jRimpYi~ z*Iy(5<42wClB37!EA16o1=5-|RDv+NaT2FGk&ECG^b+17av491{}@+lJfdM7KSC~k zC5`WskN?!i*54@i;VAaJCu$vx2esi)uAp=dKl&1`aN;9cV!LSMQeHYpXXHO-qLvhY zijlhgZJI$L#ubo8cLap2Fs;C>33=xN(&H zB2+q!0#yysBmO@=Mr_xwH}ldaIR=nS7cqD!-+eaT+LICm-@Ls`{o@!V^4EJ1gl@z^ z*;$_>n=|bjlxAnb;@e%DBs${Mlx*-`h7#92bCsKiw|4~XdYGp@9cvnOj{v`3NVISc z>B()1a}Z2)^xZMdHO(NWgg+e=Ja6irb~So97~`IOnYLFR`W1I1^$jM&a0i&S`Dp5A zP4WSO+NH}!4@EhkmwSKQ-Za!RXOX++ z>kWA979LUH<=%I6S06iEBJiR^n|BFM7r*!-b@Y&V5rl)ig)1`k-X|b{56Oa=XYcL6 z!qG`hV?20C@G0d8z7@SYN$psiHoTE61pgSWvS*pws6GV#`FP&qP{^oA z4%LvR8T=DjT$6SUd|JQBC~-U8Oz)Zzs6g?)v;Hc-Z{a*YtgY{-hCiAJSKKAm$k>{eDg0CC)q#>J7Otrih zR!;30N_(}+I5o8_8rxAu&r#eas~z*uXr3sHPGObUdJ_w@MHI5_A^UNNK7ML&SAcZ2 zgp(2IDh>9y$|QO~XT@`mr*Yd5zXftx1XC;wtSLEtaI7!_XosqnnWX}dDn=U8E8>VX z)N@w-?VqqXFXI`^0&6u=D>H*8KtY)5>3J?mRE7;V(MYtJF)@eSWxlOZ*56GaH9`aO zHzuMspXD&a3%q546te$i(LQ`W;OUSjkTufXamHX3B{#{@HOX)G>0ETyH9~=iu`th^ z3a3k>+mD8-&D*B|VYLK@*z@=Hc50eSB9}gz3yE40Cjr+L_cQ7EP+&uW;zAeBBbpvC zkUt{8rGKvbAc5ZPA`d#58QGuQKB^0AsEad8E_Ku<5KFUX{6?T!-ZXO2C;aHtG zQ@G)HEMqA8n^APc0cYOYtkk~)f}?gxQG*lFh<~r0E2G5^*Ot{veM9d7II~ULr%9CRazK3q)X1N&4A1}lAE^*WgmfL+zL<>7Ya4fzJ;d7u%rR7fubbLh zi3o{vHQkIt7tjAf1H@sbXlI|J3OITKM zeN`Ds=i8f!r6r>%LM!-OhWY!NP~$qe+qXFA@;(}_5hBhdOLboKl|BDQJx*q7O%|Nq zSxnFi%3c(APRjE8M|q_my8SA9m}M@*jQ04Muc-r?Jqmh)o_?pQ^|i_+UsyfxaE zu@UYZ6rmAL!0Oal113py)wFD& zmO425fcK0T$q73O{(4Z9_;yFO(I!al5E0!OOpT&p6lz%WHiz#MaM&TqMeS0*y;bdC z5flE?_0TQSZK?~#iQwZ_QA}l!RAF8izhN@gllAaxOGTXIn$NJ2MvJGmOHuhG$Ee7? zi)?J3P6iTyp|nbBxLLNW$g~SR3Jcw;3_utrqL}?1WmMF?r|gUew6X;09=8J=4hQ!( z7$N*XzHPEI%3S^tNrb_RfTURlJ`OQ?o?hi@B!sGUT=rowp*nrF(5L0_;mm#XNa z_F_=a9QoDi>vtu*i`PC(`m7^Q;PRBT;5`F+MLoo zIb)%D_)=bV$cxC#ZS)}-uGOuu0{kA3toSnwEa=zdw&&6bZCbR*r2_ha)?ybYH%cjr zZ5(%^1D~xC*hy)C&ubQmFu)2zmv~jj)rQ*}lD2;iyF61jIN^(Ua^=i%t9?bU+Qpxq zBYv~|QAsc31n-)Li6Y589@w}Ot2{RU1u|BI2IG>pO`<~b4q*?x{X}*%qY~MwV+H8{ zcSIGNJl&1lOR5cU08IGXe4h~P5BLr2K`KaHw=;B$NJTnr)TdDkLxoXWpibIv{%ZBh z1PdYRma?hh0Aqgqi&gUNk9tVN9tkffwLzer6@C$-`i%dKrHfQ!Am#a3m%mPHz}0e-nQV9%OjE#g^nan!hbyw?(X98!O~tb|>Mj!&3S$@6SrT z%uPCxNkoOEgy{^?DY+WkWJB0+acjcDelulQ#7q|va(!sI%Ea!VS05I2U$PTOe?SdX z^W;kVysnz!EI6)DB0F#LQGRB5?{0>#+Cc$an2~94=egH*?AU4l7T?OCP!Q(-<)pou z21GrPzu(b{yOSV}c(cA{0m+&Kx)R{{UK(nd&)z0sa;nX;gNo$afsi z9D{yeD?Da%7ks66YB|5=qnz^?QUAYqQ?j!BE^=!%-~4L`Xm1G{1)6$IoRR9IE63z_ z-aXd1CoaGPGx=w@SuO7UPevW5lR;EJiFrF`eRVe72(I2u!JTibiO72Yg4J=Jx$GOJ zLFauPm-d*0<{mJ)_iqCIqxKyF@GNZvOPpvRPas=`O_!uvGJrMpcS~rr`ysAA+I}qF zCdI{nsP#&>Pf})eDFhJlYtym@M+CbAL;3itNI<%&RB((~Z6G8rppT4TjR(|%@{e6V zO6es9lJrj8X)CUDw1d5xjusJ-$YQEa1UzZg?7u}S@wXF5~-HqKMd4%}&4c{qd)n>i&$oJ7w^hHBW+5LP18iGsn z9iS*rzL>w**^u*DO!=!=M1;kc+&~)mXLv6`83HRu?P#N2<~7TB@@=O)2EVM~H?SFA zh4Y&xir}6vg@J@E5nX#JKgLn*gfLj-a7hi03M!2Y1)=N4g7kU1qH5(nJ5hNl^#wx) zx`*-)tA#?BSOrJqHjNa@7Mfb=xp*1TqbOz^xP4?R4N9NBhv&o(o>dXUFybpOf;(g=J!enT+R#R237&=2-IMPVjX-c zE_3pDgnR+}Bx|AeC|#vcF`$$>tOM1Y?cx+Iwv`UB;M-ThqxuZFg%U3+I8zg_RCMNjo4Kfd5Dm}l2U z0!YtRam1LRg2W*I|0hOb;)lX8>R3zb3uG9xc4`^L+h=jsNChJ?V*npXE9L|^TQS;y z|A+zUR;#T{k-*=!_H^*I%L{op*TV zR6Xt(Fz1X&T!ip%wSR=CulO>zPYMT!i$L1iv&Em|g*lWH@Pg{t)+0y!RLwI=HvwRD zRPnb>OSEm50GJf~>ihCFXIZj-EAGBF5%*WEaCzU}5cg$Br`Fn3k7u>{1b)AKT;Pe0Onw2fks7NJ*HEuQuT zK=BH2B3$XPmU1k}2p)p?MDcBjk5zw)d^zZ<^^$CR7+DPJe9Hj7qZUJMkl85Y+bog<_!kuEd|^C5uG z)ckgRU+v%IpPc`nee?XE&CiW%ulz6HC!xP@|C8(U{GSB>YW}bP7pRZ8z9~Ng;W*+C z1pnCd{QS@SU+Mj6`%V8d+!OVi{->}$9v_?k!|Wsd5B&%2H@$y@ui`&^J;gs_y6t*R zozk6*+Rvr$@hOm~ul87~Nzr*Gj|H508M_R%ofckIr<+WSL^HANStKPGi5Aa1QfTc-(q=1+Q*6x24)nFwl7q@)~|6)hN7sznlWZ(bQn=yRbI8$A}x5yFv;h6K|Y-R+T3$_wBY3U8(@Oo*G0;(1mvM z|0VFfX1$1JcTk4c3q2H=0a6S{t(eO3(cuZeHJlGob&O}jg##djHqO&JO4X~Egvyze z(S(7n^3fE$B0bQcIY<&Lo@tCPtlbu65=0|X860TMmM98-k#MCCMx+6;nP6B+5kQoHYaidA)0Ecv8szJ`gWEKr+Shi;6Y>fcUWs<{_FVZN>Ds z!wq6;CE3D-K*IRZBzj0zA>MVY5X>j%1oq6s9H0=I&fo9F;XH^@j>l^pyD(~}&py}B z1QWrqs7T}`;yA*a>ZzNR>ofbord}c?SR(O{Te@FZ+kiN!YaNu1oQA*Og>!(LpdLu={1LcaCmQ zEpm9bLDEe)@zZOW`pJL+pJXazRw6=>BY})ZOP!Ft+`K8#`jc6RVTjP^5mMxIB%5zk z*G|gRD_$#F6sC--g)oGQus?18ajKHa`~_~(@_r!H8*n=*#GVbRP^p=goAm#|fSof5 zLaTmc0vDjhhnNh^2VwOu; za4d`27ybT?dnU4MYlkjs(77~3<#@-p_NuAE3sMwGts_E;d73|Zp_ujZ8 zCQgFo;{~4?P`sVuY5IgJrKyy?`;@2tW% zUc3wL*NOvVW`GiftNiRinzGK^A*-lr&&pRQ{*g6sG1G!q%KM^#LPM%t1DV7urSuyB z>&?8R$Gcn4@lo{Q=A7bbg|evoZ_QvT z`{=fYrE`J&2RqZsQR2Uh^ui+#kY6ACO~#&E9-x$Xyj!H&Q1km<@*w!X)nC*JY8yo% zm^gWZiPgV#4|gU6Rf8uD$l8=@XgxThi_`<@;jo9+me;X#?`*jer`Cz8y92l8vSM*0 zC`&jtzKa5tc?^C*xp^QE4>~A@Q1NyMDikTHUGJcJ&p@X!0+`2xVQ#!HPP#Z1`jpGF z(&ki05~Ky6uCpgUsB}XTjnjLtTK{Xc{2GuhF(wqXsk*)foQ~CUa?|QWyUS}5cD#^b zimM+9@F^FjVV=Fr=*4~2GCQe(Wa*uj^XS!4INoRi-D*ODjA^SL!39_8zyG*nvUbWrFeO} zLp^_HX$`EHJ4!opCRz3r26pliBV>n!WuC!3%Q-><1_ei`-Jlu8h`9--yewn8fS#%? z?;K?^p>O3#SNXmO?80d!hjGf%1LAC6>j9ckM@^?RPR)*_llRVx_Kd$$r)N^5svFsH z)$I`5`JxDtwbD&q&K32}^kfEc-hm?j;1>wdu$9k9la+)6Cw0di4vg5yDwxN5gIgOEf>Q zpAi&=ju|Pkpva4ni(f@C-AVxB85T~$>6LooIeXzjue5+L<@YV0$94G#+m&_j2U|^% zh?I*#e~Db>W@kt``LG*Yw+;#UM@8k2Vli!f$$-l8#%byU2$NS7bGjFi5-$O^#j)1$ zO@BpeXJqG!^^5ekAm*m*G4$ zTIw}E_%PZ^-}1-mN#i4T&f_;QSqy0jaj{Alz00}J>?lwhtVSu$3DIawZq{QJ0XSM? z^C|eEx?cxEVrksD$whRVBeW`v%VIBFaM})rJX_1E&b#`7iIUK62p+tUcqRS}r~l8) zM_XF`Wasy5;0ElhH$~YY+3uWQc-gSw+VZZlsjFI<7SwXxtUwUHgj)Lc00b8F=tW~4 zUnTY(iG_LU#hrDoQ^GJ+xS}BgC!xFkf*c%Q6KCa17M;p!v80FeJiq26_(-E}>BQ1M%~$s1-=X!lEyUT($M{ zo{MLvNZC^XsD5D-A;54hpXtzlBkb#H#(Cnsbi(ym$@^apI8vv)AfDbOw*&K66jlJ; z;u*yj4(b0Qt!VH=a_Wa*vHaII3~Q2d`q0#kZCuV2T90S(ewrA_3{yQM0Dq%PpR+87 z$9$|Lu=95$E~o2&l|P7&>r3AF^kY?E+<jnurV9t1r%kWqSC29N zK$q@$7jAGG(I&aXI}&3Y>ES-9YjwTNR=4P7;#X1xhR9}5%^_j1rwWK$wU14d^Y`<69K^z@b;22vz5qpT#i<`K z)5TC5!0$*!Os8iFlHcmwSXr2&V^c$A?xJ{kqOy5#pQ`iEJVb+&|ITEK2{;jKgzpXw zHmSX0y3A*cPglKWAKnJ!r1Z7haHYlkaGTM+NV6){BrH~79HtUm0vD)~k)@oVBeU&s3ocQ5eI5=KE!#k9H160zX>Tue zRLWpG3tEw`7raNgubbjpLZ~06O@E^PM)%JPD8CpNN($=#xw(J z4bG}(1|CO|W2t6QII*;A7#WfnPeVWMi0Ds?6#irHZ>R4;@El+{EBB@1WB(9NZeQF{ zGU=TSk_inACe6D}RBiG$zh|~FA6flM5s);DdLs!rtm^RJNEG4b7Bl`(N{H{lPB7W9l&8dZzboYov%rNx5chfONl`04-cYk?^T)D4~?JdJl|56Esl|1iUrg_Yu5PxHtU zp_SCJ0cNYqEP-PNq%pflFddB($LD=aW!txVK)!z3(aE#s)?s>cNK46TU412==kI?X#n2#slz-MEF(eyPfU?!FB zlG&bB?gK)m;9z*An-@Nq;Dwzor3u`EJZ(+g1tT7t^pL&A_UtJfn4gRUn1D(NlbW8A zkxYg3LefVo9*;S^mUhh75eXk;Me9KffT{0<=rdjRf_7H7 z6OtW%obdhDHIU-w2gTCwT<0Nus!9cjCmF$&?8b4u zP<>Cttn#(&!COWA@QHZo=p|du7uN2aTDb(1y_v zu_c*v6Aif`4$j@p^{|ON<%9>&7dY-)I~?t*0K&H$sa+EIk>MuyF{(79tcUC^rzHvV z)mgNoHyu%+@B&if_3~XmS`rq9tX8H+iZWXToENoY{#Q=cQvBcUxO@k-O*X38$hRxD zCrIx{?+^^ZeFhDPlGW)y@l+MdOrZ{uQS5l&RvA9^X_wD#B>W~6P^bDhdn5N-vW9;> z-@P>eaA-HGGAgvuzr{eV@Yb}aVV26$Z`BvbF=CcbyF;Jy)E#$3Q=qA?VR1~VO#rVz-mE935n#{*l)J7^5(0TSJEI7*j9sO;+e`gn=W>iqmaHdqH^kvF z%C$?64@5Qi#a#JLgMw|U;juYZ86{LMw!_?6pmE9pB=mRi5`9b_2>iQ|?};k6CULz? z6`*29TeXKWOt7l;^`mUnvUB9pkKcCrr)NY{o4O>e5y&&Dd!^QVoZ9Rg=x-B60XmQq zSw**B4nQ(?lhuQAkY|EW;*-F1|No`ULx1t9#nQbRPvbvi5P=W7ul7^#O)ofo!RQvu ziotAD2&8~vWFRJi)y&+syfoh;;?k11L;Cgnog!cxk|vh!S{Us`YHgw`yRI8hX0ZYF zy&5_g_ATL6YYCt5iZXMy8CabC5m5%d)#o(x?<5y4Kjcvot4i`Rr>rrw$hT{fHShUG zarr{b3iE$!vb7yADQht_LRNoJ0c?^LQfNxWT`}&QPs@Q`T<-_Tmq0ejGkv{EQL@mVPL3JUg}wX>!rTTJP>pTYqRSl zAN+OkZj1H%VcOK$E7+>Z9b%>u_R(vp)>;)6cS$AQScRdk_e8~{{2C-Sw1tzzbPmqW z!l(TJxzRXL9j9q55eUYMQ2mUYOOX8~xE`KY^UzF>$tk()41CtjFpvE07F>sW$WfcJ z*oqfo`&LRr5Xq3?iSS>oKXGmU?)gztA7U9F5+VkIH z#PdWRCZOrsmS62)_C2MordXFpfwu_VEdPQ@w(c$oskFAdx5|;CjdaS6oxv4igF%zU zUCQ;M?XGt$DG*!S3AM^W0$==|!JfTWFwP2^m2?le2sa0G#6)Q$)yq0xXA2zuljVUUkX$HGBAK$}>Fcj!87GT4GiqTM7N^cF)^};} zfZ*kGi-SypJ24l(vR7uia&;c!B342=hbJNraX08qx2xEc`aPp6%V!mlMgR8&F{YN+ z^fUL4Lb|m17MEpHMVz3y_uQsb^vwuoYja3II^?9|@D{dE75EOv*)oVjSt#JxED}A> zo6bu5(u1$0gN(K zCXH+|oqM=GZqtcvc0{r-aWDzMWgl4DLdgyQEVhgKIQ zcC_k27A0fFpo-B1^(q#Om~h~ZuyYVjyxr&!2@J`AV5@MB8fNDCJF7M*^o1;hXZxHS zCQQ*t~rv-aXjFT>dm$&ui2HjazaJsiA2dF1b4M<>R9 zIj`xq5<8k{wpkssX2&J^F^4MJBZ8+7Ws}PZagGOI5_P<=1a_D0z<~MWLceg<2;4H1 zrc|CCm;)~Elsp=2?e!cv-2`1m^|%g*@d+WMeAydzGrYv#`6+)pMBb;+XgkpuB*g)Ni9IwAt-#!EWhYS-rd_?QlyY$x4N zQnHD|J*5BL7R9t?f~}(RtMO1h7A*%gsO>?jRZN1y>)E;}h|J~C)eUQW)M{n=nqIWH ziO>E51U&Sa?Xr%UNTn1`jZ_LguK+>-pD&`0`sz8;lE@bt{&k-yp*!#k^+>A-vi@@@ zD2TbC#OX`8hwA30bx;)UFv`G9@-Lt`}uC zydK$hG3)a&h{EFZ&^D>t4@5G=HU&481qXCaEI+1(BuDcV&ra&IpCIO@BkTazYM7yC z1^2tAgqVFq4QZe#sb`+2{^=lGq+jXa7w)GmVp zptXF9U!;$H$)XmrERnHiEeYar$yyRMJiea(0ruTRa1qqee|u9q32C&iw|#2snqLp+ zW-|GatC6v%y3}#@yg)xFL{i70``pgAhO)c)Bjc|iYE1l7Md~<7a28ez`6gg72Db0l zO)*0az;!|*S<3>kz$6&0<*Hnx@#CB*2ctEnP=jV-=4T14Sr@IvwX#JHn=tCSOym%t zR;Nt!zH!mS%s*vi)iuk7eU0b)Ief(|_h?@YEn(&!RMP;^09I$ZwvXfGMqM@AWBA-x-|!~UE9606!7F5(Um_BPL96LkFSZ3O zHKaHFW*rLRm_=Xs-aG71-O#NTR7rA7;&fcI2_H8R6#~kys3v+I{Z-AXlYh*#3@aYj z_ertoBKWLL!KFDGVmG|XUrfdVfuDBlO6Kxh@Pwdp+9p_wgP7Xpxa^bYED?opeGesK z2+{JKl!g7u;D5e#OIU%bb6fIfBT9lp4MZaSy&nm7(yy`$NTioSXNR8*!HcXd!GGS} z#;k_;>!9EwezMte{NcEAa#8y0b>EYhcyPX3j3HVl3JSaL4c(#`~lKDCPx!K#A} zJ8~{Go7~2yH7lq}C*O_m7ZBSeTf z(;=%EmNlc*50ZiV5wNTR6UwfbZhprDJFwmqd3lASao9o8@w9Gvrk6n2OjgTs6mjAZ zhua8UvThCS9)qOhRYlxnnw7zTE>(zpQw4{h5+~TDmIU7ZrT#V)UgbmcRDolH?=+rR z)Yqs-+$`uz@-NKWF`s3aaJUMFRX6K_V@L@97H64JOH?*v1+R6k*VOyJPV$RFOZ!$6 zGTZvqmew|6snv$HiF}L%k!%5gu~yx^*MTbe_VG>qAQH8e>!UP&SUvI>%k0{z6qo6C_iKXUid*~_juoP7Y@Zb ztsw=1{ms|V{^o=;=Qm$uS{x9P=Z)%Bk)UNO|uNCIGHiO zSFoL}`D36?w-cZH{{2_%>cu9LYb%%+k^B);p+oP;-lPS<@qC+5jO{9i)9xPHHs8(A zlai}82H5F%Gu(AUQowfHxU`GGJv?Npbb$+tG0O)dhs1QQ016s^@rwgxwo~*=Sf);@ zCQ@`Zo{N)fRopM8ET`##Xi-ON!aAV!IcNzvpckSN!t-mjvVGXi6+ds0Crn!wa_Bzuh48;0wo29x&7S1nR$ zo!&J z?At;`-7gMPQk4fxPG$LAt@7gtHA$&jr?m2D>hTw0%4on){~m&-(k5EfM}^FKfQeW8 zjkqMjZ>tXF=Xj|(9evzj_DWo9ydH9^*Wh~z4!oLmi48oP{p5J*zug zB)Git?wSgdlg*nin#^q|=n-&D*@tcfefVv#x|_^!N_^jWHgNv2sn_C5pX?hmZVa0V z52E?ub&5!;z1RGlB&qJBv>y2IpLS1}laNS0;@0L>!n0DqJcSCOl~d59zBiARHum1g z2tC5ulNcn2Bs8yl{zzkftT}XC>dr->=XA|^^J^Y(EL~2$mTIR~VE)K;m78zOgm>>> zjj?}v3rH51z)PoZ#ZEsZhP+wdWyh$Bz48z6w$)dG>#sA?lxib(cnMZbeKqHvn&i{e z_l)=ICVQ`^G~k!-+~OO^jv47J_^kr`7*a z0G-C#h#9+004q?;*05R$c!+;{teL^F8FeW|jG6h>Ro;rizEsc7iB>z^PQlMmxvMw- zS2zvby{B$y&EPXlF~U%kzoT^bDK*8_GyyJ4Cx3}1KLQsf@SLi9yCn2|@x){hV}*`4Wbc>Nq*&JvZ^k@^$L4tb7-~WJBP?Ne$C?K~1e8p;^hOvgo+1LFNNu}qiazOAPOr}l7@c}k?Ty_VuY$x_l zR;Qr}f(bN7`hx-}qiZoG(slsh-h;#T4-S-S=cDV#i+RXR}uJ zbS%CWScf~OU{WBjoG&Vd>4KJ{C~v8)jwG5*;N-1yJh(lj(Al0CfO$B<87ZD&_P8VJ|Xlt{%Ux4rA{E?=cV;0DuH7R#{-|aja0bx!bIO{tZkB?vu|65 zCAiNzKG?QrINtjlprN<94LkDU+acZ6`wEqw7Q%0)@A}4KaPpJU%%?S`ji)x^ykAIp zet8Q)GR&nLD`+cEspcH_C=1-A1uCYcSb>V5LwzZOYAq#m_DTtLSf~u6vJsV?Gm=$7 z>fYHg^YiBa&1zxs1%=8uyQ%Vh6#ng{Kji$y6$tokXCn;Cfen_5hV2IJGY#NUgajo6 zrzkiW`I$&raVeBPixl*z<@l?P2&PYW*oIHq&x$LILi#;I1o1r-eD>VT4k^P^#MT4K zb=j#NknsvzKAdAiOB;(JEdpnK1?tmzObml1>u7jH! zij1@IV(6w%c?TI3TM#r6Cm1OX9g~dkAc{2Z9;d($u&7blxK&kT2Q)?Xr^(dxF^2Gj zxi0Buw(X1#vgkH@_muCR@0RW@?fi@z_euCoLo#8SrZO!MAJDo`S`D(wMW zJHUpGn%l${v$d4*J|8+FlOP+$mhTfl>@e7|E$bSAj66aolB@F&U;+Y|EzhnOgzYVp zkFsR&9xVom`P35rVl2RPU5_#N;PS>PMkl!hRTG1D3;ja;Ub;+J-o~`)C8;OR*I=RI z9t%lUf$vxxEDknbA6agdG6&|Syuy2o0+Zk>HCQ#>OBEXUA4u(XH*a&FOGGKYGM+63 zJ}Nd(;#DoP$D%Ucy`lXWZ)YV-`;8)5jnaS3Ay(?g!jZx2TZ0XdcJih*y%y-Cq;^~B zLwDr}2E;iN$~}#QCBSN>%v_DQc&3uCB^u@^%*$E5iqEX?gG1ve*@!X{8=KBe)!qD- z&e}$af(i;l>vj`z&;uQmw{c0{#Rdw+%=3*|aygc(wXtstmgg?lB13fnFCmW2{M^EP z-DS1ukqn_`ri15DFtoM9+5jF zdN)1Ji}X@y>_vG5hN|LWE)%6!H0z5G4st@V(Dz1(aK>GBan0&Fy6S8g$#XX@Mm!Vg z7M^bX*!hBLDn9%0z=;JsKs!^!7M%Bv%v>4xo^q`o%(Z)@NV@a)Y;+Sd{rg@^=;e27 zO#e3uTad{Zq1yH?7pLOT4-i3OHWKuHH+AA@(Gwb23w|y$m&BK;*)^zuHg(pw;_R^X zoOv^pHS#KM^E?y!`SE|%UFiurLD&g?Xq+K~9w%uJ70B3D%VQN^j#+Lk1pIo(1S{36 zNz}7cK!VKa$gRoh0da!iP9;{!;g0FLihrO?Eq{#Vfgf)3LYW%+>_oy57ylYoep#Cb z2**(YZI@vbxz!h)#=d}ntwL^&G*6nHieEY?+m{xAPa()M?0UCHXYO0bJv4PM!$C{0 zE&W4bvXcBH6EHaoVx}MDe2PKjPkL1+s@Q|dID76Y z?x|F0#EgBguqIB5J2QRS-x^q+5{x((?cr;uY27>SG3JR8!?n|6ze$sV?l$ zM_n85Dk@XTpIqIeD?Ov6m~noyZG~zDiMh#spZMW=9(0$wOMtC@>{|Ocs*WTmDa{Xn zC|?5xOHITA9lCN z7SW5%^@;58kgk;MZw$hka2>QchtcJi`suoz=jan3?OyD}Hdxrg%ju`WR*`lmWzn;$LZ) ztQfue$Ld!hQxJ#9D%G~yk$Q}$muN>pWqIm$oAboUNSAo7yYQSXsQ@Q)-g!Ac$pqWF zNvD;(W;8w`d2&BQWv`u|5f>EM{zZA}z*gA#petOq1B;Z4J3CB0 zebKmgME>!J={^%~cfEDmC!RqQLe{M~g+l-YnT#SXa-X3;T@HP%%ie@U!Ir0rTI)kN z_tpevZ|L|I{EkXI)#8^zLdt(rKV+MLBVjHZjm!1Zb#eGy!)=C?%ZsZ7)_bxL!WnM` zkmJiXMqjUi{dZ3Aq`Cv=A-( zc?fd+RK7SYBEab<0mPCER&fve#_Kk=5~UIK{rzfg%q>K258YwBu+{!~C`JoBDOdrd z7l$+fzrj&~Lq*2@6ohvMHR|ggbf6V*_oUu< z|K!^qsT})du96M&`4)wUe9ge?;5nNew(%t2!^aVhUs}Mb=m85d^!q{~yO#}neV`)B zHgWo6Yb!fw@Oo2=_|lI~J?Zo>ONC#$oa4oN;Www3KZP9y)+98;eEJT2Fkl9cdelm) z(91t5vc&h*W^20W^pR9klJN)fcW_}E2AzMW%N6-&p18K>T*Bwlgva%~NLR%WUq~q*A#~Lp6m^hN`-$M@*4pB;k{c zCsK3#f}gTw9!avaTo!GdlIft`BOccHt#u=F<2L-Yw@pjJ$r@-g13ETu3B%?c! zI_pqu*8KY;#$1D2m_I!^2tq4>K`AZSwW1YI-0B9iJhUl?OMtXE^@y%B9Xe zfj~N$(F6~&NhXL{FdT;4PXIPP`J*fq=byNx_I`ca(ju#j;yo9P`k)f2b!SpABwwz8aj-ZP4`ts-aZMr1&ytBs+&B=;mqC1kZNW zSZSKkAcnJF0bO!^S%${Dwa0fZ3(!s#uS)C8KTR;!;q);C+WpSkZ(aYSphGz)_NHSH;2Y7d#@FXF#|a7i#R&qi zo3o6y8TL}v2)av&atpH`aK%zgJ#C!rgK~AmfH>?% z<}w1h1}{oMl`@etVbiCXm~U}+{pG854dxHGdjG*nR545=f-ESw!fvlD*~fao0{jJB zV{LTdlsg_V!%m9_nee!kG@I>-pV}t#qVvv}rCC>zAiA0&616hC9ntN82U#Y*6Nc1f zE`Ui0r>V!OV{$10ZJxi$IESNMs`+znz{TfLSFL7>j8ogtQ1mQYqnoVc z)VLY}s9+?*{UU<6>12h0L?is9ePbAK-M#x?yMZfD`sh=(xGLg+#4CFl6j~rld7|G{ zxOzqa1n_!q`r0A|+r^*4;FtA>FZF|lIm2d}Lvs|SX-Gnw%16*%Q5}WJBS%V$qGe+^ zy9V6|O7&j`M^wzyK%Mn}0ZD>@wF*4dj-63uA(UKZ8-?P%(oD7Vliz*jxO+J%LWj&5 zR$!N@kk$}dEXZ!B3J=<9fWx%5a3GtIgLQ55-Hy4DUzoH%FaXwDnXgqiiR-97Hm=sw zv1_d0>&b_0-wEj~YVQ@tFk=T}j<;EbKmH?)t|8ji(W=)Q5g@(jz#x~qdO|^BFDIyU zwv7(L;Ynl#;b>g4U-eYvXdSyMu1;o>65o>C&P#$=>61M)(#LRMmGNE&QtCmub~=|B ztZ`^9LD$VrbMK(;-xXZ0?`YT_hk*hUu3Fqfr6_Xonaxm;*E0o&=)-DneWzKI-C1jm;quaPv z-es|vOS9PeNqaGiU>07U!){N6c0P7OC-1%rsTKuP0lxHbzuqKuD2kce>28&c4lid04G)aufhYJ78l+*-U)lgtz?#zG*5dfSV~CMZ2Y7^ zyHOby{RdOEX#{E@U8sTfI)H%2j}6%aSyJC8?i=La|CRP22usnk^LRy*L{)LB1Q;$oUbvBwMNF*v9?k{;a%Z&a8NgF^({sZ4OKUoP-pNidlu+P=& zKREBV;EgTCDn~!Om4NAcUfJ17{euAUQkArxs7vhL=!3vlfmPZqrzs!RP0D(5H7(eW+s=PI|00{p& z>&2&q$a_Jo!n5N7O39|`2-YLJ5v}V-23Fweb_D-H`GA8P@7NWTT&e(2GwinZ9E>6h#k3rZfFbUi9L;nOK6s4WWec=*FA}1d4!{QthKbit+zp9OfPrPs*I~b47 zClbK1jXdu`j3=PPEzmMAEsglX0ED(`BsX>F=&QTXTC64(O{5F$sB?*v2bET{Sa%FM zy^BLKdQQT8y3pRg2V^8=W%~aE=$l^-hUENX)7&LX(~{X)hM}(5Qrc$m5p0p6;!VOn+qXd+8ye>y z#1(Mb-rc9s0uTj+$D%-Mdg@61dNY#Yq8zn@8fU1i5SQF7B#TBmz^lWX&$P`*nM5LQCLx zQ*iaDpSf^Ff_u-VKLphNy45SqX@B5TBkr#~`sYtlPqG`P-9ukWl)^EZX92_wJC}(~ zm9MA*Hpr+3K$-@o&U_M#agp?bKvQGFF8l0ebn%8n=CPWU+*3cRmWp+n5si$5Nlj+m zJt1~5gRSRbv202sy{=f>#0{4Ij<)HCDR)qqiuV}MZjpt8^zzEJSwDI7kP5dxQ&BRs z8MueX%Ek&f8BzV+)clB*>ZyHln=me7BubePt$_OC9aP{tYepKLG|)AA9;VHs!D8uW z=T>~27*h=`J>FX=j6h{QeBKK7bcf%|gO_6_kAhD9OZmBysn6{*Dpv$=g?*Ax?W879 zX~}y?rv!(ql9N{(7D;8Uy6zCISjo^>&2GF&7-SQJOi1rxV29g@MR4FD3CG%G$RcEI zUjk75dq8RSi%a?iBca9i$&JJ~F&)ag0nv^8b8E}euufTy9xHPmSg%9vfm?W!b?Yo+ zjboh?x)s%a-UB-<5jQ@2AG)<;TiGHyyy(xI0jXCnNLY&zJ>!hyzY>oYCnJ6dD0<2Y z7QX+5?|~+Jw!-o46jOw>(7t}vwN0DzLs(o3DZIdqf5;Awn+Ih~A)_z8Gh5A+7-XEQ z*>aS_mN39)w!@G@BaqiQ@uu(HhX}}P=drMzhjg_b1`R!e*>Y!%9se9J9cAlgchtue z1o06HL*+2ii8Uq3QDC@s zk<-TEOwbP#>!FCczszq+Cl3}bj0E5$xLnCVuTF+(XfvjoY&Ag83wiej6|=u%A{)ps z9b;z;(C(fj7F+Qadfe8M-KQER{tBSxdcg|`+%X|38X0a^u&$C^y5(q-qfZ15P|x|y zqqK|1$z~_rpI@EWj{IYz{M*_`2vd`M32TVx^j&o9P;LXfqV090TZx_G`u;TiW-^M(S&0=yRZr!c z3DlMl?+|L%yUsp7gSC?unmZG|z;~qFUA{o!B~6v%t`2~eM(w&_nZL-s>{*8Z zb6oso+ApJrds(89!Gb#OU$L?t!DmN{l!8t>Kw!-7DJac$hKwq*RpUtAVgBrdWmD&m zmv6CKK$0*jvOtj93rdLxez}QlT!}qhL&hn6+~tDW0=0~Zpb30x4kXk#d&?f|TJoG< zMc3~wysqb;9rNi@TilRdFOB42sL;4EwsVKrfV^C0MRHJH#>L_|M#zU<{)3I{h-?CD zqAQukh9z;5kcA8N^OCp-3*xPMv$A#^b;3pryf8>I=70e%m-n9*_iGB&VbZf}Sj0CM zmKpM##(-MnVM?4!Gy#h*yCgpZmvCcHD>h9S50R8J9-O_fI@+j$ipc7C$A++lm|)dP z4)~ShbL@{4LI|TbwUPPkN3eAe;qpA^k113gCdik8-oHK}@Hz;3bNp)Zg^Xb@${Mw4 z%hgt^MS1Z8V|PrdCH^w#LbWXjBg+r@Dfy zQ^FE^{`q6S@$}O|pM!O)5+&w?qyO2m<`~=1ml~JP4bte)-d05h*vC=Q`M#8m_J={0 zBTL9C-~3C~uOIj@)^~}@vwYEiY?O|`Gd($f;b{G(RlFP=(>m|?b(Y!~z=t zStKjZJe7B5vN$9wc>+TtSt%V1L1<3pTz2}u=&!=~X2*2U( zOuZgpi$vw%2zog3n8&@Ik(oDAuZqi^96Xg@Af-YrqR7f=5@HlXRo+sInBYj>xji-P zh3WVi;2DsFlRt^?tO@gq>w|Y0oMmRApe^gucUPty3{}z>>~dOODr0K%I?Lg^i;*B_ z&_$+E<0;4K#r5|fk1orGXI@V?;<8}7S)ZHwuR=-1p-RDrqdsDfyMijdbeK`ac85Ih z`0^~HR=vHWpnb0D-(KSv5C$4EiD@I;GpmKzk^Fk+tJl6*(Lvf83xP^Z61itXlywj8 zTg}5vtLG^MHZ={vVAQgpMTf%DQl5L-bq17jF-Kn^Fj=Nnf*iVwg&p1>8M$i7B=haS zOwKQWX$BTdqZ*qFzVHqeed&9xM63_V-iSB=R;I33vF8*stLH`zM;1LaAUzb zay-^0Jfg7>=8mGPl^brCIT=Ei)$6G|H0;oknUF?9lBUI4Q)0YBzSb|a2v`o~(i*}e zL6~>U6Y%0`6$V9`mBF22W}uRRepbZA*Dm8oWSKixG90DA(sjEn z`59ED#8^%24tuqPd|85^ZudJih%P^q^eZ5t2bkxH=(`cr#ol(hv(onK^FT{cx|27P zb-+ik54bH%&`mO`yb&swH91M=*d#dQJq8wj?+F{~>d!Y#ovG`$k?WtG3b24~5;|2S9%Xa4uTS z+an;O&DPjam3&|1THxD_=qcy2flwxo%N<{EV?lLj*mTflMA_n3)5v335oMqc%5KwR zhjz?KW|H@_90-Sw=;GDEVg3x1j1VOCuRMUdy>91r?(Y#x)qnmRZ83S#3WIHQ+O-J|?fd zCVGT~Vg$6xBtd4;?s09Isj%@R&Md_<|7uw~o0)V<0kVEn%-o=fQ2jO@ zFGeOwgC(q*v7ggi8b3ydde7M6-#*=z7U9p1qfxE%K%}7!MU1~O-WP}~L)A^d8qA>5 zlzJ$-zIOA|ak2Uh=DC$`4@9JrC3j1GN!4VBNV-75bXx*_(Lb$tK*(UREy4cFF|&L0 zJ@10|S`qZZ=c~X}Y2wqzx2T6mXQzng5xpylY#@xgNC0~4yTSnq1Zj75V9@s`FCGF( zF?I!$<4zwb=zbM7XO&1Tx0-buBY&t33Dva`cXTFwMsC`6q2t%V)qSoDzvW-!gcIRg zCqU5ZqmAJcGZBFu#7GxtM?C;3J?)?8%k3J-8GNr2lIMUnUl}|=@M2jMgNS~Vs?j7N zpccxsh?ho!9*>rFcaWDX)7@0-0bctm>PsDO2RI&Ejl4J?#5cpisi=%$t z_svun4`2pP=k^|8u7fzGZ!Pr*u$|9`)d`RN>-gVkMJ`=5YKTezqyZx`Ajl7ruF|F0|MA1C4f9e2{^&>bt{5 zL?Q3-UsU1lkkECESP+^>Haj)dsW+~YVfjNI09;01a}}|YrZ=n58(-CBvMK-W$EV-- zJZte}_}U_d-`-L>%)wco7LLswvGPjG^dH$iLB9rSES4rdrL*{b4TZYWLno7f8XD& z7|US!-R2^+-ZO(-sG@e0r7}Xy#4v#`<%uyck2@PYE;g+5v>f#jf<^<(2gQV)?A^h~ zo0jUwZLintQp2;|%vjJ2^R2H;XntC?cFegcTtJE2X|W~h9XiSjv~??xl59qS7?Ofc z*tr*JH5LODo0Tgh4XF?wyti58Y!aGtZ|}c}N|3hvOra4PPc`g)`78Z0pp4CjjTg;S zaOH^Mp)aC35_5R+VCq(j0Chl$zay%73^$?YJSS`)?^z2@7cbyw;z->CJG(?zvO@YK-^!%|I@)8Sk#~X)K#u%CmalYZ z+=w%l{|8V%WjY5{&)@oj!X`Nm0}WD+fAh%NcE!;Qdz$!c%aaL3mE|v%pTd*qHuQgE zJxiols*uBcQ+xS^_i`Vq*uHg_d5~kir3DVFj=>)CgF>4nT(7V2y{e`{bC7?pkQb-y zi~HYC_Mq1QuKM{TZ=3=gEI~jh#{41y(1J#n2(;#Irbp0`i#TYm%+wNe(C}Wcco-uk zU*&ARD9jL*1;fJ<5$d{Q;9*e>f9dq(QcBP*Nte_RbR+XD@rF1 zt;6lkwG`n^?J{}ww7JQ$wz0yt%+_QcB0U>;l|(U^P!2&yJFn_~9TWnW8*u^|`O6$P zJ`^12eukI)9zg?;x8y69>F!O9q{xb%hUu*Z#k9S)&4QDbHJf7=UR&wRh2?e*`a9tM zRV;cJ3T_sJJ={fdoS$sa{?i1gCA&UWoEV>!=L07HJ=U-MiFy_`&;6XC9P5Vk#G^dHvG=jafeBYj{XEPWyAa6#J3TW#w(T04R4ht0N1+weLiR zIihGU33sElk?Ikt8ptOEPMoz_52dojH?j!9Bj30=2*)ZLS5 z=ic*<{I1?z!623>&AeAWg3X|Y*{yG* z^h(ut0n+JRQNzx{8onh(c$?pB`&lRitxG*_d`|cu>=_XTD+oWRJ7ds4v1@QE1V9VK zS7I!R_}+ULh9Iv~@UO5@TJcKh@Co;9kGC6otu$|6bBt^JvitQEZ<;8RLE+0VsNd%kN#|pO#AgBU^z!m=C4yVj>?`Zfb)n$W~GffH}e)ynQX&=E$%$vJw9P zb72$vJrL4YtnKfIYLt}$QD(C!#sum-d>D%CZp|~`XMWjc7T5^o>FZ&-7L{6{54^*| zi6>*26rbhC$U>X3^7ZpMlnHSbY$gHXA2BFqD& z1`d3rA@nctY~c1U2cd2?pVXf;ZA|8LAe-yZP_Uf0e*e+x96h7tBx^|0*cpSw0$Y;5 z{5Bo4g2M=D4EOeO%dZDJR(ge#MWVv5nb43t(3eV-&JC#;GrHT#59|*hC?;w=S~9GIypFf-yUJt?IAj8ab?71>uA{Qdl;->j?Cf3=E56_~c>*?|h!d%5oAV^zD5rQq7P^pysta` z$Q-DZ#J0Ld4xBPrLM84jFA5?jpsv>xGyufNze|LYs&kdyKR@k4#@6WQp?~gyadvy2 zFG8dXFFUZQ%^}N=PHuUf=l#jITxFM<=<*sRk!znJAlnHOx@5fq{y zR4?vyP3sCswl7ysSu1Id)mbgCCe6iIE_mXD*hITuPGi!>gIX0=34_W=8_ut@@8|V* z0At)*or>5eo&{MD<`pnm;}7Vrrh*iUB1v*Tex;LJQ_jF3XIo>9S#m|?Am{?b#lT<-e0wfk{A9mJu&~exQ}1H4uNLO>=kK3tkF)U zs~Yb$*zr&D}Unm_$A}N&m!}&rfmEohl%^&U-~E0x@dy$83kEHh<)FQH84*%__O(s zMr97FVP2rGTD4x6i3a4VbD_mad}lX9Np`B=n%M1V7uVXQ#(Stzr{hSIYhlBGjN(%g z<|q15Iv^rP+;OGkYX87eWJq8>4i$~9+IHrGayt)NMwq5hJ&kYAD6jc1G8jLcrtey9 z)&Sg{nC|VL6JW+YUo13$!GzTurB&@tF39b_cqZ`B1*!AD1NbP&7(EQNTX~o7^mlrj ze*On@a3cptor8dATSg{oWr-;6EFGFqTeeB&`Dz==G_;i1CUIS62pq6%_J{@26tWW<-zS={Tye=vXskv=-aMXOa zV*P`fU0lNctUvM1^)I$xlb&||i}zpgzcxNFZujvY=Xss?G5S8gf6D*M;?M0Ldw!!{ zLHVlu9)(kg{sjC7{ohY7&i}>fr`d1$-*mrVKdt_w_@aJC{{OI#^zZbaQQuZx5`TsD z8R@m?s%>70N>*Rjjg7sP^k-P}`<9Y}(^$Q_Um>Hf#oe#-)1@;74t6m%3ntIA{j%eu01 zE76f=Hg(GoKj76ql2|#Sze8kG67ff*w>G_zQAdlL8Hn-8VmK=bahOCi8Pj$=58_-! z$5*tBQOBYtml|#oJpMrwy0$RqmUJ7_tlvOe8_u(^=7Bq$?gw&wZ&~8;>OFYE4$(x+ zb-B;3U*vcMWcUYCx2F&0+B@Mx;HCQiaRi%s`wFqBE5G6i9a1Q{8vHVGLP`oBiha5S z3@GbjAbS|FlbkjG|7!4gpphCvb#V?e^9Z~F>_r|4RB3rLr_>MJLVdwYJOCuA z<^Og%a%?MJo%fw_jj>a-|4rQ2)cN^vzwi36UGl#1^1)C8LhKOL;?)FK5x=B6rYdHLyAsm@^7mXS-`f~8a7^1g(J!~g!M5mH`Ud=^b ztSM`c&DasIU8hIJi!rFi4DVzgP3<7Cmm>14fEaewsob)p%>h^pw-~Pwyp%}ggs&N8G7VnWikub zdn567-o6rTy3jwG2=GT!5s$AbV7wkm2%3D}Y|c(er6-cgQ zb$Z(7Y+hTJ=G`o?v_qhr-rkuC-}eHvd=SnCE)8@zS_Ud>JY`bV5dsNw@bJ(nhWO)C zblj@k>yr93E1ruimH6p$r-N+6t&jW-_nFiFqhIQ75zE7R`kCBS&WnSdIhl|BXYQ2Vdag`5Sj1Z`auQ%AT|R6%d4@ugE4p93%8m=GK_gaXT#D){MEQ1UE_x-v00WM7gIEi--Esv<$77LzL^sWqP7?GG35LIR2 zNZ(u@qIysI733x;dW|OT)l2Woru|gZDZoox^V{8Re&ClIviZw}F!1t|&DR;n58bUA zq)B5C>@JMR623}2$$$jXMfF+^TH{Q+5C2YWdP^yGywNQDMBes?A9cb|b4dy^NTQ8T zGA~L(;BVWAmJ@69V?{+d8s1f|wy@X}n)treqf9`WI}lWc=ss7g6Oo6EMwq??i)Z`t zAI5)qThE;NwOLWBv*QJXTBze6DCoy3XRXBdU7)^&v5pfaoJ;D$hV1)uSirCrRV`|} zP}{QQM2LsNYF~ri+wR!~doJqzMIDFI7YXaqTQRw9bs%fz!w926{^O2J>RjVMk;kYM z3Ef#{wpokdJ1>R@5%10$b7eJg=2+>Ty|3)guJA1+v?+|mTW!I^ZyP;nvv?IvroxF2E6oghhD7r@4moW&Hymtt+iz zL;p*I4HJd(r+BIUIOxalG7{n;{uPdu#0O5ZHI&irTB4;q=)=ewQWr}qMEU`9QQlp* zIurp)4Rix!tkp^&_02<_H1{`%dn9wJ9_c{HH}U>XGSXWvy`hW%Z%NUiaAA`hdcUN9 zYA}SYP9+}*53A|ozCjpWh31c@eTz@7 zK&Fjl#@Le3Tn42g$4CTot{+1flHBe!G6x+lEd&p17a%^P7$mW8`Wt#g8$aJOcsJXC z;zo?b`uh_8p}XWM`-f@pM;!m~yt5;k!Zx`@5e+a874#$B7oS?X()3h-gSHP)=)gUBx-%~eqMjz> zH9|?zML}|GZ$sN?jxHYw? zytgzg@Hgy^{7smR=dr>wd0)go+{rpFA)ko=0RH~NTw@iK(`~HZm~H5=WC->WbJZy? zS#IhiCj1vg9Z-dGwc)i2-iUXZHUqVFPjWKc_S4*u26EhA4-yhb$ zGt{q4gRYCetw}%X2Y5evXU`p`c|W$vpuw=1I101 zqdD7H5mwSwf1SZL@GMAL6HYT@gwdjI+?&cqsDk`EM9_Nr8gp*{fpKGoH0q7l2>b>X zR!WEsFl%v@yb>#MwD>E)NGId9hmzajjRffARB#7aGwGbzaKT5 zkW@;YMw!YV!?B6kGH2&`PUpkCOFc*4^GToV^2eFW^Y1sFNSt9VAs1gut6{;}M+ zoI1-n6-kG%XSwMyF-iZ!n^y&S8DWluKn^s0mET>Z`AJMfV42?H>xTdWtrEb*JTwh( z6}!>KEOUox%p(>?rb^lwr|?{A-gyMhkdI%5;Gc9UIGuSGwRI{f=_e*Aj>CUBDl>m49x*0Sb-y8J5F+iH_$m&Ld2GTmU*G+# z$#GIAEf4;_6Jr~p_Pn9%5!yZJ6av+|tgHl_SMx3(^5^HD?>}5803est%Dt})zj;BQ z;Pu@nZu(C`r2Yv$ZyyT_YkJ`Cs2M@5_yDxZn3_8RCjvje?lLjv3q5-0{riiUS!c#w zn?<#Od#b_$g+fr&ZaZHz0zg^$b-?6&Iph^NawIh4kgbcrEN}G88Vw(W;&lY`c2w}B zFV!ACN$YrZ{)r}+Qrt21Yi8((4?&IRviWEAXFw8>PH_I{G}Er7ws5X83l5$+T|{H* z!GFw zqb-zu{W(s~%X^}m%%P0=PRk}t`WYs>QvL|uG$v*U zDc@UEMhxC1j)*|I1X(-~?$hn6xtG&L{?O?6H3MR24EE`H-~6M0Q`26x>l0Ph;U4#g zcpu;z)*eofu*mm~8$dkn``UTU9?lwBs+% zL|yf?6iR%b~_`Iam7ul{Zl$X9!s(I5`5_oS~zT7b{v>2lf)TI^;+ z;Zw+L(c2R(F^BbN1*9v=&pSKeKH+1edk9~o*-g@MH=Z80%K@_0q&KH+WI?YKn1u0) zPO3M5Muydw1A6GRv^L`Q#gj&j3yM6@add#MV_@_{v?qQ3D_f!&;^Njp8r0(YiUC6Z z+Me@>`br9w`V&vv+7kE7(wcw2tfyYFprYI!N#KM%SXINVF!BQ_+>b*haN=PZEA7L% z=67E%r&KS}`OkKy2BRm*kJ2!Q-a633u1$i4ymrlV%6T$E1*f>M(qv4jg}S*LR~=2S z#{**5oX1DMf!}tIug5d^{^OaLqXh@58!WwT*Mv*@;aCd?KMmy)=upqp+0;#<~dV zYs!8z(MBRFAOctl-$`)fP08~BZooWej9=kwcZR3sS;6NRK*8%DDkk(w8#%neCKSh< zJicn2ML;L8$ltI@>)~nxelX%38*B6VPvo2GM%YT?jZw17jIt+KRFtrGLT{jmvFLi} z!jlqSMj_RO(A%N2>rdZ9uY_nE`Z3FB94PDB+O&Nj*1zTV^P>VH*W83o|8vl%cdv+9 zuX`p_Syw^3KGrvLq1I}V7ZGmb8Ai)!Lhkv5@B2HARwEd7=ek`pV^=J-!MoAxln`08 z{S}d&3sp+9pfTV#{1aO{P;B!1E|H>UIg{Z7pfp-O7v$xgjvHTdsA6g zL0+!pC;KEo)RcC~QOKxYBNlpK3~U-_kFY3Ey?ZXbPC&oF`pvk#)qzS^LQ__x>PW|H z{@L449>wM<&Ke_AktKL&D&S(WklL$%A^u#re3(^FCa+GXU03#J>uro3U61+IGjmEX}!c-L; zH0E@dDVJzngbY%g|0*>lYAYsXk3O~2CqpB?u(6NV?Nn-zGe|SlVwz8 zR?KUSgb6owxVBrRZX%=9d_iCF%K#i{(!n0H;4O{E7%N|;Z|~GzY}9Su&(Aq_%sGJj zsGel$qO9IF*Lh<-wS6wD@$K^8&3w>4+suo?FlMD5k!Sm8Ob~?MxEsQ+2Eb{?R_Vk$ z{%|L!>aei`G=GTl+x37IR_p7B935pucE+pF+MTT*?#>H4>ECDd!#&#-mecb)G-uFL z8=Y^=kp8g`2nI<^uXKvBcU+5DXw(n)**-%jwzpX!e|v4(0?YUh&PnptC%PBYUrzL` zANL{5PoIIxS9C3iLB~c8mIB#FN2J>Qmyu4MHPtF8s6#;rfishc(uiGMv-}`GM!QdR z7ujbnA)7gd5z^v!>y3F0{&MQy}40{1G=q0QwHF@QxybZLzq5*LRTY09X4Pp{8WR z^e)?B_uc$Rh*9bbd_m}t&Qrf40*7jchno3uVavF*cS9CZackK4mBAt|Z4W<>C>4_T zpWgmf1681*$@{yj_VH=~E{1ZYE$jWsK3MG*cvp#~id2-s7Hn;{{y{v7wAb6*5l%I5 zmXQ?&$V_VBUoP(~92vqVh~*l_@&Q{+IGgmn@bUMMB(f2b!&r0B7Wf5tPEyk-U2>?ja9E7aBq9omf1@UR8 zMRvFn1eE51W&Dy|ajCo`V%LX;xI1o6*`Ph0`0{S6u<5ozt%yG_Qc9#Rw|shTM1-n` zUD8E0glLx?1X>dfC(_mN{im*VeN7)oeYmQ`>463`ffhQ_4p~i6y{3sw+Jt7l7B18V zQHV5XC*YYykLbcAm8vN1A9&#Aiq|a&;eK3eW8`b8fns32kTkQ1P5-`e=Ite_*2=g) zwS!CJ^C2-*yY+GK8*0cOw~JYE91Z*`FN_KhD{ix6O>-@H>M0_c=^+&8*%YeL+N;D3Z^d~LenRKz?G7hI#o^?>N zXQp^!P`$yVyMSUYpUFPw`_Ab&Zgj)uCXSm_M#y}}r@5ZbsIz#rTEx*$^3H#!P{oXW^&{Ga)ERSF0C`!k*QI5(Own} zq0se}t{@}-C)H9aqg{6_;1+$9KZ!m$72!KWBRoCKTVfKAnrN_$95yKQ1V#VozgKNq z-x(3aq^9BbySu)v_Ouhe31x&ZWNJD{{d5T06mJTC;VVS3su{V}JOV`L19TJ8+vKWC zWuS*9!y~t~Sl@e~SdL3q9@{=_q~Dn%FK&KCNF6LALWcO8WO#F?g9Ncx;apjd)-;HE zx~Gs@X+)2T?$gr(gNKlorf6)I#)X7M?g(_cpx$;L7EXkZ-Lh{dHZ&y6D!Y%#l#3W& zu~-Dsmd5P@QhYfW0%L!AeG>|l^r8=ICvX8p4B%PC^;vwj=Gim6A~p6Ou(j8ZOf6(HGEIt^WAz3Ntql0Okg zKueH0L^Lu*lK^eaqF6f_P3?VAgU|Sf{286njAU24>n1HMZ_E8TK{U?p_017}jl|XE zHY%m1WkB44rDgv2KmMUlPq48j6f}66IjiB#jkvmnnmSSN>Z-tE=ICDHkk9Rq_W@34 z9-=0FduP$HC7hE%@Su^^7svgTx6miHArlHyBEFhYZ7kw-^p=h)9k7p#T#Bh|Zo z!{Wk~?4q!8w~9zsY-2KjR=%Ca^3T?yM)nT10qnz(ka$QWSXW_heCGovsH-#y7Qh26 zH^b z6!fK_#S27VNQ80wNiDuz*o)}>nYgi3racx{g_Ok}v=L=jlkP&V3A!&7co?ep$Lf>^ z^%KoO2#fHG?JiZ7k}wDs%ne`0A6R03iLilV+WT*k>fX&yKOYYYg3W-ehO|e)z2fXh zc!IF{N3B`l_2lrZ)9gz~SMthiyjHe*#aL-H5i`%Ux{kU*0hN z>Z5b9kE30o-8Fe4gZoq(m*(U&H6z4C+dicCyyt)Kq^R!kc5xzKB8kx;eCPgDUD8qu zJQQO&B+edx(=f`iV2~XBzHn4 zUF}`M@TefwfP)gTT*1k^CH1(^V6>W20Y2?;B`#4?Dou#Mry)4G=M0cQAo~H-dB{&K zi4jd3Vhr!r-HfzKJ>mhsPgJTmKQa#kt~R8{MQWHk2FKXnuW5O@+IcM;&QGZcjZXi@ z(Q?v&I||41r@_jH%{~_{AVriuE(zNISBH^>Oh%(e<3qZsI6fIK@ZNxDaHQNe;7uT- z>=cDQ1*d|&eKu-OrkNBaSm&SI&jD1K5LpejnXXei!d{pT^|un?$$Jcwx?0EU?b32c zzM20`BWpYE3ALk^*1X#TA~ksd5&#Pv%i?fCiuHv75$dgx=yI#V; z-(*z~ffcp>=N+lZLn(!)(O=%8Mx>557mZU*ni0&5?wllgfj^2IB}+tD?z%)}Y)Oj* zcxX*d9yWsw4?ORCC&>@ff~}idooJ-y;eYay(sZ{HQ|k^vsJl{N8BT$o?4Uc|WqPtMKfg6$LOXJ2wK=e4e&$i_C)3umE zB&cNrAipOtO$rxV_j{TM+Q&SxX}Q*1@8SH`5j7aFVng3BRr%Gq{f9||U9D;U>lrP4 zCoGyzQ2~g)yr;jBF?q2gp!YXc_bZYTF`J5IrHD#-H4SpB(G112V7rJjORi-$AS>`}xrO63e`R6ZShZO%@AGb^$fZfPKh$%4v)lThK z9%-`7Ym9%C@_YiPdmXuJ!5dyP+SRIC{IF1J%l1$Aa%v~jd2V!n$n6);+Fk;BWeV(F zlg?dM&Lz~JQe(QU0Hc2qa6DcGPlj}KOs_6*#9#$+0`3;Qu(NzqkPDlb7vTX%*~#hYjRZTJ~5=^L`bbiY&CfQOzuu z#2F>(fDHE*0%4ik39eBhFbKfP*VSTm;(sT)+0#xKa6`Y0xJ(*Wl}ILxg9zNTt42Jk z3i(Tc%1Yw8!Oau{0$(SjS3JO1K#T=-4EFoo*29A)-JEsTk^0{4H4@?oPN?-cYh2vW zdpN!ZTa;=`zTEtbu$41h~^;&e+c!Xn4!{?n(1j8# zDn0amiPCLyZEjZv;DY}sF{=_;@SHBjv!aJ1dwB{>uov!O1LQ{dLSh6Gh=7<7o?vA! zLl*w0*YL=E%;fv4;C->@migspNuw#`I{ zqf%q{9b*tutKlar#p*sBgCN6=Ar}d4{rgqO?TsXGV38CA2EEu3AINRitf?ZNM25Qg zM;`k4FSW7$b1QOa=ss$`;)YKn5~8N;N#Q()!~LBD!GI}ChSX_t|8{)$GB|_`^pCCgGnRcc! zNrGEf!=XOOOTMzgBq?u3TBj{cYFcS*?GC)j;tQK!WY!H8r1+dqc7>E-T>)vf$VbW6 zn2qw$Y;)GPsk}o*F{lQJR^1uq2%1ho*{!)V-wI zoosKlNryw;uAt#VE%a!fvydC?^PUs<%6?{ZdXT$7h;j=8E#+q?q{jE_ z4!tp*U79bCt30((#Kj$^0_)i$c&3;(86DSdjjYQn`|5D{x~6aPn~ZA(V=(|?qdO`O z&MkJ!C^t+J{+&Q1*tuH%vGA~|cN%>`tcWP?y=?$$FuXKtw9zxh8ypuPG3w0{CuE{Ev zf1W9vW;|f%u)iDbVUOZqdH0P!4zrh@NHTwDmz3VC|E%jc=Ez@N@+uaRx*QeHo1&jo zsAm1cBKIwkJ7)d+p2O@?ncq^FEy>*9*GRE8J{jxn&&Ty9(=6j2(DhJc-~~SMU*Tsy zxdbYEVjrN;z-mB_?wUf%8Uk!G-UuYE9riz!qG{BzY`mwEx@zNvv|j3n*2aU;ni?WuYtqJvNv3}sF8j=HqL402uK-w_h6`n@O_z8G{Z%4?eNJybPbE(HdzH-2<2y< zw!lDFS)z$STwcLOoUPdT-r+f{m@uk|rTmzJ`$iN;&2dSqgLut>2j}mFe3FmKo?Kte zK+7|`i9AeuUSTL9LVFJ6M%3Z?314dRv``005 z_$_!J=d2bE5Vkcs1ypns5^qe7(Z}-=!`AeBf2;{wJXw!-QSuKbW9U%EP4EGOq?(p@ zp8$HPS)GrfRoJ{bo{V%ZLXman0fmOuqCt*{4@d|UoHP<(9G zNM21#Tog{ek~O|{E3v=C2e6P&mPDMw*)r%9pW3Giw7-X@qg<6mdNaWgC0Y>=pY} z+++oy_5=oMzh`wp|9PwuwW|!P7Uh1nqFszL9OjlZ?fb{2tjll7?7+xBT)R_QgiwCi z6S!l<14Stv^(0|ienWaa@{~?U*sWFYeMXRtaWf&NM|AE1v)PR@oK@rYbOzs>Cwx;h z5{!$DX!FQ3169QrO-kB`aTyWjq08u}#k`#h{1!lg5JF*(fzwn&b>(n`Gq+BuF$5elq`25yNL~U+6Z9GGq)i*u=5TCibw`YjoR-zh zd}Eh(V|G!YF%+GFkLWXXSH54~+N-HXK|L$ezSqXcaQ#G=g@5my4+t921Nq{4G-iI+ z>g6ZPk2P>+zibF?f}w(kfqQkR^hU)z4axcjR2_|F#OH8LTYvd}#nb1HfMELg6h1sx-W zAv6aab2&gqdDk_L_Mn<9-{yFEC2%qHQaL)j{er)!0;TeOg8K-dD6Gs*5$!rLe{r#r ze0R!iFY_(n4i7GXK}c?_@pnORbHe+_Uzu{1hn7m60-tb2R|QyoTi2$UUF3Op$&4w` zUiShoXS5=jWjG;bwS9HRTx1v0Cku?HBPQ7YP(tZ9SUMP*ADH6fIk+MklUC9pbSrL3 z^IQ#7gL|1W(Hm>1uGMiR#@rj@UdV&DtlF+ET*Y+3S;JvrN&$@L$0TvVxalt8T*-mD zK*M{s>9I)E7T8(#QQa(OEznAi+!py$BeSWZ=;H1;>cfE4x<|1U7hlM9TyHvE zVxnbalgflNdA?Gy4|eOndH-G$8c8I4RYSftMFr1jSoPSY0V}5;>d%fXhX`HW zRn%6y^8197(JiNfzbtD(G=F+Zb%$u&d`H5$<9T@j;JYlP<;+(Sje+QZ9RmPKwDz9E zA${N6)mMY)=(qZnkIPm{g4wnaEEo@M_M_O5dQjT>zAlO#&mz(Ia6* zwQ_K+RAJ*aca0DC8_2KGiScjXKtxk}Oj`^l6D}2UJhZx>mv`xua1%)ncpqM0evN?` zs}1Ir%7+6_4QEe9?P~eC++$^9GOwRK%2d3N{>t4e`<}yAdr%ycJf^E7738x|1#&Qc zveD^+SnjHxMLo1g`-PB>^8!l&VRtmr*&~*fY+2PJ0y=bNAuRDMgy^g=Motaa={W zLU|kb|&y6*?vEze4Z-A-mS$xdpx5VYf(%WkE8Y<-*tSQ2#EUa*9oibn;|k z6DN++(L)Z~Gy&WmCFtXl6A=_>-?Y4UEnGco>6QPkwoVJL>Lc*S|9wSAHiBipjVR9V zYHe7e7Ag}c_=YmfeQ|bQp26Hhfo;j!Br6y+@tMnrb>T5ZDG3FKt%%!v;~{_uj;Es8 z#ff)uGpT_5C<$PP9ISLLKpwz1YTKT!XTJ5SW&Lvtlj$gqjQIJuUim3b6~@&)`@@2K z5b4L2!O_y-C{XwHN&ft6?m=mOxutak=EGFwkxfci-lAAs7^*kTd7|S6P`2HQ!*?nn zvS$w>gfbmA4iLL1+L?%vg#v!;i=7o};phPVx{zYIk$7u9y^H&-vwx=XNy(h#=VC)UH!wUG5v=2>kAxxIOPoOdUc_5w8DTk|xEyJ*n;8>0>IKh82f^nH+ zYN{GOXo3tI)$VVx6CaWYK-&uCt>`b@(O})@(#fO1!y*R4eSaDDUEf%>sLw{0=K{#e zVrHK1Tbjq(@7QsEHsS&c3@F9A#fnv9k1#FG_$o7#^ka(n*E5_ZXRKxh^x_=6)sAxt z)7sDN7q@YDsyN9%l%V=JS*!QR*>_RP}9H4)0jP4uV5v;Z{^eE1(W`ezu z@K|2e(b>QJ6{LR3bn|GXb}`>`ZA@k;ck=fBJ-iqDQIF6155r`(*_nOd#W{&z{015y zPcLP1)!*S&lRcw5z!YlJ2U6d_q7rUQq^*^y{5_Jy?RXeN-dug47!m`nq-BbRuXSM+ zz#64I_m|f+pS8uFs%B8Q;r_7~llC&%z2ju1-#kk)Z)I#nI`hw9D~F-}!Bp;V{4Qz4 z!zfMTjCFt5G>-2Cf-M;kQZ0$TfER9Z3Aay$%0(Dfr~IfXXuL`5bR{I>bcLD?|n|HF(s6 z!#M9}M=1Q}cQFY&C%M#s+3j4BJydtutMZAhP_tkW`lJ3s1ftHLfh;l30OM!t87BI1 z&P^?nCit2K)`qkWk}`1>?eCb@qtUUL{{bhL>^#e%%Lm7#!+Ib7k-;d+qou z(7da*3DaP+<;Sq5Cb%OcpE=qk&x=q^tkc($SsPZ`ay;jWE z+9LXIbIWJ^;w4{ zKUZc;-%us??0pE0%yysC+pHVKh<>Uc_Siu-T^K$0lkPdpK4(4}I4O-6rt; zwCY$s4N&&i+x{O|A?#|ALzRoqy)BOWi2Ur?E+jJh8!@5qd;wJdDc3Lv)!`fm2y-re zmixILaJQ@uY&Juc&wu9b%K>b^2BA~uHb#qJiTlLQ+=t(w$i9HKn0H1!fgm$PH&sa* zO!x_Oj|<+@b_4>a-?oah;=L0ETd*`genSg!s^ZUk$7;+BLMSMtr(LQzc5OZvbc*Tk zS%0&zKHpOkIcuX=-^Orn&}HoNI-fj0clUIRUH2zEu)ha#!aJBAMB|ahlaZv4J0&P1 z^2SZ8q_(I#7T$K|yq){Q8qJlQZKl_#H(Y-yTzgj1A%rnvM1Lz15kW1S5S8G@|ZfqfRHUR%D-X-H0xgmR2v{(Zwpd8Qqp*F(DmFwZdi3 zR6s{#P&|}j+`A~hO!5dmLa=!<*r4HRiBZa#_ss^d_&Zor>i0a&!y527t<5@sNUy~o zylp8BTo(m%P$;Z%c}Z$7o}#@wuN}o-UEu~pz-DhIZn{!B&2fU+-ARFTU;)M&0m9Y0 z=E+$C>|=X#EUXxT-bMP&#&1vEmiq(qS~K#I<=8?|Zqg#M;?{Gs21Y>^p^WyFqU+3* zsmPdXBjF~w!LUQXaO1s(bV;Xk)7w$BTAb>_EqK zYR=(L?*PD@_X@vB_y+Y1uK9 zof#YEJkLX=cujk1R6c8dY>7d7jua88dg%cPqv*!yE7Bc!K`h?rtHzM@W0FDEi&X-D z!9bx{RKA25G{x^mr;t78l=PIcsf~vJrxDw^;|t8{5t*q?H{>55Bxpmk} z`4t^i_C8zX_l@)n_|)P0RLF3TP$D*o+(Z#kvbEKB2gN)7$Ticu`l+`#l(0BdQT%kI zMs7=}l*(z`*D(u{`m0)gry-|bWEDmu=vlG_pXMktPLE@$N+M(roVO6F!a8RdAcWk(HjJYv;j+Cp*>2Oo+3~wC!r77Y0Z}%IW@=@wwHa> zS3h)6mlhWe%REcOh9UJU7-;oomb3dSDb1X3D)?yH7m6?cqDBG zVBA`mMK6RR=arlCoBamvajwX2J ziNgj?JLyw?wXa|jz!WDhK=29y;8-^(^-k6b0)!8STlrzkSql9>U#yTjZ*V?tgJ5ZbTIf|4SHmMl|9g@Z*yR0_ zGF!_GlwHaPY=Nd@B{2eFj+&ob7Tz_Y6F$n8C5ktD!7_}E?UX~w{nx)j!enVJam|Ib z@L>atf5H9U$Ng2k|JK5#@W1Da<_Tq}W`?@}=i@Z-WeL$)oZzX~JcwmuF9EJFx4(7n zap2KEX)+_YFIH_nOCKyEw$IuQ5AHQhS3&_lk`!>uN5Ef}v*ZWFAyeHa zwVF%S-NFqKLd*UBJ!*n6EsZ}nioT)CPr-HsGzE38YP1G)x>}VD-mlrO1gYd$p&;KS zbsD8gCe6X89iM9dY{!Xw`Ccef)n|ga;1$)LD%r6RA8#2n_R980l#GsH`3v`Qr){i1 zr-S+FYb)VzZW(E3_*c(ip1`aZzFGE!l&_0+9|Tht8jRrMgI4F|EHDWG zjwi<+$ht=YjAXLv=YZ)Y#f!jteqs@fo0iCNfu+~6b*2?EIeJfdJU|mW)j)MLAn6aC zj{?9q4pi&}pZJ_DLc!Xy~4Mg6<^Ge;|YSfuw=KCnF^oWwsK+F0- zi_stURaA3u`4ZIA`-NU4Nvr&y+`FEFry5v|60z=ZPZl#>wjt8`X6P50*#!s7k8EU3 zy#keC3cSv$qiL*}FBdyL^=}dN;Li=&{|)N<_E7I31&lDC)w)DblKqtYqa6e!Cd*(* zRJ>Mz&_saSSHAhkAn~Tr`HL@Xe+jxPZ`ybUh&6XiKomSC@bKqJ)$bOfRpyCRF%sL( z2&9nBUca%_t^n?g7LVGo;r`{VeOj32>9&d2W}}PT$f=P<0MnwhO0tr#^90d-Po;FY zNwN@l*K$T@NS)o*?;&aBxmt9o;o5T%ivGQudC$2H{3u&BN0&J3y_p{ISy}i|+t+4H z?d!;W#FurCV1#IJBh2fRxXgE! zzZgplf;^^{(c>}vIEf$CDrZ>`v~Bx49h?2=6n^>yX2Dt=FevA;kD*6@iX}u*gfv&h z!iH_#u7Tcgb{bCg!E;W;;jj-y%rfuq#Qqk&P2JM$z@rY(k(5KdvR+A$_UMGWpxkYT zvQF26M$aUS6+Hb|GMm^lSkT7ds+-N zZd<-DfnLd|bb7-oecng4%0#F!`h16KEv}8*#0xP4=6|?$Ul5#?T zz1xAS>?GbAv zFN?0EsE5mU#mvD)87Tw6B5mzyYT>dASS%?B)Z?UsIk8zM$lDj1-{u};pVNol0qY?; zq}hf&P^;p-Zz#=*BR5F#r}$wVA0j&*N9_kzGFveFXu+s>gs|1*j|8zQJ(J8#)tW6~ z`NoUO6|Az+3y3^5#bwB$`UYn*zLDI@^f^+rDX@y%0f_ zS%#?;V#{OUz?&-86o_C`B$3I4QW2%s#uek-Ch6fR0zEP5!dVTIN|Aw=3&6DWxG*1p zJc5Hii*Yf@{(-Oq$X>$zU1{SV-N0qG653oVFTojbzTp*2GyjiW(P2up`%maPLoE3z zEaaJsT{+#S>gf2fKYiODl;MZQ*XigVj~Y6D8J1eu$jTI9KuaLhp77|jlLR$OEAN6Y zMMSjUvU+UU&|U+Uzw@n4D!;EQSJdHYcqs!o$2eN>W;;s0T{JuP1_(v4D{qg>K=yb2 zQiVtXobK(`6=E7~GiZbl(ifdPmG>9D1}(`lVLZiFYJ+<%SWD|03J`RfqI`*^eI)#4 zRdhnrJr}zOBG{@eO{U#>1(BDGVEa3ePj(PXKsUhcAj@%q&w%a1df>>s9muU1a+^t? z_S^UI3W!T#WYgwtLRPnnnGwB5gEjZLGsjkgO*x&{9?X z5XL+quo`hBdg3t;$h$zakmT%G2rtx#oXL%q73S#!qVNPkgq`0Vxkvt|xesBX1dwMvP5slk&~^HiFAC?XVbFm z0b$ew@L3wK54?R?1}Uq^Rx9E~LD4xkjnVazF-k_60 zZ{I6DHUs#iAk*~i^55zDl~-HD$i-4KD_!{sY%hvB7UBg=58(g&PWR->mf7dNKx>Dy zz<~$tV(MZ!drSQ3zUQo$=n3~xy|(6I%_V({yRfsj-K+>l8TRM&Q<)QD%=7KIGgsjq zK?6EThwYfIQUZlP%?u#f-AcK8usv-r;NVV?;9Ky;$9W>etv7VFtdYsTQ4 zU$8Bt++sm9o@J1&4;j)Qb{{)D&H0tnVIeDyH&GupnWpYZ<)2I6Hd!Fle-Q_-%86d%2=J2ZTj$ zT#&1f8<5#1n}Z%QIZx#qE6w|>&LIsqlnC}$nd2ka#VO`p8&W*(J$*CXRb^3&^7Vrr zx>ACr&=X1NdwkMM`+ElVOy=1pBMO~beF1mM;T!79UoT5}FMB;&zUG^B04>Per)24f z*momL@*pa(%gY@oKd=awR5IVs>ScRYMzaT~44Ci*VQc~uJzZglIk zVE&^|`c_fe`HJIZ3puaeV*X;k-O<7WIMMDgifS<$n}TpdL* zxR@5V@|k2ao54!%lcN695;$Vzv~8RPOhTL^rRvdEnbTF17`3M4esS_cFo?8g`A4V- zho~&Q3pLQ;2OFFI`CjV!dSw6mT+!{}4S9W)NQbzzz&zlY-3#L_X{ecg*dP7WrB8f6 zO!T1-%erP3co+l`Muf292aCL#gfak*8#f12p7f8s~BODYH!{*{QSVP;oj)w5SHuUt6hc$1ncjhQj7bU z)j2EEf_-F<`QG@n`5K2&4y%mF*@nH5-z1d&CE%<9M#%KRzf zQtXIy^M<(Qd1#DYiyjy{`&`JID06+dM`tx?x%K)6pdk<`OCQN*#8w_MqF=~*kZI2b`AIm z8gB|VyQNAtJL8vYK31~Rp7(Z1k!+L9&YGyJL89JJ61$w$Pa?Y?J3f)p*UXCVK+WF0BnxD|W z|C-*F|48x8^Bot;;y>(|9N9$wZOZX4@e!{&s zeHlM!&7(pxKEY8t?~K3zf<)R`;pP>485Kdu63q4kGVvJH5W%QxFfCIh0318kb(p&y z@d~>VH=^liz> zF{2ihFZy~t0{eVi;Uf(hdXW84Gp#gqXJ2p(%}*tTntQ5)C)Q_PJ`jB)6V{Qg$#+{y zD!cLWvW!N@rFnu7e1e*q*TM`R06>g?$4}pd;_f~lLp|%@ZY1>FhO2Uv4>R z4HYXEV_iCZjtuHH{i5#)Gc^r#eO&-*W+0uU2xpfqV>P{$u%*COoa-I&NZi{xU-3ut z$J?Ixd{AxvCDe#5_JB9(6$L&}`~QH#y0`c>+Y_aE@V>9<)UK2g5W?kYJL-Ti!rsGu zSsdnH+fZ@}t4u=ha9}w=p6z8>9leUDaKW8@mCLIS*E$1hz1=%QwrsH6JcgH6ig)QY z(DK>;X4ers5;?1=hMo$|fD28#r_C}9==sx(^p%@4nThMr2X74ZL!)jneQ8F}`^a1H ztLA_HwKo%ll-X+CSmSHmslu1G8!;pU+@M%A9KGiixQdQGS% z?&vYzge@zn6LopDsgs=%`WO!qq~0kDF*=JWq|4V3BS?lC-Ph|qzaM`Ri-fv_!df}i z@1yTLboXUxJXHy$vx2=w_2lMyFVcet2^Ww{JQn-K&VL(<;&9wZ^mhui-i!6%xTdZ?4W>SMe&E-Fx@oTBJl(6ObT(gXR`Q|4+>gG5Iw+YEwuz+Y`IE?c+}p$uD~6v4W9~`q4heCj3i)Ip2zeUkpwjU#_R2oR0pVKA1ov=#O@@p!HK7?Ln=t>%wHuol5AQ53n0|uKJ;F8n& zf}&a+cvX^k;em(Y5Ipe4#GkITHu$4_R^zxi22Dta`T2LDy^}WxZ}EZ)D7jia-ds}h z-;T{6s77B|GN+1jlmuV=e39v3*BWy0*1YjA?u3$IhKA!4SG6#VM;^-!^uS{qo8(95 zGHyP$$czABcyqE#hcF||@y83$I2Mpidf%)l^l~F>{Qu69Q+;Mg2M92Vh4A(fA0EbY zMs`j$*Pr}vq)OEfhV3dy^>RD0&a^ji=%!c6O?Sj(FnXJCm;9PJ;cG@M>qllLMqFmv%wOQmB2 zXL+V{Lfs%J(^W5?I~{P*b|P2T+_Rr-AwE!T-o{qXdC zdJ|C|1B&AMBBlho@vXZ(3{iFWd8;`UNFP{-Rg@nkYwfcR69 zQS=5b;k@ZL({r<}tHt-w={ydp>IPxGLpHN17kc_K(x)ybNAdEFAr)+WwO!S909ko} zh$KHvrH-|+5j33d{)0Ihauf5PqtH`Bd>sB|Yn71CUR*(_+l&Liq|A!+c9LK|?HE`= zeJDX#FOzpr*{=39%s#-PO_mHvItqtz<}2#Lj=cL* zPdZkoSA6OL)IpUamKutShx-lPx`7_Sw0|R+dv#}dCUtg^RsEiMWPjbapCqrj;Pm;P~>is)`*7RFl z7NYp$h5FI80{hiCy+d*uxqLbYNAxO)vw8HrYg8jK)-2CrIe`q!ec4GPeHDByvchn@ z3(cRvjHC4Ve$vkFm46u(3!V0+8v5`6{{B$r^&`8+-J!cye!XLJR-}L-@_rVlD4S5l zk**L=8ap{vFGUil*bZdZz(cX3W(Pho)H15R8&6GHIX^42+M9b|*)5_aoI;ci?oKkwKXoBcLi>45))bfNZx*Wy$2is0;*Ad5CQR;smDyICV(dJxO*@A%S|=4 zowZAFw`SU5h*9*T`tUeUEqzYj<4GI;FOJM@vy{i?1-Xu(?NtOeg2sDSvj+ljkX&`1 zip6x|H6LZvH`gNFjYFYK92L2CnZ&U*IgNZNY#7@S?7^piM(8Y$Kd>>^`>a5b7nH zFbcFnoHd{tl}yTkkZCDMrAng0@kI?09wuFs`++Q-#OezjYNusAhEoCWx7Bs^6xctiBWU@eHAu#3w^5gS^6M;^+a{*qLc`9o-!=C{8jCXpFnU>|lIes||p_;`o@ zkS$?$rq2r!te*8tu(1oN>B}B6QobZ7;*_x82YyKqE z&178F#L|QyFv$cI*-c)j3mtT^c6)W_zJ(s=FQ|wyjF;#z&CEXY)vWP6qQNg)sB*7f zJCrYyEb^%2=Si#Wf+YXI{oJ$00fq@qny|KAp8s{313E~4(xlu`x|pr$WA~lxzLTb` z5iMW2WLjXIv-mr`&u7lPEf{fG7cN21joVARQ}o``P0h6ph|TYz(T&HkAHh@uFfc9b zpcRW3BQ}u`jXC_^C{(G#>o*?DPL2DeWWIm}QyVwPrS2_8t4bmgrT=_wge`jI#3wLq zh^3av_+RmbfCk-!r(!nZNIMPr_Kx%Y8n!o*a-X|D!2PPW70u&hKC#s95Fg{*M25pB zgYulJuA-FdttN4389m3jA?T5?!T+_{=fJrCM*Nsm_OE4oZL^FmcvW@#S~}bd6XW4S zGPH>4UwV9+!6*{ig+WW;4b?>wzg6gTDzA1H+q+K?D{p9#b1~SYGpgBq!q+XdEyf^H zdGUA?&035?A~xc-)NAGK6qMpA4K~ZO@11wvBKV8)BMDFW#f_7%WQ@&5sJxjh zyL^K-B)9!zS5cMy#Z)1vieYi0jL2xkul#Ivy&?-;?wj#4HY12%SN}Ws!-@KRI|JYO zz+?n(@jzC2BUdr&I_A;f^i~g#^IfTM{fG@4|Hj+ol{T zEh>!|c|vltEa<9Z$5-16cRwY-utK?r$n=_12LD#pjBV0Wh@nrB zXAPtXOIZe{Lmm%Aou=}Bm~PlW6dRQsX(1&uD(#!?<>7-|LnR{Em2goGk0|Rd-6|Td zj#F#}iuT~1;A7T<`P^i~D}mV!t5EpzaLvij6zIuX)j+7^Qmuq&-^R46|>zj5zf zF_(_^rvozQWiSMKpGEI^@Y}9Gl%HuqQm~FV_0(?$O%9KdQ*~zQDyTTF#$Y#w4?C>v z=PpQ#90gB~bqAM4boh3!ZFpzX=t)I%B8tZ1C~aH2g*Z%Zst#xS5H>q~V1s8va8C!* zCM9~~>m)=S2O{|<^P%Z>-5ix042fueZ{y!8GzZfLNJ} z`-m>LhMivfqU_AK(6ya@#G5Nk z`msrVL(Lr1rv0j{2v+|I+CnNC-cPhS7gM$V(b0inb1 z@COaW3Yu{4&Xbrg@{mWxwHt!qvfpL7Dl|ZlBb6l>oWc31hiQ+G@+2xNztgmw9d+ zUa4NIv0M|s;Qd+!|3>idnea96O2Wch;Iqo1xl9}LO} z;P5a)51Fhte_%ZXv(F$E?X19L!pE}C=nHyZlQ;+|Zns+oAM#Aw*$Qw2ZfpHL*A3WPk+O_PL6%_ z6O|Lx0=koHam*uaT8O9^6#dGvZ!#G3Pa-NYX%%iiT{>KrG5~M@F4ZtXm7s&b<*M?Z z18d&ma)$--1raIjl44WsJ z3iq7yi~1HmZuk54xBNGT2sn`(v0l$0 zSRH$yQRVrOaGgS!oJOI{gQu0k*$x9)&(9y!Q(a0{xG1-o?(vlH03} z;bgO92rzK|;hWI>>U{&mvEDNj_%|i@{F&ztht>4T^TY|k>pjdox-Sg?Q#Sh4`gL=a zFBcKlJ-$jns;ydb|DwZ$({R%1K6-z9o^{LJP20#Qg(L={Z22nxw|!$**UJWphJ|ba zXuF!GzUc2qEgnWv*hb8hc0&|bE?&Bzh@o>;#96c;<#P=Q+{m;SuFJl18H!?`Y-~F) zDqlpD@%H=C*=j%b-#+~W2pDKe;}s8o4k}!tWy7<7IT5xV8RzV##S<9EvdCxbWa_e^ z#u)$&i2EIk0pkO&U?A-W$i`DKFOX$aApJl-QbmA zFRd)-nKT6PEH+>C`)GwF>#g~>TMLL(Uu2OMhQ50cTd)|DDDLKxB~ed70#HX4DFth$ zx+fi5AS=AB)$~fg)yx*WBz4~%q}@tg?X6ureo6FZgbg7+lPAL8;BY(<+#yv{2_MlK zo%SXl%$CaNZ-gy`O=m!C6gkl%J{&eSJ4E}qp)MY78z=*H!o|O;DjRfm8Qe-%eil>r zXPI=qpBrUDh1FN~_jw52GtT4!OJ|;AzM%E~I5CKuEd4X(Mw>>~B{MtRC4s4}IjN>D z1zU4O;&8(teYAsOv|Fae2h=+Df+0X%fbfU`-@N0-pn1w11(o-NZ~NbEC!YEREL^rq ziAAV!OaJO@3;*37A$@W;eCDRZUXQuq70Kc)x)X(|#avN;k(ox?Gj0OmwoIt12LKlV zvp}3qQUqKQ8mRXcNCYRPU^xY-a&kMg!n8UWmL8N9%$;+Z?OR=YOi-LdOl;cn7Oo$0 z6-So{BNlNTmZ7x~0hya6qF-N0{F1+=^S z^k0RK`o1Xh%`mpm&A*Wjl^~}6ovm<7FI6Z^KV}B;G;jf=0;7of8%M)h{sCSM+cqu1 z-HjrBM{?lfJ!F_yAlNS6BX4nrBsJQp&GgJ;wMfZ+lNXlg1BSIaz_t~$t(Ol)3$GLC zZ^BHpx8-Mb>#)bfLzHg7!NU3n(pvq`55fUS#H50!nTe=F*64*S5&^kuvP^Jve^4&# z0O-b`WkEs*PZENyY?_mAazg~nvd6nKT zNHQJ`4!^VW_NliKZjKeXL{WtOezR@r?uduyR#skeHo!s%{GS3xKM&Oo$%gldFGqyY}+J0OWi@O+24d+G;{oZ5S2(hW*ZKnm3(K>Kc{DWa~adJb1prrWveN(h=)GIwLq% zjLkqwpf9@YwGZrbe2OT$*rp8+B|D9K5?k-8V!{Bh$^-T8wiBD`lU&jANaWvw;9956AvOx2LhD{rO_*6lOuFtM z(c_BI)g+jWPC6{6PWU$hB8q|PZ#U3?U_3Je1W`}$WRzf_d`DbMgT^A@0ZPfiLLH04 zoXtAPy{Rqe35x;aM{q(rj?#|4y+oO=)5jEvnEXMP&sh6RbBqc-ed$%+fGq+=lqIFCJjIZh{t4-k8(>2~Kh{aaXk%^^hAS{*B^_z&oq{ zf&<{#6WsID@ z^HhYz%l;Xvgby9i5^;|in`Y79Y-^Q;7j0EGZJ!CPg&!H!3Ihf!Jq`jtB~cr(M_-F< z5Ew8X4#xI^xZ;8(&L3gCZccV^q|to1eoE$L5){i>(8tq&Zo7CMtlpKfTc1&ZM*O8&_`8p#` z;Ue!u;4|$2EE&Av5Vg}kVxH`;ElY^d3$RTrXPhEn$X#9uHVA^0c;vQC!KA~T$ z0F+iXJynEcr+?%4(c53Q*R<14&iP%TwdJoS^{Og8yn@Bws?53EntJl(x$X;?v!Tq4 zEpfnre0^$jcu3jg;Vc>MaU2hfK$?kHWr7nmDIm_6Lwd)Kxi z8)UZw*rpW}>L5LIuGgoK*8u4?AV$pN+vFL{$AURXsC~}mBw+W6$r{Mk(TpdeM)deo zJ(p&Bi4V=H(&X61z&5877vNtx-oW#gC{C!G{3xH&{tsPhnzavIKkY2aGPq9TU{=`O zl-+gw3mEwO04H@`3-UGSyF zb;{?Yv{}?N8)i^%xYmj7Ckc{+faIG2Y|3pEE;IJX{>g>mcen6yE}% z+uq1#w{p~yXon>;I0WVHD|(j@SZp!a+=abkA4-+MubaGdj#yHiWB`|44pJ~cX{jh_Q~)NFT2XSQK-6jDwE1mqjO_6NAcB(%kg z)B8tl9yxQ~90A4ez$DP43Ul_vKL@U(ewOE!Dt0@dPjK4Hove9C9#K0fPJZ~xgD&Ba zA#qn#OTNm1AiR|RTZKnIo`1&_%4Vi-E%-|#F*fmkqVkBAtxWe|JZV+@i)vsoDO`O! z*D=6_eJk?zxb@zm@P@^jCT60n^r_2aTg|*t<;gMegNT%ElZ~6YDGc25m$gZm=I;OaLj=R5BboV_iBijd@+lHTBp7E{Z(*hcJNl4rbSLPi5=GR017D?8!W#&3?} zmhc!}lh{rvAd?C#Sk>l2Y6QWt&E)A^KQ&PsGYOaCl!3qc5`;k4DW_bH_V3QH$V%%= z64Yw1uYo&)X!(5AbRcQ5s|^S;M5jTcUgb5#i_#FxkjO@*y|+G$`VZyu@CnQ@1c5S= z>~hD%onh`VMi~U(0RD%!m`{!j-4-1a$c>T(&N6V^Mc5^JMV;T+n18AcG8Ua3HS>6z z5S~r@g#H_%uxQ?^zcfV|E)};(C)6W*V=Z4}F_EHIh!}3AOf}&@Y<=1bu`iT<3zO~K z$)T`hD-STWZx>EvYaZfm9Ws6A?LWOH27;{x4Gx*ava-iZFrQh1iVqVwL5798N59L8 zhpeFO;J2hxT_)JcsCm6qvj?jP2)w zA(IgfmBmI**BvB(`N`Kt0A-%@CpXtu3Gr~?HEpr-T1A@Ep-AmL)96v;M z`e*<1Kp;oud{NCI*^X?9u^S?(7L~VKb&UN)j6g*wpg6l=i$pSrG`ypqBGPATi0DtN z<_xC_$nZY$F!VC2y1a{{9I|o#lz=2lL(CA!X0$pO-Zg%8#5m?aJai5AgjEw2UhGkU8;{otC z;@Ge?5`$t}xMy5qrOm=GPunXqH*17`XMrqx48UtMjd*F=%mQLKIa`;wVZ%_gq*(Rk}2!#N$a~*>!ceC zM!fJ4JSi>g@_1l!y%3_J;^=IZ5STAlcaJ(KGu-bkIN3(Zba-sQ%GPl)Vn4)J@L(@Ch%O60m5 zpClJRzwixzxi4DzC8!%9T`hiz0_XP_d>kbYX`8%ZAR$;NALhWF#u&(gtW9fRQ4;_t ze*fu>&?7<`un%jB@r=OMnP?0Pfjq#N2TwSPikK#7f3d&IY;$*}9IL_UG9%tu0QWi% z5S8U&=W$1_o7Gf3#w>0eqTYpljuAQbtPI~FRWsyEsyJY<=*>RvpB%SfoK2~(D0vXZ z_l}Y4=?0$<0)aQH2EiORc4UQ9^l+P{bI)*seY8*)ROG83)_-h&LOSi*fU)sP5$F31 zsOZywi?zl2+LFJLy{fQ`gW}m_B7+Jew1xe?5?qCP!dTf2k zegEk_+;KiIz+=#k(LO~qeRr;|yUTS(W>lEvZf z=wQh1PawHB7B+>La3bF&f$#1>`TK|sv-~&VTkU7x3Ouz0-%`5%Z>5_lZJ1N+9MPvx zHYpy0jQuQ#E0&LS|Eh^i8lZLdVdBzMTP5&48=sz^H@P=5g=zykIt2)`P+u|7RrIHg z&d4{j?o|i$ka0`gGm}q>rmGO#TYXL9c!SQ6mcl!*z#Tx0prIv)H%wizxg&FgoUGT? zLdfC{tj5mI)vhQHpi)=#@JFj1yDD&; zDHyTUXsE;&&PLi>aZcBgEU2_h3C!j%Nu^pJs#!YEM%0fT7nqCPH=bT$+vH*YZVQd4 zrj#=(Uk+Q*pI@*LSjpPFnvF8DKW{K=0d+)=o?haPZTe8J2@`iDwBi;@ zY+9(8neZ2)9mz{uaObmyDx{#sYOI6o%avVp?elTf#_0eCxOv+w`E&KKZYP?rqF#j`w%0c?WKqgQOgV=U_wA$$06NZLM|J+r zOQplSVNu@dxeL$9cPf~009glzYfqK7CQ}lSU0{l11BXy6$`qLBlO9S-tivdgf~xQ<6SQ;8m% zmSDBZatcf?hsfLQQO0bYyE8BT7-&bxACHZIZJDql<0XWi#PaSZluZCaWyaL)hHG0q z%$jsF5J|oDls0s)`;-Ert&#pERkAD4b!~t?Pp1Lac$L^O>i08Hvsy$dC_0AoO$zdwkK z?GUdDK%1UyLZ~Ot_#9xPlp+OFh#x4%5FwkD@eyZ{NJw>FQn(S z?!RXL1NkFDA~0C1hhAm{A$#)>3k%F{*l8k0d|}v>mb|l`v@P3F8lg3Vh9({?zSALk zH2-T4Y>e!#=T=AaeiYllZQnM zG$iVxPw1J~3gg-%oUlLWLBeaX%>542?-Y5U`Bg*%FIMA+5d|qlC0U+0d9%6Z2T0)w zxL`Tg;}WYUHf*x6W=O3~(-5;Lb9c#op{w;A#O;bNS{;ivcQWpEuNYY@P^Ah_BA1W*0)q;1rF70`ld&xcu3$j(_*51`aiJzmd(s z^W4QCw!@!qzpJ~RYqL<)!qQyR+UO8TA$#EncLN=1D6u z#zPG+v0yzk5v7xF#LE2YQRQVz*xJYqha-`!35+{MDn@?}izi9kK(90&b2uX<~+tzHF!{6xMYre}E+i4zJ&mLBnG7Q`K)eYIkkiz6C4B91a2UafH$6&LvJJl1==>lJ=;YU zNCr!UZ#@CB4VI9X!XVjCje;gY+!#_?2w~SHooQfXI=)bJP}y>Ya$;s=g+#hL&)*C1fb;Dyfz;hS2N?-0CvaHhEy5$8X9b;Ne)4HN=ZeOxX~P?e0(^bNF^ z2n?>?)RdR~-SNCBdUDYAtqZh_tN!~#th>34YX6d6qg%_MOxk5Xj@ZW`TVRWdo zb;Sfbs}2JZE&l2Mhj$0Y)t8dcT-KhJ$T%)=E>WJg%{SxY!UANkMbMTNNiKL?j>IE7?tUnnd;VjsD zs%TKy4U6GTDlE{yXZM_F>L+2M}%O^cJI}@AKVSlIVRieFYupC!u!Wt__?zND3{;`pat^&E;+L( zGXHFd*PlJ-rD02nOLTJ*w*}T(+jV&x~VJLc%FgHHHueOoQ@D(kMJ+L5Zd@NRHk(dFZO>c0Dz!N%I34u>b zd6*fc7e6E~M09&1wWGMAj&q%MnJb~eWh!VlL_TLEC{S5v`V-tP2xJXKJdA}E3}K?D zhceT6gqHJefWO&=UvT4NaDuVsV`ulb;~~F(*ygk%Z-UMTE&mU>nBNfgotKwUFdOFK zCg`BNwFSCCxxWXP($5vWXWVT!fEb?jNJNT1n`M}!GB_N8+zl>}P*~)T1yGPOIs(RU zQ7`;~#1L&27@-BqEa1YL5P%moMI(B^G6U z2he!(5cHwZ>V6*l?DG4*%vCWX4$qruCUmK>1|^670sKBqT=G2EgnQS?mnGo@$#96k zD<${sa8#zwfoT{74o}0VmJf{qjT+6)&ilxne0j~FwLnZ;ef5GfEVffpn;&w**gH5p zZa@xJ_!HMoQKLerJ(K9GC7p#>YP05@`cC7iJBDjph z0b+r%=$YKJq<~Y_L`ga3UCzJG{a>c0oyPwP4tOQfCKh;`#fQeCasIFPeDq?e-(JnV zk1mz^B%m=fbQY+@MYiwKFhEorc6ODd$`&VKt-wHbnw32;PVKyG+HYttZikn}0>BCZ zn~QkDD0=UFsC@-%=jHkQjI}DXIxRPg=XBA{a4BSZGn;UX*Re51`|A_lzV23_%Kb8& zHW5hU9O=XvZ5}z#LUs{wyIrq(4TKkC%)Yo%B_TilAMMVClW&i6dS`oJDCj?dc_79~ z%IVV{;L&+E7R%?y9;CYpl)U+U#x+0gx^7kb?ywTVGHdnq*kkVQZrgRt%HZbn@c2-{ zenC&=4ln@OXQOW~2j4jdb~W@TfwZ#v9Ns@z?uvGJbSJN=Xi&eZU!qpF&9XgGR~NDD z&uQeI9hEN+whuZxYq$2)%4*z0=w(BqK)`m?pUE3Sm6qqA5Ez-IB&2mfY_S{e7!yYo zE7+p=$*?}2S^1UdNqB=J-`Xlp(%@+x=qe{w%IDOP`Kcx(gkaGoLbU^nZ>xZ!cn59U zG--e5cOJcc)Vpv3e$-8XdS{2Y6e5x4%wNoVN~atwQ40$DI4A~csJ8hhBu7q+cfJBJ z9c={|k^jPsOB53!X%j?RqfK+(lMhR5wOcheqtBrA*~wPT;UKkLw#sN7#_?D zx##4+1u)haNs(Y`1=+3dc3pnB!d$~o~1%2KXXpNX+14=h?Nv@0`O*`S!%oJ zV2hD#$r!dPt=iubD@&RoMKI8Ov^AM{K??XBu4!RVdM*38Cic)Tt@{>F>uy?{`y(l?(qEbFH9Pg1Na7zj4b>S=eO zvIWmg`$l;?)9tr$&_0?nViPT-S4c*tt&@aG;>jCg!*t}p9?2k{4?YGVy??8ySng6o zZFMj>u0ius2Qu(opa#9R`0DjBRwT#gMl<2K?(=S0{ut>DpvIj5N8Cl|rm!6IF6BJc zO2=P`jf)%1ec{J0;^_J4sij>KGAGJhzOsgs2WnfUoDR9mMqzv%y;m<=4V7b1if~e) zj+6-3HdXE-7f{dYFt>=*z^333{kBbi@T3>`DZ%h}aOZPm$CG9Ki6Q8 zRq%PovfCN?YU;uS9lu@>h>UD5LTxQ-dHb6$#e=0Wf+{y$oPVbG{s}h|I0tvtm5<~? zXL-_6pWq(nje3;o{1%Z!fWKuBW`AftS=Y|{OIw11kJF8dac9HL1O%gYGdD5)#m^eo zM<4Z-Bd;cSVc0SWECGP{o!7~M3AHE{=^d?<*J4L1s1Uj5X5v?d<%{r;M0;jlh!(balG96IRW&SJQ~4p zZJZd%X`a9-R?C!MV)dd`^XbV$hMqyHj54M9b2BqRNBZ>C6pq3!TnU!WfX0OoYAO>Q zSjD4HWw$)gPC~8Ho&_GcmJ$nfVJcH5v%PyvAUZ92pHX07GesTzAVXhUy9S(=%~T>1Vpy9FV{CQZ=f~;bT!JV!FvIk5G?O89!aW%9s*{Z@0Bg;QqJ_Q**VKb_ znSrqFwT3nWPKF$hqp^o8($z=4^orCBdIwB?O-h%z!9LiFke(}62^li4<5{IzP(6OQ z!As=sLt`7ry%B%k<&%uaN|g5n*`8E}(}qP%)J{d9-+L32^xe=XJyoqr|2qZ^^i){? zAQpdJ2t|jw6lzVaNfj)lxb7DpLa7q%YAHOB-ge2#j-PQ1K2&w2jRsvFkUCm{vA0mL z_56n($o7+9COUi%^1z~RFPA5>9Vu&pnLe9jH#=L4v{_`g4aoOb*ko9g`;eB+7y8{D zMxd^flai*Fg8W1dQ{M-F3sm4ugzl*D-?5TzuZ`*O&rrawFsptSsQVYX_}jaP?O&Gv zl{Y@5=a@i=3wK+-1TJj`X`A~xMz>dz2IpH0zZM-!fpJ?P>oO;mYhOeH?eTiH7fJ-@|AS^S9@o;0oJ{y?#(*|oW6n**Cz?SUs|CJ5n0 z`I)%yU2QK(zmi6wGmHXZqw!qe2Tuy zlX-&GI-76s&W$9VzIeSGb(%599EB&-RFZ|}#f%MCm1)*Fkag|Bub*UD8{(3=jI>4F zL-!^?2H@<)%c4x}N#i>Gq&`2>Tcs2ezGKWy*2PH^62WH_#Shw({;>qtLL~==M^Vg{ zQCjL63gz@Oen9v8RD_sR45eC)LM>fHLCkT*C;t9{5~dx!{%4Q9iFLxPFfF&S*^>5< zE@1?0dyqElEm0`TT0%ze@Pw^$6B@M{3}an78U<1JiXX4^iB!aq#i>*#62yLuYH;A% zT{oCmxHV~Vw+2&Vc0T6dG&SBAd$M1%p>($vsH#L!qa_2NGEFI+#pZiXWWvK4jO47o z;;)H;V=vt0Ui&mj8}X*dI8JN!VO`#D8|C_4W6_~{u97aNbM9)29S;=y|Jb0>yp z@RF$-rmZ`V^#$Whaw;_k2S@gULoP!Mcta@eLI6vux5nU@U`$7nyjiYP+VfGzv4tBJ zG!%UVb+{5I!RkLE9b51o5x_2MQU?mj5)&Y^pa0yDJ+-N9D zSL!j&l}&|67C@H^Si88l)}tccAK;~O@IVL72WKIfhAO&Le?GVmMoMIc3;^!e)wloa zTEdA{ioU~w4n0APw|whucTxNyCI{$N?gK;oFLd!L_ofbVGQcj>;WpqY`QjPaQVa}# zF_3!fBJ0MoQcTD~RThHHSzjcnfV!b{@Z9cBCuX4fR!7iND%qsNe`E5C zS5ecNFxoi9Ukv0dS+{_XMs{zK?i5!Z8qTAkDn1a3yZi*(G)w;DyVyvO=_=9&{E`$* z^`m~`ciycp1L^weNDyuGC)^kLT7ZtR;7I$O3CGRJwl zca&DiC&aWB8n7A?BK%~#3)JP}g+oWt(iF8@RO6{+_Q=4ejBPYHC6}mQv5x?AgB87! z0wdFyhJaZery17P`ZU%Yp;GZ&XO*9_z3+<~h1hZzR{SUDXN55W@rInbNvrPw=AfR) zC&K85k3GjTL#pv@Aw}kf%hgt6SuzvObX8t%ZZN&uT*T{SwA_%UnfLH-8Cfm5w!MYC z0wx{;a)ealVceCJkU>Hb;C-bCCZAo(6=pCt2*v~|AxoWZ$G%>8fQZlC^9x3=yViD}avzO}4Utbe}x(YQL->W00~U=vwxc?dszmmYtb$oh7Eb?l9| z*F$rcbpQ2hPa4??27a*Nc9kFWHNIsAS!h}P-PC~XgG(Mfy1TDRaMn_`6~EG&Ckj6DZOpZ zyP8axnsK$2>2D=CtsvRn)Au7U|MJ5jY1v|V3C|CT-ko`}QfB6@bEqS6A7Kl);lCd` zff{)o54aQo%6(`Yb}RB!M)n0~>JAfZZ~vJKX?2az?I+9f=^7$K@a@S5pZ?hKZ*NW) z?A=b@t#Dr@zQMc9_k(+1pCfDC4M{CEzj^yPz8I+7*Guv}2B@RCXp0IM zsEluw{uT|F@TlkrZvUA%x7tz@4LN(nty4w!$uYkMb=Jv0 zdL!gl7M3blQ$P?rj7{{5_k1zH2daQpso7>o*U|9`6|CzF!H*p0ViI4^BjW?yeQ=N*#TO zN|56In;T!^N(`jd%Q_?$H^MT?>rbyE#i*O*pxh3yd^_6O_W026c$_}N{YxJa_k}^8 zon_zW9C5Ow*4z|#hTBws;ei8X)wzY_v*ONTiXE+zdHj!qil4m((@2p#x{*|SJo7$C zZ>|QEM70$~vim+ssG(%ZE|L{ts%oPw(Yvb9QQ3;2fldW|u@oEg?KP=8_iDAdDbW&E zi#~(3!fFU$2u+h_Hgq@gh&bDM!0xKy*rkl0Ht}p78^8LRr`N3FwrK3;-~2MLn*pl_ z3B1xU^;G8W=@kRu%85i&S{VTd-u8UwHdaphb*C-+ogLO_6F!#8A#pv;Hf4i`|HzJl z`Yn46`EX&|n3CN!%hTR+n+HJDb0b<&aow;HuYQNM5J(GECKE!T^M^pGapb5@)U$& z4layZbux^tkBo5rSzAn@In&uW%`@(>?fe;VB&E{I{`}AAs$6X;r|Fhcz2P|%G+^dMrx>GE5y~Lf&0$~lfv!&H)1|lhyEuz9Ts~Ltv^XDvOE6hC;Ro*s zsP6mUfK1s%x$G8t z&{OxH`oQPwdzv)PPn~C3L_cOxNN0+3Kj*jw-oBsM!e}Ef*ip%ss50t1w!Sa{EBF%O zc80XX5>ca!ASdJwIFhPZmAOcrCiC~Pv^xE)%7JtqLABFK$vEw1eMsHYe{H6VSx?|$i3SBeEa2^v{ZWU0np>NfZEz0I^6IP#dxt}~KLAOHYC zPEAH^NB{r;000000N()s4FLc`0000%Oi)N5000014>11`5kf)RHca`GE{3~B^nU`l z!y&Kt|NH;_-|zueP&gngNB{toDFK}UD&PSQ0X|V8jzogJOsxO_pq6&9JSSJW1Lv5( z*)q#B;Ph2TEnduua1D{be_!Iyknr;NjKld91^@q+A99NHzv+MB{#W_m`S;%c$^6~; z#Jpd{f2HPc+$ZOI0PSb?&+EVJdWv}C*eVwe6!-h^U-tb$zc2qcreA8m@B4uMt^e8e z>%}kd|M&fazo`G1|IPlN-qZgNJ0HS&i*(}jTsk_PH#DXzw*L7RVDM?~0d45Xq9$LM zCLbxQ^-sW+Qkr?f47y##SR`mi zPp+Mi>S?*=WQE|C-rK5lZF7|WOsyBYPBdBq%x1Io|wX|0tbQNGI3b6CYBxZ7c zPCHPx>mjJwXf?Q0sd=a@lOBZ`L6PqFKb`M4zFv!g`)FOHH^e$m2jh`XoMXwpZqjQ0 zwpIce-^JflDZIFEYK~7ql%HqNI3_w6p!Ao)^`TY6{J9fJqzl}7h_!+Se_34Vc|6A+ zEk9cfyPP>WF8pUrD}^d*_~+R>nV?+y z>oO$!%s?Lr%+XN5SCSa!LGi}84~+P)5p#G*i;C?6D;7APLh1?rs@f&x^%p$awOx^ybHO;!so!QzXnYUio!R<)#j_I&Fp0*9QB5^7) zg^F00j<2f6??P#hH64X{_={O617@Sra^e6DcwU2<#amXn5LHM)3SWnD={A4j?KP6@ zh(|fPe~*P`QaUk-2Yyy*sWYdz_Gl{ypBU0vGjjC}DssRqk0i4pu9QA(*O14S_1DE1 zJ4NOkCs<6A=~cB$?j}5eOR%7>} z)EoV-1FCDMvM{P0n&?$xst3xrRZj!1ER%T6b_9+JQ03KVYa&EouFysFDE*j#}DmQx0a`e_0NJf^Nc~%A$sMSejG6uKWUXqt?;hp z<960K!m=x*1uq&N&2ea5vtLqciS`_4Zr#P%zKPs5>Kic|_*DciBk^h)vk7w#IamIDbjTYxCRF6xJg#Av<1Bwi{N#3(e$bV9N1;8(7Vl2) z6l+pdbuP12C-&1Gw1bFwqpsl?5>oy2lg#;!0ix-5A?WRc)GRMFG&!9mxIiL>gHPk= zFwGwUTG^6_FwV{MKm@m4D3Wht5kOOp=lvUmKgYwKEB+>w!o#}zC3zDnJJ6@U9G!b4 zel}E&xRk-E-BAc%##=ubpXTB8AA7IKPZRhF+~tcKu6@gFRAMy=!NPaZy{~oh+l}45 zd@vj%E9!x(mHl`(pyJh`J^3TplcEU>YYZks~WBy zTnK9^3;pcV#At7>rmW@27y;I)x9{BK=e@b1qPq5J$#}ERvHmXW{ej6Z#*s}f7|0^< zkttz=!*UKb=eda+c^?aE<5^zQy7i$8=}G8Qy~3C1Cr`K`i%3e{ArIA219_Zs`nmL# zSsLusRUWVF#Ma_%r=cu7MjjZm8^}?v-NiCneF5jZI)37X5sF2+!g0Spff$`(hzc-0;IfH$hAOWRCy|=I)mVK=#Ls zB6JI9r|9`r8|DW<9$7$NuPY_ z(adL`m_QghJ$VQLFn96i&u<}Mk*@6d69g-+&b9Jz1pxd2gRA^%74efgP7C@c)o$YO z_kHcL(U}uNVS!^_*e4OtgrAgGiEX>)I=u!FQp3}6Cp4*q6#Vk8d#G+fIQNbJi>j_3 z_ug@VnVGTL*F;Tro-v)HU8^fy5m$Jl;`1NXaG-_mt)3B$Q^Pr$Urxg7pC84Pr{#q{ zHSmj+M#bK^nyXR`vWyZ#2G;AdGM!cAm5Z^V*naa$h3ZhHjtvl0{_>n%xjp8Rw0iuX z<{jj3YUAl%Wu2;-vuUCL0RH{gC}nN~YwQ@a&%3s_d*$W;_b*NKl-@C#+IyD%7*9MY za*_B3nHgZpsx0&8Q@5Jazk1KD?WIJR#WEJ{+ZTO#GPU2MtNt+|^G}SD&fc8Zu|-Si z5)?qKg<$pa0G0*Qxjrz(HZaWaCe7_A<8~QAKTY}E&c)gXKSE7n%A5oTq&jWm|Jq)+{K$dFjaV6D9#2}4cgxF zlGX1vrsV88i*#f1p7De+|85I*Wophxzlx~O0)qR zzB+kF$3Y?j#b~6IMh>xm6H3_Dlc^7jUln453 z&ZRUt!VpHHFm4t^b`A&8x`CM`uqGj3i>z3rmC2vla{OBcveo`^u@tZ;$KGzvUe0U~ zGA?A@1&F8^11sO{Ue+#ZE&>k=*Y_hvVARlltJq#Xg;jb`H7HBEH_o2<>5cqbjtZSq z>9Z|tQoHNejrj@6utCm6a^n650P*r_ozIsQ8jmuR-wRj|bs`4l8g50jz+SYOHVPUQ z{WU-&=tK8$;^LYI$&EGNZzL--w>;wX>VLvUqw%V1?{BhqhfvdciLqA`Bi{?#``LTl z{hG3)&%312U+Ffe|LKEh=qj#b48yHPo;SIh2kfK&{npi9vGu_UeY?2I+rlA(o-FGV z>!%rw2=pgoy=gMe$aVgnJA4*cWt2W82yQm$tpNRy0gKa&^j=i6*4|++oo{F~*e#OY zpbkr~F9_Zy!_fbXU8rE^zLGPxh_Fg5+g+IcR|#tLa^)JC^B>|v<2|?e^o9~pSD%;X zX^IB%lKxTFS1n~<{SkU?NhxQe;;K{WInFNZ>k7!(;R)D`CusaRwsPC5`)>`d{WmB8 z^gAZ0&3&Ci4yZ0jdYK`g^^4T}u}SSvdyMFZf^{t3y9W`uMI2^G=mRgA55xROwq%(l z1UJ0T?g*4)O7Hz*rMU5tsgN|+6^WG5IRtdDV|y;en=6Mjm3fARPZwK(LBvp;W+L-A zfa2w|R$kM4X2Z#ArXaYJ2%A(-4H`K*yYkaZranTK+?9T@&)}}W+(=c=<;yBhT-!JL zP&hpQ4U9P2i{ij!xsQ!g{6$;-8WGolAu8za<4okM z0QG0P;XTL+ccY#=lMXX4Q%lxs>AwFf&)gAmeK^x4GVM$vAc2VCk$|p=HJ=U5_`XB( z4PMu%>z@yZpCD)vEVGoE=p8SQ`imX7esa+7Ou$mueY(Ph|4W(GH;t|z272=9F5d^{ zh5GEz3h@|5VtFh!NE z5dncEU=9Kq`CTuw35)Pof7}3mX~7js=14>TINhT^YcMRq1$kN0w7*b-u%Az`fP>n`Fx+`RK> z{~j;7Q`!nGe|9U2r(Coq9l}-8)Jw1h{x;Lg(52NiUA!)=>Bl3J0=@-H z#ey4>zWgE#To)vRe#*TuI105;=U{@mP+l*o@ysC5@B1wFPfz$PvYxH2QP~}G9kNyl zNmzwj&{#mtm0-m70bG~N|BEx9SVdd>U9!ycubq#z0j)f+(wYLuQ~x~&34`cBLg|`S z9_=`VST1r3MYkAOB3hwmUdN4ahR$S|7oTSA!z(xqRPQU=aRmS4sPQ+RiNSE_?3pYT4i~9y(Hi&9!tLsh4KKYrGu3~_>8fgKTuNWlTnI$Q;D_Pp_pqK4fj-0ojwJm5TG09rdJTHMA9&DhH;U))>$@|&lw(T^lQT?b9sb#P}2 zlkdEtUqxH8jM7*hF0!)Y=9bQ1?R%xrL^bXax)~-1I@=WaWxz^5UAYG#Y0a;Xd1O9e z$+K5xPWfHwK87%%NyFdP6i=8kToyc+^Kb|=R!qCs<~!X59R8T}B%xm}8y*_Pfa5UJ z_?qSKSUlbt)TC~>C1W4eahpa1!*fEvnTU;Q$geC%tSZpWlfs-2-6(A+s?e&+;`~*e zL(w{=qe>$0A|^CIWj^_~>{QLl96Wj>l*P;xqHfnj|u3 zmy5_m^8ATDlShCng_4X1W#@?Vy}v5wIW|A%g&P(ogfqBnclchJrOUI9ucm~S`!Jtke*}@f~2~Ya^Zp_k{>T5d0ylRB8 zLYVEYP5n9B={v?OA=&BJAtI^v_X$V-zWhs3LH`UsuM+i*b2R@It5yR4{>lcKLX)y` zHz=IVz5|7P?QkZ0R>$&xu;t>m49VLXz&R?SuD1aCj}0?N$k9G2Su;F$z~79@C|$IA)3t*M>(vynI+>jP|eQf5VP9jWJ%`Jf?7Ut%a>gI6u2_%#IQAP<}(F8~m#$AyK?^oZ%G_H?D(z0^tNoZ}D^&FVuryDY6M z;W;)0r-h;OYQ>ul9G2?Nri1y??D<_-Z@Jr41L-l}e?)M`*bDsr;c#{pSqo>NY>66W zW`yXgpcM@O%FN%wry^3i6NaH_PDZZ@AdL6G_qjm}?_m?D1ReY;mY(v(=)88B?}cj( z>#rnZ=r9H|I&s0E@o}kM1>v?R9Sv$&onysu8_Q3}HOKc!*n(C{ zVw9X2+OOMcjo7X%nw9SCcZRfQY@^K;t7<*H!1)^!U zDwjGLkxC-cpSSg3w2J$hI72J>=^wbJq`Os+MX#psNmz-ncpftE1mBE$1DMp`$)cZH zoCH*&bf`DQOo)Hy+s?+h^$l7ITM$lxEOt=|80)|VwDZc4B|S-bMEDb5jS_k4B77tF zJt)FxFX-IUN|90*TeUm*>e{e82k;Hn5aLAeAgNB~fV+P?=lcv8PN7~utc3a<)RKB* zh&i_zG`1>OwLQ%e4Yy}@yL#WJt$@?Y@mf&xp7;B(v6R`)TP1q@bs*QzY`!^uy)5NPlSEk4kmc5)?{YS7 zx`B6sh~1|IEen1E_SHjI$3KR@aP{E3Pr>Q%8mHifDLScf-7WtZhb zlny^-okEkk9^O>V%Tu3iTP;Gn_bfpv@n@gYz<8@2LaoE3bEMrReu?*dm(Knm&+6ue z#5FrXQ?A^V*ds&hKIRqvKtd()0Su`FTRd;X)~f6!>oKK^htenYSRn0D6ulob_rAxH zhtM9#DnI}ueZAs`vdSb59cH8DKbYUPqF__HSaApdljlwrfZX}&Cw)SunJREyJ)Y5e z&Qi6Vi+kRe=tT*28bg_{x*7k(MF~w{DrZNpKXOVRRw%Al4zWbEBILRy$LfDGj0G)4s+-_xsIIxTsuE@27jWhE^? zEdAT?g%7dSdg&l9SgEdRB#}ii7cpT2yMgm;AM9Nbar%NFN0}xiLK4-x689gM8V~iX z^na+|ni@%nvP8e+zT0hyHh0KYWGdNaQU*zhW(Ka-O9qsggmI_Wv)zG=){PQ?5>0OQ zRlRhgHMS<{kT%{y8?}M^VE?mKlZTdT+=&!jPggvHQgH~BUw5|ITX;Xb1#)_;qG_tU z1yM&$bQU1D)tEs;XUFK8^78Rq7rw~3gUB8FLaA8cq{gK2_1B%+psrFQS26LhROaaO46CN}Dq<9*r@tgP`GlC;b z-Mt{|f7mIKlHLK4F#LPe-`3QU6=|xsNd$M+>Sb$D?=XH)(`>}^l7Y0}F$rZVCU)W# zD>+m37&P3zc$+}qVzd4D#Sai5JUS+kB$)Jt?R}L2OW8dhJt9&C%?ltZBu##W$(*y& zssi^tw3K-+ztz~GZW)c6U5}f+aet}yJ0OE=sy#py*PP}_Mk4LCrQhd{Q0EQn*takn zKQXpqh_)|*J;n?EIyndy2XXRBt{bucxlE%OPqQ^8SQ=V&8j55tf_g?LG$4H+2aq?a zvj>I64CZ(zOiJ-u*jD|Z2=Ru8h(Yo|&MFSHfrY0xSm%nQCKgU! zL9EC)ia)npT^leOg5=beV%T{Mh%x{Xl5LVpT@OPX37Ot))X0jc$4Z#C^DhsAS%)HZ zJH%SDV~bRh+l&qPKdlD#@v_8%VLb@jUV_XIh*SVzUuxyzeRTt`lmZ;*kHLb##~7|g z6=#n|9F@Q(ZgxSJcc-w226=MmTlFQ$he=G?m#4Tn*W)2pg^8BPZ$rhuFRGQBeNo7= z9!?8&V#DuANo91P+TEHTu1_MbM@dd2x;v}SA*HeeI+Ej_yX*fK*q1eqrTdAYuZcM&DIFdlm87@eEyS&fWI|NO7l)L>!ripaD5uy_;Mz zQB%t=(j?DN_xT?Fic$7~TkX!#J)*#3GT+;ba6?vL!b`#976L8(c@?g{xY#MKP;m@# zwA%@qWV*}j1!gO)E80-zsn&^W);5?=f3I&c&eIz2(ZF)BO6r5d9X@#_E?W-cjwDv3IYq*y&x!?7m_>{M#8X{+zL*3Sl>xQ3ItVt z27;Q1&hVc(+XdDnTI-rm0b|H@W*C_K`yz5j0T3vd_G?# z^A)7@ETLJFcx&$aw=OFg6Pc8M)U+a@22qadBMRf;AkfaK1ZLvz+9cgh>2tR-bvkTp z?&)vglnH0@jPcUO-gLh3l%a}~ZoNjpq*LRX_x^<;angok(s0W7US&B(G>2KmatEAt zn0Jld{AlK>4q>7c}6W)mZo7fb`VMm3h$%46{? z!qa9uC={u6qk1O6K-GQh21evDB@96wagvEVX*N888PJbj<@tb$aqZK&j87h<8>zOk z_{+j3*^xd`oHHj84BP{o!5XWmpi4pdxV5~IYqcMAX$-}L|8f2vFw|>AN;qM8xrU$- zfZ%;+=eDaJGWy1jq4kQpSqVp12e(mp;4JkXgrQ1{$V5EB2$l;!P=-qYN0 zq~yDC$__n4t+SQ{1d!6NFK`FT^uFEbWG(?4%h*nEw8tW8fQd}MCbwFf6sQ}BXZAGK zowE#+6f+RbMTS+X&Yman*}7LLS0b94)_j!j3??1yIpH)hr*mu+(5CkCp7B2m{w}<@ zC+|cUS&e>^(MS=m#o2KVsC@mk-5pt>p_6Sck133Y=2^M8)1NO#x$#Nxrwwc(5`MAK zHMga&GmyW+QOQ7!xWtdXv3l&W&;w%>pEpK(-%3M%FkEBupU>_tnB8H91#LY>X7bHZ zY4DouR|c$0OZQRUei3jo%Kd6No_0^2wTs7m63OE4sYIomW5ZlTG!0kF16U0llopiZ z!E@jof5u~u1~4i=11du|jUr3UoSbGH9TiGDkHq@0=V1Q;BE^gF2%@V?DCzW--`ZE< z!{L>hd3IFZXAnM_!?hW5xJh%t;O^|Xj=x$FbZ8CpFHa!0b@7NdE&5{cL3O)~n^1z2 zCElggSpGHm*{S^jO*~P z{)>s@!5nED3vkDN(ehtEzqogKqApIWyrg;;;5uM&Z0!Swvn_C3W9~F)P`r)dw9Z z>mdxRWJKA)r_m|7pPY49B{^`i*>?k-k*D!-q0AMxR~CdYXiJ0BcxFxfBvv$y!5!=f zQ7`V>mm(4yzLY~5Qq6mnFMn>fZoMgUr#uM=^w+=5YMlTygblCre6@&k5HXmgEGZYj zdo8O2z1Fn&*%Vah*D8NOF9r#M_aJB)F85?dO~Dt8BEFYAFaiB=|BZc)qcNQ+V5ys0 zsNz<3x07UTi4L(nx)2Ar;l?+q$m7k3`yeJUyyqw9k&r}@93y(KG=x*|>|PO2xB|Mg z+ouajWnGqEMrABk>7L{Sezdy{oev`>TvtO$Fdns*LnYC_&z)DW6pK;#B6o7aiEb?^@KB;yB^Rm!k$Tb80O1k2JQ%le!FKByb{^{2y5Cv@WyXp=2&SfDq zF1i?5>urQYmjbeUsA~BV!A+`t42Z)Ff(SK>X0=afE~*$jt7T8F3Fb986U5SwY1|Y8 z)5zL#(z3s>LnJ4LuJVb)L4Z@oAGFP{GiPkSyXXX3!fUvAsvR zO5(;Zsw~SE*pXE<$C=S*7+JpACqs~|j?1BG!t>!An!Q%-9ECRtz%Pl%`0GeU84*YL zsubxuJ5YQu%oi+i{DHVZobJleshxPNjs4N-?8Ox>!CYKpPpz}``fplD(jzA8>l%w`4nY`#Q;iTku`Z2G{ zS3<2fB-3Fqx&*TBtrM|k~-mhdq)YItqFBpCA;?3m;OB&$mZ{=0TWs4c)5(3 z4;CQ;ceAqa%{0O1-=uDNRk&ZAEdx`H@il^$#h&yY-JfxQvY%gJTn?EWu9oqt=~zu9 zW)+x;<6OpP4O)3`gKRk}yN#qMh#G6muXzF)+roP132L4Rr4_Y&3P>=7Z(5iF)7e2>bZjP7 zxupB88y;;D{#=w^VsMay*{>Zrw9iIe#^ur-tw&p!iHhP!Z;iZV;tFah4y6QJ$fJ}2FlR%{v5t*-bqD>{` zU59539>9_elafIygv2HHqds9lDzd<@Mcs!VNEi}mK2dfhq~_VtYgVRTu^QcWYIANX z(Rr{t<0??1!plMQqKfz<%?}0w%=UIX55%kAdZ5^4Z5ve4({6MZT)SO2M359Kt>?W6 zv8R#Rw}6m*YN)IkwFZN=h)sbudrZa6FVnCkRabq0`-=nhYx7ImMU5S5o)z^i5s>(Q zkKtN@qL#amy{c`0Ec!dpm?k8Y?d#V*r1|oiRDMI11-YN7^yx*3RAU_y`0Ngbaug7L-98lA%oTxpcln1d%c+h&(G1y;Bj zdMyH(W%Ew{N*wx5JEIA@de`9c^$kUj3Z-1R3&T5(eKI=nO|1B^=hGbo#UKYaNP#c< z(#oZgdO!y`h_!|R30Yxe(+yo?NWI50AytqiYYnk%otQw#6h6k!On+ziKk2iUrm)eP zRR8T@p+h^4z#uMnmx_cN=E?GOy&}5|MhY?X2ZCoqLL3s15GH0Q{fcb(wD&Tas^6Jk zF+|AZB>l;NEUXzBOb`mCfnegqMOLn)w!DXCYsm#-GNSN*WTeigs9LLXOxHa%o~>Yh ztM-lM(C9jGVsQ)0MeJPmCQ}$DNUq&bIyEy4r=S*5u3b?cI zSam-Jj?>gt)gDqEIq-+8g#%E0T728sc`Okk)CO)S6v4KY#m=~~N-lcTIz33NzJAstnn+sR> zb+-2@wb*(0YGzEWB_k@cw#XJu)PWU^HH9&B@=ZBkp2cS|+zO|%LW6JNZ`z6IWK9tF&>7|0l<8;%=i^4JXLgy7;b+#f<~nUL-Y+mY_>*p@FM1 z{E~8q*4h1Lpcw0o!@Z_#GB7sey*Y>ql%8>Ba4V8Bf);p$H&ZZ8ibzeBoN*s(DFts# z{cuMDpvauRhBfI4=qH^VzQj|#QC(UY4maRVe@oQy@4^f~WPD1d{G&US>*zsGYT8;c zzLi%Rjm^=fzb;ZS$XPUT1a}bZ=Of_*7@Z%xbh}(we z8`zi-KqH0A7WDiI;lMQm2LRj|z+L(7qXN1{~0g&dA8Y0=2R$o-R2|X%Z&%Uc6{LahC>?RcuW8D_ZA{X9%)W zES7Jm_bpD17hpmZ}>jY0F_Autbob-8!&YKA|I8L2*5rzq6 zSJ^AG>qAVu-jsC`DV|t{qsgG^lfDw#XN2;I8zfvHP6PBfb{1xTUiK@rl62eX-N+B^ z9%o~V5O3QOocg;g7?M4nhd9VG+wB^|>Pr@lN=@QCYNWNuubHuy>RF50jCJgj~B zR<%Jx8u73WYIfP~kk}8F=X*l9``2$RL-VFXzFg;m)-)bG1?FnzjEh1dIn)hj`6#3V|s zsD8n_c*3L6+*LUEea~TGV0=V?)a)koFQv$}VJvoX-R+n3f0UGh z31;-K483S~Znt_2;HQy~SCnER zXx3>yE5~UJ;JPi>Spu*nEFFi7#oaxX|0zqy(!={ij1FFER+qyyNC?r?PV{8 zh|4)Ft=UIeVAYN!L>Lrrgk3^b+&Z>e)WzdnI^0P(usw&+>0N$&fB$DssckcXXmH5x zuygKy3`cK_H8N|tLAH1TR)FV!>0D#yRn9~o5d^n53&lj#eea)3e&H~AZ z<_=>MOOcJvx?6%O2xFg|3FJ7g){w(!-hA#Nj4eQmG=d5fZ@)hjpiA=y+-1fx{*`r_ z9aA)!S(lQU)&pGgRcnz+={*HJ_wn7Ol{69Eb{y)=r%@QOKj=_a>FA+W86ra3K2r zX>u%H)ea0j7IEWI!m0?Z0VA>R*XkU9?*&ons3(r7lTss?hBm(H+u_5YIsAeNKi?e> z!|wv=dFq^CpAGqs02k)D(|Y$)lI&izhe4#8%V>|ZY_G+y=#zkx0OGUBZ%opm`ld3v z5;o22U;7Z^%>uQeFzCn?%;m;6xC@;ib3@7lu1ABqqip+P%69T2);n$p+>fhiJtZkY zdN=W8#;IwS`%^4s04cfJJelOT>5N-fx-JZ=%S*OZeOBD5*)?*odn-f3+cY+njCSf^ z!$x}M`mxxv_*w4mU&b9ZD0~@sv8>fn7)xNh~Q#@ z^1g%?uxNG0Pk$yoG#fO_tSCpm3^+Fle@`+g=&b4?4|)9v@}w}DNji;QCm$&5g|j;#(3K&T+q+-W`-(;sCh z220bMrwfu=@(HJZBvYucLqSrh_pPGx1nj|l4(yP}JD#Uww5)c&Q}ey@9>7kV@A8z8 zAf`94i7dhFu%+=}xPOW~%2!PVuf-od!&fqR9ZB7c3TP6q%Ytl=NT=xE`AtvOVo(mB zeQ96mV+Sh10_6F^9An2nKk>n*+U@PEt?-Uk=RP)kUbuCwCyCcI^mOz2u`00Mdu zFxVbdaO*kx1fl+LWB&=)Xl%j|;Dh|kie2;V(q0YAfVy8=1&+Ks8Zl=G#QS?l5T4Zp zKs|kdERQ-Zo|G15=&>ER%HJJTjcnOc*zM3_OO#MBg+mJ1>yweWp2j2;ipv9VbAl#p zMqw}FO=)Y@4~T6Zqmt+fXyb&viV|Ivo6ZlM2q4&7MA{Q;Z*s1@z%u0xI|KBK^;n-s zX!FJ3+&X&vIQcnQBC0GWDyCX7Uhf6jy5% z1j#|v$3Cd8BA0)nG$B`rEOyzfz0VwVUF3djt~ffJ1sIC~e)Fdx0GRN6DGpWa#$;W1 zODdxU%sdr1p$9=Hcn-U8A3jx2R+m{WPt7tkxZ|GD#UP#T>$w@Hi~Gams8CaYsW*W3lfKPAp+}me z={FdNMOnGdz=-d7f&S@%d$getGo&PWd!v>qiaS(uLd2(DHi0RwNUl$R<#hpnI#XZt});4oOD+3&(# zz2U{S0$Z`B#N$`4_%1z${?m1puu} zhIH4Qvn;;;e-723e_%+XZ9L$+_H3~vt-lDHYK(n;@L{jbM`*t3E#bhb?fgI-Kh0^R zTiw=GZqFXYD3(zoADwq1l(!j_a%f0il{%=+WcwwWp&DXSJ3f=q{lf&$%t__rtA8)u zc)C=5t)EdCs=2^Osmvgr(cH1x%X(qJ_})a+sv(YV4;ejJp@UG;OP8OtkRUrLJW&TW zb?CAeQKbNQ+?UZ*hd(ksyRx7Sb)rgB$)7LTY9q27^n za%e9Ky~^G$SNv>L0$BA3(f{eZ#>UjC7BWOoW{tnFR{?&Zt8dugJ-o59d5UM5-pju6%zzI>#t=`q zYr3ayds3ikV;_8x5;-NwMw5&J-cH+44CqQFkR)i~e)m@}{k3c{h1GHrIkn8;Q@s9U zpD(Z-pRVR)t=jD*r+YZAV55z6d+^y$G?Q+KZ{<2~<=lC74|&W@a(2#t?H z4mfRT2{2XRME1&>ci4ESl&U-AJO!$!0~E3cnA!5(x?GT^jFF4p}?I^AB^-)6)(w+jKS)s005|Q;?ezXdJbioHVhy-fE5Xtbrv0H&5QUW*Daq-%TFww)kM6dHVU|=uQX=URT;}46RbG?p-s)Kn`Uv>458s| z%z|^CdT0Bo*MrxG&!1;|lr$a!D?~-`5>MF(88UusqDYql3bS7l_{hZQ8Jghp^B6a3 ziB#-T=c$8c6IO5i79{ad6u+y(ZxsL@R0EEcKJ^UoWPU$XmYa<=T$p+1v1rYHPa+fb zU4KSysJiH)^`3liSb~S1$~du@BBZ(V`iKAyeQj<5gC|{{mRX6P9?D?kRSFO|UX?bqdjl!uAYQ?5)Qi z64sm2FIrLAX@mM6_fKz+R``t%aMZgn4wXe}h$ZG9P&s|Z8B1J8n17VqX?5uq<^fpt z{+oGZb~8ANAy2%~n^)Mq({&xW)#RT&GYJmxh}~2_|Nj1!VwqT`|5>$@{yRI&Dp$o? z?e76?uG{R)HUUS4))WX+fRgCgztm3E)fL*4FPtx1Qmz`VLn?)w97{LcP2f5~q&kpu z4KwSXl-~dg&xj|2wytF?kl)rFu`qMV*~%+K>3&d>g*<;H-LuUkmGm)TNuNZLf~DrD zk|E{G(8qx5F_?_K$Yu>|#qK5n~gj#hDywRZZ?6tc&N`7!6$3*DL278Cn=DUYbR)feH%a z%3oMS=ef~OU*cF&ewsr>A91clHEscFCf75mWxQxJcY?EK_wPC+o*t20QT+9wyT>pHM+q0 zu7&;Mm-iBJaeKt#q=1q755AF@Br~d-XJK8vO<_)%Wh$rsyqKQ<<-vSa%eJ44K0_MO zf%_eD%3k9n)LeMPkPnL>cj=Ku^woG~-*F(s0}eUjp*+9uEVH2oQk7LqS5Q42Nb);s zKH8m0$bVzf!f6pVJB#E4a&LM*1P>N0_?b9l6DNA zFI%?;f?r2|x|2`zkldNGbC+ovj|T|aL2>YM4KrG>X4sE4VymZ16BTDg+qP$62egT4AP?OK=JNu*nVw{o>`sC>3f64SSm2)Z+@HQ|HoSyd2tWyFo*UBMl*uWpGluK=O@2D*szC4cLR!k{=z-N}0qG13KFx7gI!b&r*P!$`@!=eie(SkSEiY82hrqneyB_V_UveDbAkbdUD zfqUu@y{HVmkleFgzS;V;_(#C6De>7~Nk$>C&g>A#d746b#)8fPC$(uO;!K#7zuobP zNCoTL_Igi1pW_Nn^s>?s4G+TmEz_Jt0{gDk(y7 zV6($sO62@?YMfhkL?yUcK8}4?`;)9(FVlm;MN%MAjVXjs^0odSG^`kU)A0P(s0uB^ zCV;SGj#B6#hlIhqL3OI;-(QM|Opt4nu8wCju41N}L@%LfPB|O|b<+aRSK}%_F+~;d zft5qqY{?)|SFQ#B?6}az#Kl zrhTHo?bq1kg}VMR>9YFgK0Hy#uPY}Jvb#NDprhZWXaAxwsZD%RMVNVTb+pvYikDbn z`B;iDu*cdN{hx<3VxSznzLyK5!vG?G2cWEznC{S-mTO?vfbkkE7>h)H#HwtFpg#s1PnU*-gd1w3HpKdqBC_vv6C0-}*s zIMD|56-cg`RDaBN`&H5mgRsr`x@a!YpEULB)<_UZjYHl~JljzVOMq{fp6bE45`|%D z?W0pj*B?grTTEW<>X=;rFb+^d4SE#)}f^5PCm7KO@nyd^&WRnbq^27w)dpzR4^X zz%<-4zVyYVX*|f79Vn*bk&XrFR;K5<{ht4I$m4k zc))c6vK8k2!pFI|F=WmT?-mim0%uvU{D`#Vv8wQ3orU`^+=Sa9HuY#W!jAG7CgY10 z%?z<1!z;;es-_a~8x3-YNC#8q%Rtst;~r8vk>=s(tQs&43b4_mC&V{)R%zG$ZBH_LH4`3uZ$m*kZsHzI$({j?Xud*Cx9H zk76qyWPAs1VHUa-XJg&Z_P)7s`S!iZ*|xEawP`zORhCC((7w`59p?cX^8y|B+39K( z>Y|xhvj{dsT9)OjPI0sRfy~b_%gBK7bFtx(azk9GngfAnHBJJ#P8a!N<=dF43d9Zy zE~R=d&Ie2I{eMPflTid!Z;?Bo=g_i4prK@JHI05bW3=;Lcwpy)j zO57l{KnPIK_mXwI-Y7c5Vyt=m8?08(zbCG3$JRCL@E@mt{6|J04V{Uz0dSf-Bnj$; z%WWMZm&VjlWR) z#9nCb7bv!=*3X^2nY$y(bh<|K;BwV60G+o!&*nO$dG0Y^a^uV%#vnEn@l&Gz3l@%3 zpX5L>D^5{9A6(4-ejaDf)>h8~D)|kC-~amX{gJ4o?d7!jEz4tx5b&|F8Q@N{3gypY z{0v?s>R`A3s^M|hj~Q8$=&chQfS#Rg2CHfoO?GIXmvFIgb*=2xoZ83phN22lWj0JC z%?u9}HJvNU*Y}cW;^a410CF_GFzv8%{ykB3^gEky{QI+%@KS}|L@)0b|0f~O6$3rk z!B<3jE2@<0EA~<6Z#can!@m)9dud8>v$!gPZ#wiq{hf2?EFM~5nv(CF?olG`e5{`s zLm2fy5#nK%IKpcWZi8NZxQC>UefK@IbyvtDHK_8nY<`eFdnh*?=>#u2QlgR&@X0Lb zD76F}^t3PW+y9i{f`LyVMEi=yDSec_(WKM3^e3{jae!=KyY%{@JXMJ-5Xlc3C!P2q zoG$XAnn@CsVE;9~Ei~_dl^Jcv3{uvOMztpN3;j1|EFRj=5yOXm?^yOdZ6FDC)2Hyl zm0yo_Q(%d?x*{IsxTySY&w0LCe|OEXxo{RykL~=dPY?Yue4A{3cYoi<76x6bgFnSg zVxxa!=*uC##@WX-1_9-*6pEG~+MQJ2@ehc-??N53kBON+KCkC7K)YL5Y~3!lC-ry%E#yv#*V-&sl++k zDWwfi56blHIsx4(m5o0N(;f^J38}~e!Qy@M6?&`5t5#%?CSAA*Kqlh;dsy4e+a!Ry z2zTer2ofJi5ib7D7p)HCE?+zmG?EE^J5qTh9;rIvEH`BW!0yk~ZW0d_E?~>Iu)MV0 z*r-4)Aox{66zNxp@a(MBmViHs)#q%TLxTZ@*yGXF5c~o~HZza?qTfhc>otI3D@+#R#) zo_e@9>+e>o!87|lAD@GVZI);b0IaMPEILKmq`0kZFm@F6zm`amtkSTZ7z%4dROpRx z8q>lI2R;zn7B?JNP;V_?39w&gH6SO?;H5;bmzO?{NMi#j4;B&fdm=WGt`yaU90tTx z>0w!?OJ1UgG|Ci!%#Oc<(o+s+sZgPSvx4YRA%RfKwZZl@{2!_0Ud`ILS@Px+b3{DY zb=sw7p7^AJsJ8+PVqOY1qR*4Esu!dr`T>7hyYpTA$jbGIgfD?k*;&ZZw44-tzVEJg zG$lz7DX3B8?N|ujF%C3|BJg09EeV_+W+c_#V~q+VwAq)yP-G%7+y>6q^;VDXQ>S@D z|MFT%tHFFe@wEm|0eL-Lg5@-LHtMczm-)$~C(*0S)IvHXxErBSu7PI42r8>^Le;ha zq1S%rkhkAQXQu|$OIu-|zIw)z`f>c|awB}ko}YbIS~HJr4!14_A6to>85I0uyE{XL zdt)DbfU`83N+r;Qi(xxi3`GYQo7rg!3PXT~0aHZJ4rs-56=)r$7!1h&;TkG&2|mZm zGg2_m=p*;z4=C#Mf1FA4dc}EkyhuxX`|p}DUYZR&pNbLgKVeob5?DWC%zU(x%wQ_jkQ)@EiEdOjNKi$ep5?Ck%Me!q)lH_W_Z&R<$s8^8K} znhe6rV#3iq$h*wAWl(c8%SuBxqIrEzS%adywo($~b7Tx}6u-`(=Kjix5 zUEkq9&hs;Keee7SpTFjR;Plh%pZ!nRKafAy5BlD` z__uyL|0l4I^ndXG`oGnC+5h9`OZab5PMihPiC-qVrZ@!IrGG@JqjlIp#O%wKYOJpsO4>-w1A%?KGz{mTZE2q~ZJ zcR}cNLT?V17K{XJHu=z!5udPk3B)~w>7wu4J&8!aKp-pjvX1U9R6|lUc3Wa8$twzJ z?xM|;_uTTa>5#1tbO##R$nFKKBoDpnTNF=gv(*;fKOY}9n@M|3$-f-pLa#`j9U5zm z>8-K)iDO`ZruA{#T$=ZP0>qO1%8UMzLf680U)8q<4ww-iX+pnsJyrEs8afs@zo#2* zZ4N@_n6e{X5J!4RV=h?m8jNOKS>JIt1DG+rsZUu&lUPu*Sb_nyRu(vw3tMy^mHaf| z|21i0nSo{2-TnXHYLJgM!S|(b>Z8|82N91lsf)TKabLgGPO^4?R!1oFS?n!~n`&k1 zzb7cz$OwxKhkolWTF1dCrIHE@pfssD$4@@P8xMd|GnFe`aqv;$jZ*Bi3jYakggDZw zdX#wq{x&vATkRXDRTCT~2`R5JQBIsMk%@@dDYiUmh2*4hO^XGwj*GQwrI;zQ{?pv} zx_tJi)MCIk_o3W2fT*pgf8-G#O09Eo7ZkVhMqWbpx9t=t?N|uibi;QQsT;HA1|DkN zW&-jhOh1Dgi>L4hwJr~u;{{YUZ@3@ovk7SmXcVheyxy3VWKUY}{U|dl2l6*uwh^e; z(!4FC*<@KZ+#0nrkMP`zw>z^axYEtYh~0jMZs+zcXZ4|TGY_a?q8&EAG`AAvUH$_JHDkMxvfnWORb#=8JR7eS6uYxl z$z6nle>IU*xJWO*2g}Tg5|On8tb}Zf+L||$zL2nO5U27NvN%YleS9y)ws?| z^x~%UiI9UuRg~ed_5S~y*v6c|J99wJx%nG(PbAPUHZQ8hp}~ak3UT}s<4vEQz63BbC5v*!4Gjf4wt zbL^E5#Q_49sVkQ6FfBVHAI<2!LeHRDj>td5qiDfT&D|nIOtqWu=U;AQ>K7@Nj-+h0 zLd3n(&fciA3qtTb(gtLR2DL5#Yq1*gbj{PMLAT7|1F@7}9p^?jSeumk1qsS(_uVe+ zH=^p@X!wRI?uVXg9DdD+?yx2Eknj{mzg7lD9t770@Nu3`ri#A}m%MK$@yK6rFVD#8 zsoLLTkMAu-={?m@lgy|WU1+pLu|vJG7RQanY6i(kyzlQ03_l?>_(CF*`QNLp`)L7g zx}d3L1%^|<0=K&8{3^RGKWfHXPsk`%JEqjyHm_4C2C^#w<}p|JNp=mWC(w0h&=h_p z6Js>nYdU`8IN&O!?~kW+l=9tPo765H&|vBIqSBbdaH_d@16_YF$gSx9Uaf&PuVlhvs*4o{REAhftz(pW<&$q z8=(i;%;BI`IZqd+h|7!qibHvfKWGx8+XPkUJ>CjiH~wtH?ciMY<7bZ!v&moo^EM3B zJLfmYB@JcCI!gfOE=C8s48Cm1Qg zweZwQJ~;8y`&Fooy}wHeZ}hzB`an%UiI#DL{n~BOVfr3_#4&79n|HVZN35|m_DG&C zQW8v_Hk6YMh_sG942zV~k@|>b8comt{cbVDmgNLh~suAJS4Nn3JvgtA?;|{I*=C8y zuFy@^E%*u)7NeC?dZ+4D!y1X>rYVVAbMluX+9>bvzJSitLJ*AaJq2_hH6wE(XD_P% zRkrp-0Y4gq{u_U=ig#u^ic_hLW`p}`H~Z}o)L*25h@QsUoaEdSH}GLE^<0u%MMK2~ zD!>xU^ZbJ7jlGFe-qyB`9@|_-LM9*07%Zco?F+$8m&1j*%r0;Ssl_4S7TK+ljeE}) zKQ&tB_g1_G6-RaP`-!ACTp?uSHZmuDC@-YRpn(+xg^G6B=?32n>iFF`WlnPlti^a3 z18)~%9g}lrzSCNl>QHx*5TIu&`7=P+(}9-ebohTmR~(a|wbFlkRQe-W-dd3FpaA~< z@=|<9AP7xa`x;asrIWY*`mNW}cxkc9ca*8Hr4#Kg;V`EkfgmPaA5H25Gpzq9sSGc!qKWHH)Z_F0*+0tdM z$C0<`rXR_rQdA|orNRI)WZDa$kA}B?5v<4;*eD9-r6PTLV*e^E& z-MNd3iny-r5aGt)znv@EMqzz=H;OZ&?v9vN3&av~7T9s1k~vjlS5VO{y&9!wIo}gt zaS^A-^dCvE*?Gp_t0F$1o!0-T@` z@_?gAQoA@ro|~UqmU%UVEt%X@-q=zK@b5z(^AoBV$bpt;nRxJ5Z(bS?Fvl0f31!sn zxJ5z!809%c*lI#i7T?EzNJx5P_yJq~jk8abq795NSkorI+q=FC~eAQ{sJ zpBKIufkS=%>!D7D4*)e&-0%NM#50AB^nqKt!L(DW<%|_0-ELKPP=V8g*SKQp_uKJ@6?jZ8bd8OvWIU5ula8B_rI*Xu+fBe|K)qPPWl!kWgi z*B?w2+Sa9w>@}Gm1f7}Si@P@Xb^8cB;b$TB+X-+&Fze*RZI&0cs_oq~8zT_8-qw?;_R@?ZP(=k2RQLIuHV$#?xQM=#g-mUG^3 z{VA#6U=h8#a%z$$3d}uyklEZH`Lj#vhNF{AvQ{-#^#jtl#S{lxPV5x}HEnrfguMJ* z^l(VLt0CycULd&wEQpa{#|hju*q{cB9{3ES21tXbY_*yuU8eT8T5)-Tl{dNe2zHeB zfsTQ6_Kv5?HQp#W%xN>cB{9n@Y=L)?01mP|&L{$9)u3-Y{>4zJzK}2E24@me*giYc z?jW@)`KQVCXhLtoyS!Kvl$D@FiN_vzHk(?D zvNDKHX12QOo0jNZdg1Q~&M@fcH3rv7ubHAYY3JpfGBoF`FMs5KO%9bc7=AW8%XUF3 zfD<54f8ZPDlO9veo8Ktk3vjggPj;2N-%o)i-r~$_B<~e#vTMY&M>iahfh&%UD`4X* zj4zVI1?6ZEag?eh-Z|#8;7zKfIFL$rQ|P-sJ`9qR57W5%xp9>ydh;qE#dO=DrxXWS zyvt>Jai*(r?E%;|b^X5{H^c=LhjR+1ubuXF*aMF6M!xo_fG<#o?p1-B!L>59`lI{s z27Rvm2vvt>;3Ar@Yfg@wxB?rb|ID;w{zt@A@=nm=6blIHJAhp6D@Lt85m3KIj=l!C z9|?VjJetTq|N9|A#KoBN!v0i>|8EihqWp?9Xul(alKnmc-4d&{n*JN%-I6(Ke!STeWyta6t1&n);TOj>y5TBfQ_}Uob>sX1|fl7t~n=ZieKJykU!f($1$f2i08{6 z#Ka3!z1GS#I+PZ^*ZB-i#wEWJg>?_)3#VNYudXJb(Y28z?nL>}QFqMowmwuc6*r>| zFXyDRkgFERui?0)IC0wcJ(}6T5#ouF9SWY=INO1R7jXQ@s%t+ef+mF zTFCj@(+V!I#?$Au!^Evc?_rlkGNJKw&~MVDNBE)N>6?7wZ{Oa9xOm*fDf8IwFlLeX zqNP(ZW|FWFlIHm&q>>=>^tab?@!1bQGwxODG*OO8HyH;-?iIy3?pAEn_$%42UBMMN z!W)quQSzsn@mpeD92r}wQJ6f*ixN?C&87a}_>T+bZSyxB%R{^A7N0f-TAY4k`A;TgG3*zzF*ve0qT@94)q^b~ z-C=3=exb_QIIk zSp8Apc4YsZPqKi{DIKeDd!Ij^7C57}Rh?jA_IwNrNnp3s6gJjV2}Z;gJ;j$x0D|6~ z0c|9bH3ALbZTh}MnSJ0C7;5kSNwVlIrS05?^Vic0Fco!V?(SRte}lQBW#9_dBP)&d zk_?9S(x1oE*uvm%MagghgiLf|Y<;vmfp_`La+CRQN`&LNhQD6TJi7H-WBVz5$r<*c z<}DG+Nc*y%h?ePo!Lr<^R`#bCpJ-v#eTG9jD5x|5x97B36(+jr9Ee$e{avGTD2;9& zX`3q&d0VMa->1cUN2ic%d6Jb~m7uKs>B9s9P#btIjEyhhu$(83 zzu3bc*rKKslh)plRgM=w6mQ|H-mO;1&P#k3mwtd#J1%i2kpM{8Qki=fO2do=xzcB_l)4@pb#zGzIF-%ZgLC%Ywh_dg7ZB>UVF zcX5JZ{HbAOkp-b|!zNOj&Q6)$RR#41&AF#S*;Epvg&-$67=v0+7edm>l`Buwu zHU6f-pwKlaqF3S#{f6{AY-d5&8QVjL^Na^oH@0!)m^2%HMnu*PToi=89XCw!wcn%d zNT{fyaZ2-bwrT@WhDw#&^Q9X3?DuGuC2w&*@)5lz1N>CovUmqaB`Nau)sb}4w@3G? z?fqfed(b*8(h!J4SQ|K;L>H0z|bQhL~80nq9Y84qXWl{aeXS<>Wd2}?!Yx+#BmHOX?TR5-+>Lkz& zZ}Oj}l;%L~WUB9BnrgGWo~KdO$n`6ZL2VZuSA*7yP(+ffwg=?Mq|{4gW%itW7sy}- z^^&%SS0l^R?e%<$olfq8ZmSS%l&*A~!z~`x!P&2z{zA=qN)aodbSPYb_$tu2!WLpR1&p?6TQG-QiYxJk}p0f@(Mha>^QguSPQ# z`(Twp76`~fBbzB!0*W|#bwQ@)_mTaEvLfuIG)G{8=s&2Y@z9HIBRs008&vgerqB~% z&;wmeMJlv=@-S|Ycj51^klyZ`<19oAq+per^#6&Hj{)E~?k^=<^raXo0iFp!Ap zXTrHd&WcoLTqB|1Vh3NgZRxC~oY$I@DS2P_CZ<@T`t%%z@dOe)uBnQiK)^mmcy9be z2xIr^OX+l@huM0|X<8-P0DsTZ{T~Ba=2T7VJIn~(+OueYc~1&o88z-EIb`0C^BRPY zK+0Xhqzc5>y8le@a;Y5BZ>6VUChx9Ek9T~WTup2$YxKS^6;tZ~SSfci-_f?^%}Sp_gr5{|TSi2Fe4Ay_bzb!R0J8nSP*47g%MA)EtSPale35Kb9uVT!Pz1 z;mR&#TbP<%h|<3>hH<{WAx3VcRxIm@5G_Jlc7D*JK5_+o@t#EhX7m zZE(%v0k6=ew-mM9K;SBZ+2XE6Bfs6{#cc+<^xxXWCCCd~C0ZUlQ{;ucjUSCN=o(MI z@NUb~`Tl4PS;V~q+ubEKg;*^1vfR#kdWaj&0GLcJ1`(;$@*SzQ@H=WTik4J~f$;R> zKI{D8r!}s9^YWq~F#kWlzx%czmF~A{gV)^jEY@8Xlc=W(+u%u*bJ~YAU?|Ghyd`mu zTyCrmA2>b@%YN?{E;Ok&pQzdNJAVB|bWx7KDRy$jRmc&VUDwOWbZ&kaqt!47pjDDi ze-nrTI$FUJ31Ic~JOnqf2YY=mqRt1ieS~xaLVat+WuS(=&dL!piyN$@O89d9j%s_V zg`MmiEb|E-XDgw^IMZT^iO&Q^|7z{-m z>rkBuwYTOqq*hqOASLpL&!DkHoA|!t&K1Tn;9w&Bxq;uD(cDe zB4Lbl8J|YpUka3lE@sbNHCvoi+!@7q&nBg4o{Va{Xh!PFnC8LzIzZVs+-jGPLKF~Y zwWmSwExjM6rnA^@rcnz5gC8$1{(R*oe^sF;QO1?MUU9<_e^Kd3{}9p)e##l&Ef;nF z@XI>EVTMLmgn%T=n81dV{%i5#rn#{ZXgy!=H%>chz{H7nv4Myh7^R9y zx{|{v{yNT2g9b~HTh#asi4Lj+@;`IKhf+ z6U9)U^09!LS;mdJZ(5mn+K-$2UR9@ITCGGh!O>s&09f~ZA_TrHx@7wXuCa)N;}EUO zNj`zCizu5q$X--) zFA5rx2kioQYZgvtNQZw>)69hx2Q1!0R@?{7Ao-Eq9hC~FRY&m@XMSQ-tufBo%WJLD zFtKh7N_qkt=w2!!qb@d~DkWRVr+v|%bz2isIOg-eM&j=$^u}z5(qYUD&A^qst={J_ zcAR}?r>pL?IA8G{@G?^yn6-SlV^ThJi`ud8l#O8-&C5FPDgDdr6s!L zT~PN0It23%d1(Q%T??9QizZaSdQyGLDirqOX~L5JB8Wu94I-r*r;6nk0f4q#wPUrY zK*E&qJdj^3c}yje#C$aSzui~TrFnLt0Se_?8J;V(tLlJ>5m0C!7Mp5(Ghw3~i#&Z{ z5__c|M(+6>HWd`sBJ=(X;ZPx37a)@9-!7cPFJf(IdJk4(sSvd>@%2 zp)MyrM@#JHRSz7Gk(6LQ!Ia=MpEPCuO1T#UM8ZcQoZR*Ek7q)%;l6ga5Wxd@gNeam ze0QFG=@OpX-O(H!UZt@7?`8dhC@o0mWk?-t?C0f_xS%V2MfXvFtxl4r=&2`kJzd&X(;-{AlFmrmn9 zwzqo9_Ry|W$5*W|RJBeh0YBoRh1awK(%MNfqIp09MA}uL3w1xM>mNe3pEELn?)r$I zvEkWxUUGURhzBs;Y{SXHPx~?_COahht@sx=cen$3=cNA3ZL&tP)hqP?hYG3V{_E!Q z#tyA7Jm(l$rjfFSe3!%ZBRsDE=p>k`&9Oq*;D~0q;Z&*#^~b3#EID{Y;RqyI&GW>$ zm(;|>&P?0I7$?ji06amuzpaUZyN5wUCF0?OL|vs1ich_jwsM)Jq#iNufgP${~H?IYcDm9#1y`S(>l~6Cyj|ZCiEUqq;y2Ur89i4xk_e zcX-q!D|3u$SciZM-PI}6=1|=e!XXLVSc)FYx?`su0k9!Dv#L4&4op5QptU>V_`H+t z&+H?nY3<$0?v69&@g7iUq}?3f`ON=J3S3rS+2%mIQ79uKPs0Ji3v#Rjej~c4op+(E zc{BbgB97l?9Wg@kJBPgiqyE;{>de$YNFi}|Z^0@Mg8;62{z&k28pS0Gu}OQ@T$*`* z^WHypz+Z`$%eto`nsqI>Pn@ z#;cfvV-;8hxWe02aZb#RbFrU39LhsHMSO*lbpF}8Sl(FNsTysgY{*LTImR%p`=jo& z-jH39<`>n1mm&bE2~H@{-KQ7M3}HcL|%BC6TcP*O2uLDHjxHCOW^jH5%ma*pEUOlojltp zkV#DTT0k3clMp{v49#pLF$Y9vYiqkxcE4(Z6^s0| zl>rMAM&LS&A*{Ptvu0pqOC_NXlc9rb0WgOT%Bj3w`|8ePo+#%>R>i#VoXI6S2xLTN zsP~^dX(bV36k}R6jt)s_pNN>@O>Xg413{In8?tDXsy(Cz8~221Cc9N;C;qNZFH1CN z;dq|_ECLEB;PvXKfxc#OY@4#7VQ>s!@>Y7k535bz2FW5BpP})*5#st};vMEYE^28p zu1rC+!0By&OZJe3$;P?ypN*AD#{240&##k+8@+lO0tVifaO+v0lm(yLr8J9M=AL+n z<7*Retc*apqwW@)>ZQjxbF4Q?alh9^k?V!i`Zs=kZ4D#j9DC}dUX$K8&suxG{v;X| z;A~*LFKXt_mHf{>yJNSMRE|Y!K85R}u5KQS0dCDHdbjq1+BD;1+uO5a5Uo8L32{`H zXpCnH1fLf|2h7j-g zUk2IwoZdQFCKk1l4RzyfN8tZ&$eRkVfU>x8E z@~k2~5#D7D>`tq8Ki@Ad!?i=UUw4>r6DQ0uq1n|ph0(iK9o3C)Nq6D~F(aaH;-+cy zU;hS>?f$j@{jzr!UkPU^2R|K6lsl)Dl^YzdHzUZ#m(Dq(h*A6G?5|B5pW)1Q|I?J} z88My!OJE7)v{B2Oe z6fPD$L(v3+>r;D#J~#a(?l3m`(~1EADu$@Z`1cP00!r9g;Ha`>|7|(v5>-TiJH)+h z`zfmRW=+)-YX^Jwp)Uvsg%U8L9(FSlk^P3U_lyiH zW)}l)f8r}wC-4_O!yE0g@43mwokU?Ynf{-Zc11oJIfEjwWZ$i0lZ{`7L4W6M{Y20;(sq9YLz+$v?!vYI2nK* z!hrgjUAlyP%mYoH)rJQA+Tu+IkuW0x%|Rt}Z6goj;I0{LS01zo%Wzx02%|^3M5SNd zgcPrbth54jS|%a&1D(w`T7v1>gB}AUJpC~o6?-K$CM=WxGEI7)Dw#2DBqA)ZVMi}AxG+K4@yF}ZBP-*LEb8`{j+qd6=gEcDaCfk z8I>X3iv*4yshDB5QFy0myf+IZx5CIWCn#!=Z zK%%k9B+`b2`SA?T9{)hoj|RPbw|I$7$r`+s!M4&ALi+b{Dyz!;EiG9ovSCLlsmGrP z6(`TSEk9xqq1LhKh!7EK`pg6XjT(WWM-amx8%@_N?>oXHu$&Rf(Pl+ZcUJad@pj3R z6-ifMzOR?M2`N>ynS(Rq5IV^Cx(co4TpBSeFVbjj#=F6i zkQ2ZT34wf<&)x|wm4*?PsViSzt+c_hYWIBDSqHFdYSnN-t=(?T<3 zbp@9;uE|q%80NlL$quq#3hV`i8X_BA$egfu!Ep5D7xbftE5$wx$%aG)@%N z99DzyWIu{Dp#8o&XEvV~@!aJF=*~*WnUGR_TK4;23$y$Ci`hBCHdyW63>)mISBUdh7}G$8r^{yMq_D zxh4+23VkZ{8c(bXZsjE@JQ@)i>eA0~p2hyn#Wp9zxgu8%1%lh zW6JUNlH+4oA7GpV9RN%|onAF48(2_#AEOs9e0XUsIL*4wbr>Mi{0k_n6vqdFO-}1z z{$Zfv4vVb%CQ8e>P`eFa6~aW&+qT@=y&jbK4w)&r0anbZae_M5W^FheJ^175Ut&J$ zqmL1!$(wDc8Z-Pqw(P(@jYZk`%niOx9w(N_u@MLecINf+C>7r2DSmX1QaM9N(lTO> z(@JE)a6w|%!yy$$)E~!@Idx@d^@x&&D{X)eB-M?tx161obgAFV?#iuShomviQ1<>S2*bC z+1ZB~P?+Dnz2dU3J2F3(AAXy>nJS7Oql!m&6Gn&-_mkTBGUM07N|IG%6%2FxWOL%m zD;@qjG%i?=3=!a+AD-1HqG0L~^cVuOxQm_7UY=0)2HUy`oOU|M#BmI3&SD0BU@flG zsqUeafeCe@Wvgd@6mn)kb0bNR3C`@ozaKfAuJMoF2gK1Ve-5gYbWU~?s(oB0phQ~f z-3R+j3MAL-v$pd*4@5l#wnlKFC(>aHbyX3T^CA9@1e;B>`0L&%F9jY0UKN(6krl{3 zd$n{EdPce>!ACg$H7>7oFzur=^;D?(v_(;20bnJK`b2yjl7fb<{J|rVPyhfBu;G}> zfnC;Z09tq_fxB$I_oknG@%D7;l2{9oR&_=)WSD6u7a5kKaR5wY6|NfPgTh*opGj!) zlsq~%P@RAjy>P}MxD{eb&0HPVUOiA5BA&jc*0|l&06+aH4HdUUV(t$DdP}l-I!+pL zhKu9jsf>AA7_x*}UQ+gsX1c~?|4|hFh6C#34h@*c-#=${qS|NWyX@C1>)(YmUw)%p zgv)NUpl@DEA}fI8p%`J))b}2FgdI|0GwnPfKA+zE^CX|MB4g`U%|Wl7r9%%q+PTg3 zp?i#^#P${J1}&xT2neD8{6t=PHlH>dp8GPv;krQQlt8gKUK~H&bWyplXXJgo%JaGF zjX5JB$2z@r8CS%t(hIKlS7cw3tVZ}QCQ-~%tEnkDmmcK+wE+}(G>^#7O1S+V{(T_h;?JWuFChhDL-FY!H7oBV^Rrr(cB> zu0Wh=RYmO9KjiGp+UM`bFe|M?4gU-1FjY&qI)K+C@7j?2Ww1ZGh_1hB=8&uSUGhNT zFPO^0k<@lsR2Mpb+YVKBr@O$vVEsz^8Tzv|7e-&fkvB*kR9RNMFvXH&E{_2To|KS&SZAI#t)J-R9|SBwnXy~~x8ArvP1BReeZ?zU){a42(25`YXN!P-C# zWyRQS|A&5;ofwxyp;<&r6M%2HITr2_B7OMSLDN&P5vt2r22zDSRuGY*Yx6v8s2r16 za4^UM4i^E;s5OtFz&Vr~>typzl{JpJDC_C1${>~~*2ioqp zkopN44eiFlojQfKXbTVmDYAsSPKKCDEO<)0qnUWXaL#7spbXq0m&iisreTihr$F~@ zuxHX}p7^Oij+6hM%WD{kz~$;WxBrtrJHUq+X?bUeYr6Fj~_I>`ptUqz@i#HVrK3U z+jdY=;MdX`RrGs;u!(E1sHKeDr5Hotvz<|JR=aeNcjheUMWB#;r9{(+XCVpJO(6EH zJZHnj*4baRu+BZjfSke&@-(r(E7 z&p{Y}_>7jUZ~P}h2S|cbW~qYAn-hY7a-GWl8W1{_kn|vT$bDKh=(~%mn}A7R_Q_Mv!P_(lwRjnvZ^)K zx-04a-$T4(OKoiBFhdL+_So5(^ZFk-8;3H?aysOP2lk6>*+SKcE__t^+h-aDVV(ID zo@Xh@dIbq7*5afcY%z0uOZk)UU?@`;{&K<0p)#6f zvU-@Dm5hT(D7)?e?juoFQSma<4P$(O!8A}P3=!gdq>XnGQ8%Vk5(z=B35 z&w?L}6yhdGmbV@|W2+ViJROKn!`v%2xxhXstAk1~@vX?7Id~uKgE%%6}Wl@|LrqUT}WcmFH8a*^9~bs< znSPLntnko43;AL(_eE{vh{++_-w;FhpcBPzX{;rh)U+f=qr8>=}` zVSj1%XU-TTV-+ycn%LmE9SM)+gTN{eWX<;z68->9XlVfAoGKL*!d=c46v8jaTI8&D zkuTV%A{iN!bK^g7$*c*Hq}R}1luZrkbmup%hy8R+3bOFuymb0kjnG3_`5qghdez~K zWHKi^)S`@a&fL>HH>AzT&!3!3ehdwcY_DK>7of#BUA6CTG|a1wS-nM#DSReLk03tv zyb%mKB>~sVU$Jg`<-WJ6_XJml8Ajyx)a(K+4&|)e^vBwhk0n#!U~NMZyo(MUz!*9d zdiO$dt?V~5^t`yeF^i6iR92)DJD@GHKtWFHo#<~c6oZFzqU6KR<9sG_1D0NOne0C7 z>c2u={XDb8T{ZJ~zsN^2f^yrD;ZHn}1rnC^fpVg@(lpV(P@0K9RcpU*)585;BpSZd zOs2QrCgJK@FLp}rZygSNG+xFxj$5l&tPjjtNuJ-k$+i=h$K3RVavIW5dvf^nXYn#l zjYX#FA`(U|e9i7#aWE2~z-89`pPxg2$7OI8*`(dH0QV>_y=ytPWm6h}>ZT9HJK3NV zEBjQlsfl<+s2XCx>T}{u_MPUQhsW$TMc{ZT;yDUuLB%MD1JT)O!3H0*H?Y?mtUuQa zP4{?zj{!o^PXjoU1QT7NkNFG+EFCwStsb?H)wqo+zag-8axRTT~TjI5~OB z7x-7@#4F`aX^2RNjEa7nb7o>h78G>mXUaSQP6wtBUlrqwDDHCGUp=P0k$eviwP^L% zM*STb85*5<(^U2%FiX5>_@^(Iw|ahP8~{_jlnk=L1c!NMS#+Ow>Fn&7I`fRrS&(-q zLn{kXY?&Dcn0)cOIc z>yUWQaH&rRbS3EllmHR`4?ASOrWJDPH`M<1Cu*wXA~lp+MuSObswv{Wb_aO-=7??v z8M$K20%Nj%s^dO`D@zP_5OnSnSL>BBKRFyu+Q`Gkd@A1-p0$eT+R*IUax3K)@?Q1A zrP=_de$fi7B7>ottj))^8~nbqD+t{ZWBO(4`ykpxq9cR(cwHyV5PkfoctrBA|Ect9 zm3u}<_7bVjKV#2w5P#{xEM{k>i#cMY{A{1`-q4^Mn)cZFH+*B zb@H%lk_YNNjj(=Eu{(Fs_i!VM0n(C#QK&$SmfN7#65>sqN!#Xls+MK3U{zo3HpZ#9 zf2=p68n04a5AhcRxu%fV)Y^Oa*$)tlWR0+z!(tnd5xYotRUfAYHKs2~Mb3BEHH};8 zmrsf8VU+S<>RId}cw?j9W1nj^?bmB)aH)r24~15{9Xa%5+~o9jVcJx>8PhsvY0DP!EjYSWVd*4CJ?w^;} zCnw0^`fd?-77O;^`ojS&UYXzx1_(3#J;lY+%dnXc6DR=qOVMMZWbkUu_5;1XNz8%E zuv@)@n!m-!?q<{5B`$eq!WEpl#X1U+4iF`MgRU}^Is>Z9kJinKF47e7YSe723`&hH z9%y&U+~`Nm7opASctBVL3qRa6aM!&c>FZ%~{k2J=5l;F@DT$}YlBBtEgfO%sS)$4= zE5!;bDD58Z-(();jBfo$@N?WIi=M}&|66*ZUBe+@yMETJ2-5^2Q)X7I2)6x0ZE9up z5B0eR{FZ@hh#3%{hr5Ruf|%koBw_h_*Lha^iBWzoBedZ~4S=2&&0Y$23PnBeC&5$8 zKjwYKZ>;G>`rj{NG@kpG1sJW}546=0AQw&U7RCnRcP7s+N~|1?+pkiy@Y|T0GNV>v z_a$Mpju;Sv?7~*z=;&g@)S6=zEVV92BOpx$v}M^>nV@C^OgdNAdNAF{UVN0l9inoI z-JX7HYZD|edj|okMKGKdlpvdO7lC#n&gLA2ml9c5smC z^|L)nPcC<+agpk5B#<>fPx(T%J@Qv2q+6Lnx)?>Tj9SfD{<4BhSM|?AB83x$w-Mte&(Vt=QSr49VEFvW;x57H4j9Xi=YM|t3(~`K_a^p>aT>88-_KMJ1`3@YcoLI21)g8{ zCA#}3n}d>h;0&5KlwN)r_w7*}qyAB4xdGZPAB0_ld8gTMaC4$yuQ0}FCfYg#|NO68 zTYAzPMxQ4;opt9leF&6~tP4-WRsni34Nn`nOc~lYq-Br$33{N(r0Y{(J8zV@Dl%A+ z>vyGjq>$VrdrkOrPSxS&Tu@S%^+M2#zxD>k9XG`uP*b?girH>L^HL5cMIyy(#svopKOsTmju8+{b*ArUZm`e*Dl2 z#H;ol0YqxrF=MvT0>7mG2&ny+c$h#(0bZJ(n+?`#I|jd{1Wkx*NYO+5%Ng=J+2pGv zZ_6b5YU?}*V(Gazh(!8ax!gFK`Nj9!Xxhy%^>vzGze5(k-N4sX@42IGgMSf-6yE3_ z2B;T1UM2JG@YtZhWx}P{e8k|xqh@=YW;_k2mY(-xtUEiCf3GOszS8ZJklo{L80J=p zHtbbwtaqGUiX+ITg(vBACi9qHo+G1C@xdnqpnpx(B!A=d@^1W{rR!I*+{6Xl+nf1bCtgkK zM8-|Z|7I<65;-4YYc)tCI5CeO&SC*R712f57Z)>T`R5Y~^kKS&LsMpT%HkYHP6w=E zXk`XffTOf`mB^Z03=3G;iz*;oWK>IO)26g0QhSiO#+2!GhA$X^85KG+a2OS@J@(k+ zKXHHA#N$^q;AoVIS?r#IIyA7-m-j;K#GtB+cF|$PeBQF`03h z+^GoGkBiz+4Q>lUo1SOzLE+0LnXfTT^naR;z=@_(f9lkuDVGFvNJM_0UsNobQdbIO zWLocCYndu#)MapJxRB(2b!x~MNaVpJcE_TxZTz?%%`ms3ly%QyAb(a~O%GIiMfK7% zUOy7v4d=$kPb^33HyOVpyc(Hn>#{7}>wBy(xHZ~r*fy9u%T~44C8fr8llFk%uoqzB z1oWt^F1M=^e@VyHKe%&X0>2?wQ!eX)hzLL#`Z)X5sHvJ=J+- zilhvRIE49BZ6{X0Ut{n%@_(}h7eIdd6p1vyBdWTA^a)J6Z41vSP@c(=f9@7i`TMDc9HGXR`YB?{};b%gO-#%=;dhy;--uU4RShsv9~nIT9UJ?PRzIddj3GDpELJpD-?TPRU?8H83fOi zWZCKkg?smhSFyV3_CPQA-nBARrIT&WH+RBheU-Mwm1HxYEZ7uFklD%f#fdfnF-YbL z-^le?lL>td*zQ|)UK6Md6aX?PO+K9A4SFtRu5vCn6iYoAy)`ij`y$3v6@)%i!P55WX z9)Z?}Ip}Sw&_fy0rLm7(EwnnQ4lg|DKnv>E5c{SNN`U=zDC_AVqDo}5Hjjl)_lm-% zYSz|*U(N}VsSI%#@AKYzamLBo`xT6jj2BsG_zh&_=91dCm(AIx(j}0~5NZqsKrGM7 zRTEheRIORPU<5eO9)uF4mPUzk8GDi{ZIiYnru(21X$yDXYee%I{4`kXjpIc#UNdPM zcscbH4j1$fZRhZpIshAq+!qS8VN+Q5=9zIyB)k zE4_cwbc@O&Kl&h41}{X}!mTsL)P|k<|9b4l-`==I_b#VxK*O*xa*+UA#Po&1l7O%W z>WVKl@#6fx<#F7P$&ngQn2q&kmR%{kf2)o(RA$DwB~|gHfz)7Lac3mSn6CH*&prM( z=X;r%mPfaGa-uO{g6bUqkvO>+U)Jj}^C$XP#XcH5u(R-)rygdI@)GD7cH~e%dD;JG ze5-%JHcjKAESVvJnd7$5I8V_54Lt0l{#IIlZbMEVPQNo1$yAHp3ow~Fr51P~UeHX+ zKsvONCKRM@JcOC0@%899la?DL0wmlIsYlk*T;P`6GX;eUNuV6U@NPE@FXDY^e=o!g zHgCKnX>*a%?7`i5$qRig$2U4OYT!8Or(~kTbjRLQ0%V_kIfxG= z{X54e6I*Va!cL6x(w?aW{TJdASu@$S#xn+sHhjF%ndLP_<3GhHhpB}4`3cA_*X8V{ z?gTg`juL445alJd(zEd={_`-B5`x)ExmYQsKl}pmV2m}FHPvFW)CZYh&mjJJ9@cKb+8XR6Y!RayFT6Fmdu*e&>BfNuy3@}i;k|LH$2 z{rku78KMb6itv}=vcI(pf%0|wtSK!G3&U#s31-CZz3YW(uS379Pvc1UpXWXgnJ9sv zilnr2<1?%abU6!D#zSR9k4i5-X-y5cPe+;>@?=tWX~t8#yFeZgO!FCK8n#CQ=;TGH z0EfP?D{0K_JrY8uD_TqC&WsH+mSdB_$o&Pa321NcG(aAsm~RMGAm;OzYu4B5j*UV7 z?F+{l-a8fpAMc}`G3&`KAach(@#5JE;sJdh+_$kLG!$HH-z!@SybzMFXAFRF068*Y z&t=K7-GdTImNA|AA>fsAJ6*c62hlUk4J{*Nc0MPBJztlvHmJ|2f-f*f@`s1)YIoN^ z6Q3%OwPn$tIDVlGi37c6OfKYoULMZlH%s*dbNwt~-|vu_FVNk?1{v&hT!dy*D{V{8 zvC`pu)hbIuN~UYeoLfw~}eVDPOD7#OT3|o>~ zE#6vY{Rey<+$UF_Px zNp<6p_F987)%SIOZS33T(OAv;DNhE9rEAsE;+(kEzGH4USvAX6`3?kWn?1&=EL36& zA5;Af*CQyM6b?iTy)yd9Sr6yG6xdA%Hv+9^`{xKg9+oIi6y04DYtRXg+_w^XM?IW~ zIO7d)p3jcCQP#f$HaP5C-y$l4vr)eZ=9kwl#-;b?M(N$|co%ms!~fDDdR8;W_-iLl zdd&FmTsy19Wj&(sI4HmkI8px02bha=zmY(dwNCG7`D=8cpF|@9?vfigi(xQ?scF-}wnrv!fJC3#OqO=J$TJ z>I3C&2vz`1&~O|?@lb8g7d@vf@D+w(H-*O8J~{V`XKTQex%M6nGuTIY<%3tRQF;Sl z01yEUR<6H0Jt)te7$PPnmr(PuCDE-V681Nr)Xhh|qtz-pDqubFxBvFL{99Uypc(Du zM?l7*oT6WY!SP5YY*knE8_|0gFvHy(WEo0jr%*9o{TOC6p4>8F7BD&1wxK#e>vEyC zDhuRf3pW^P-09mT*(Ihi?eT2s9K$(PI?ZCjq0;;XB=VdaP|}ibpdNg3#z=$8x z05aht;>?R$3|g+H)w35lu{%^|DNjy7_-q%Mg9uhu1IZepo246`qBtqMz3=qRI2?#C z?}J9I)z3!J;QoWDRskzDso?=2(Yv&44T%!C$rDX3GeJ6$tZh3yuL!_VwX+fapw*&9 z*|0OxI_p;5zer|t9^cN6$Z!Du?UrZ{lS^7fz*fU)s<)NlI7ySH=8)a#I>$P1@wtx< z7Q(}S!fyVzea}DS5R^*T&9v}C`t}@}?7&XDaFR6ed?9g?b9`z$)95`Y`=(K=qO{I5 zZ`6d&x;QPkS=<&Bdem}7i1{;r-AnEe!#JRSKo55*2)*3EH{RF~_A4vly0TISKJD$~ z+?!4!!Kgm%rlU`=#(~Dtxj+-kbobE_=u8=}i}l;B%Q~ z!|>gtp=Dbh6c;*$_5i|6?&J*|jK%==tDdiMIGO)|>PKsc|vwJT<^*tkM?m|KzCj)MMzD>We%RBTU zacOx|z_E|8ROTHeI_y#v?O_oLFcnYdwwx$wo;5}wtJ^Ak>s+KY^&q?u0TEzX! z;buYTjaA1}+H&}P`al=Ad*}QaEt#4codR$>SytgcT?{<~H>36sS>NM$1*M_epVfaF z;Y%z`=Hho2lo9WoYgMpo8YOADJ^x4jSsgm?lx;p%dQy!&GDp7r>Tc)7m$o%S+SD(u zP^mm2)L4n%L&BU;E7dSSrvyigOa7}&j>s>W@9Gzea`vCQ(GiX1rfKoGxDe-Hi0g0m z7wNAQ)ZZ_^2O3#=b|iH`)FiN&9z9#BGnbT-z7NeEj+4z}j3~vewwRd$4*dnNQ7wO; zoCbgo;06%R3G;TYy8kY$)c{q9!QN&z5C~F~2J>j;Pc(L4?MY>sOABtM{UJv-v#qE3 zS*x5XkCpBD&^-CrbTb4K0EaF80`Sc5@N?|^`-llZakYQ)3GYz?dF}pp)N?0Em4Hxy`{8DzlQwEG`eyAd8P|5OV>GTHKJvkg$D>rw`>2dXRQ4q_3@aN^;XW13 z{nm)y1%cPkEkZlnM4m#=iZBRpiMZoKu}hS2H9au75|6q`yc~J@JmqxsU*l)($vpD- z%;|q5U(64LT(dxu8kbM3QPwA!M}eGY>dLdDXOdOk!uHI0*Xk`ClkK&^Q#!frALCyH z_%ET6R5}6g6V);e5Udf1SVu=r@4ZMFc(a~o)M-?ag1<@56WaxK2%pz;5qA$ii}v{z z^v`_H&m5$m;GCZj{QLasTsFvdmYl;N9IZ340CG(tk-Aw^H`>jc5#es#Es#8Lu4;fS zcdXmSHYA~^54I~fdrf3$qd%%VxCg>q;YL(@(me;C2I0VFTb0I!v}t+=Fd6s#d}We* zBWo+M5brczn%E4IIqcd&tl)L>?eEA=sq+;2V21dvKz>EjFUGg#O zJZh*2GED9{;4K9^B9YBK@?Jv%)~8e14WltM@H*946Rg(J^GPS>1d>L{@lpnLn9{9( zYBqb!p)pzg9sQH~N=OSAQ8=K3KzwY}=nSb;Jd4e~F&=xA{tT<@I`+XE&U4Xgi3nl( zu@+Bo^b;rX-lVNcJgk={llj4LZiPM4C(_93T*DI^A&NE-Bwiq9zxwm6DW-hAr}Tx} z0T#oo{}bUz8Hf8QGwxUu@liWR*mHbA_4sR(0Xtt9LJNq$f-NsS?ec6T`=y)WucqB} zHC*Ka{oLr@)<WA~(fkhTQg9^D6y#yV@^ra&ghLBAi2K~1~aKe9700uhfOEcb5am(OX9Hxs)0 zQV*6Ya@+5ktI~>+MtjRPqgp5O2P@mGdpiiCRC%BysQ~u`jKbADW=>6lG>Ju8m!U0c3S-I?LRwg5#p zy=*N7=~}|&m|i1{pAtmAxt_V{kygBK`}E!QH|=8xG{$kSef{E}TngR^+9&Pu z*RhQ;;clLgc4Yrfy71tzOSHoa?hmR~99SvYEz$eZE75T=`lV6?<#X?SP6S-+-Hn!_X(QTFj}*J<7x_{zU559>nL5NmtJQmc(Ysi*aA>l(d&+NJWpf| zwuDA46DrjoBd^zomMh9=B`O52cE`P+@7ZaVg`_u~+k zv;>4vIlV`=H~3bSv`#LrmZT(=M}Y~} zX963MiK$4Z5et>=Tgep{WCnXS+u6#M<3d9@WHnv&UhV|E^&>XIu@SgtcBSy#3t*9^ ziz`IL=6f2ixio%n0(P0tckJIcJ?yWcX1;*})ja6mi01{pXkeWSfQ7E)Rpf_-mpqQ8 zWYz-D7?cHcR{VoZC6GOA?vq`Z9o5cfZel}{yrM;po&_cOfxS4u*bk4dnMR;RkRl1n z-w{HR!Jr)%;=E>UFwn|Ep_-fF&`$-IApfo;i6>Jf&@%gwxzK>ymf{`}edQm13%Y@P z(^d1u3TuUdCYO*E9XZgmyFVNfRF4etJ!bBc{g>2qg#KqDyZ$mvSl2(0CKx#7n=RD{ z?w2)p`)6#pnT#Yf|e~ACbd@9!8IJA$3mKSj^H^iUq~Jysdf^moh?vpv8vK ze5vpx`usS0n1A}>NJj7KkW{iN?{I^9=9wFJ_H|nyzCal2{00)Z*2Rdd6a34i&t&mr zfd-T`NjzYE69S$OIfwTZUIYS?3&bhR;H9b4uc zs0CFVA&1S4RyurU;ICK8$&dZ;j74AKQ&tn19)Hwl=4_S0$0QX2ZzI8AIhwFXC+O={ z&xZ48wOq058hn$Gr_zlW$lVks*DJu(&9;6EyQhm`mMAe|b77c6sJz^q)N$0M(VKsO z8#ez!egH&-0Pb@i$cS*!91R7oEbrYy3J2W?P!kv8?7PUY6xLAI{D3smx(qoW# z_n*+wix~E|+5|M!LE1Qn%PnbXraZ?0{7hk}mwlAVBa*P@(2dg+F|)cL+Hqx~;Q&cM zw!h)R@*mrQc;zC|%iY}g{HS?w`hcS4LCq_rj6oLmJd5bkWq5J5bNIM-pd4Cg=40tcR0{pp*zkM{78M())9iLH?fyaH zz7*4Lb0F2>jXhrSh#385nX zg=sn{8P1*yn{Jo4(3_@2VN)Ed&k*6+tERVM$782KlF_Fvq6-4RK=UmYGYnUJ&vhnO z{d{hKOew{2nd@t$B*NVMalw}01%Qp#^(NMp-;#uzL(6oC+_7-=Opv56)3{L{@{%8d zGf~84W!S~l9nNo2f6<4;{8@hg`*wE<>|?qwCIfSm=}>wN07A|%7~$h5`COh1pfg@> z@2@@#!QCSB#g?GUs}_z!M-MnTwZa|4zlmjc^9|Z8V3-9G(M|X%TkLTw%<%S&@S^Ao z!QqudAHF28%TuXV9a~Yjyp+fp$eJVm%lEf5jv0+0eNMO*^H>&5p;g~j8` z$ggCM9zL+3Y>5`279;tkhmcAvHs|kfpt|NUc{-gU?4x(M6_9At%g07MlgzCo^BA-` zSBUyc1o9wY;(XYg-=L?<2S^743ws)35TYo;uQDG@(1_{%+T5l>eu#gv!g`B3Gr^Rn zCUk!g+#j=)@6f_(0~=O84QDzLNHV$9k>?h|@yv|gQP-l-2 zw$#2v{TqXw_OacO^&g#PNcFO-bPny+slkzL^J!C4%HLF~&UM$y`C<5bb!ye1GZGSl zx1_{+FKI++UH+y6_nww1yx7x&Xnq%}h$;53u=R)BM4Z*Q?driK81%<*7{%UZBA2m% zs7|*9uM=&!#rI72dALtU!r=ji_lJZJfUAgoZ$eY%(9)yJ6v2~5<*lbhZ5d1F@wcYD zi7m^vpUs3&X)th!ADl4F?MZWE}%#`omN!knk1fnbQcslW$-NcB$|Uua@#amg%Uk9rXftuYyRd0Ka}P zN?utv4^1Y?CRD_xvup|(l2-u;eZ@!@=bmgLkFM>()F~Su&qdXeHx8b?nf)JPDCowz zswT4_)kA~Swh58tQVNQYAU(xFhVG;K2h)*>nJ6^ujx|pQmE6iHf49#E);jeaF%w;! zgO?UJk!EEM(t^=x0b2{^ac|%$-}H^6C2ye_VBR&($C-*?UCmH-DHvtY^(ZQ=l`8y{ zdsHhxuxc9brmz+}{y^Vh`RPEX7Ae&*bdi~41?AM6w`BG_?K$GY8Lw@LQ-AsukyPEy z)FVD6?F)+jrXp=~Gtjl}O+RV@r-m}GjUpB56XiR2D2Wmk0%w)N0xAr@CFl-hd*0_RsDWAgD5wjoAovY+f+x&-Svc6mc%VR!AOkXVG_Ci1mwp#8X zJ+^_NeNJ}W;7?SQ5KZI$_aXO?M=KC9ZT`4V!o)K(dRHMssZS|gwgB!|iz|7>91k(-cEw&W+Vy-i z0Mj%cO7E$vMs;3Ehe}3N6_sOX_PuXiN5?qsuWtI7$@!6-4U2$x8iG)P1w<2+vj-0Y zXAT#r$vYJkZVlUknuN+pj!O3A&QE5J-B1>7FA$geVv+lEE+|$B4QK6v^csh;hIZ|E z%?W37`7)O&Ia_WWyMO?*u=df&B3y#NS&1B@(V#+PAT!$HyKgf*7TfduDb$nrtJi7Du_H>$W5R3m3<%RMxCuk_UkWxTE8 z-6xQkb^d`0yd`>Kzzc*f+1Yk6Q`ZB&l(bCDKb&j&J6@M}pg`R5>wG*uIFtB8FWD!4 zRilSL4_Ef{^kJPf)-zgihbOt6M~mF(OXbm2O-uij z&y8D;BBM3KmH0G*L0?XiHN&P)Yl1a8-hS>Pc;BakS2MO{K>=I&E|yWQ(tw1}%BCYR zG@c({*yTZezN*wZW_YJNsaYY(50{vx>;BW?JdH6g`W#s5HllD2@x~-~5iBxk_=>t? zOdrVoAUTExU+Sx;?JaaA*=6x9^SXgWZ5MiBzI!Vu+$jKw-QQ#bIR3g*SqR=AW!84Y zi*PaGGI~UgzEk=5w)$wnyZ+71ba#Qd;?zN8!K+gtN#*74N?)j;@=olp6zN{PevmwA zDiNOuIQ}W}hlx1qvxBzVB68A^J3cIncak3xuaL}g=VDI|`u{?1!02(s_d5j2&NW{> z$2|45|D%tp0UJC0C3zG)5^HIt?k5(k(Hss1BvW*hYJ&hn>Kk_bUGMPDPZ2$ZVcaX4 z?R&dAM~!-!##}~QR31cfsm{o5F-Pf}!Nem;ewxM@2MdUKY(`ZSw;7l;rNS#}r2~3O ze;G4{sdyFe!KWLMBD~~5N=s@cH`7rZr6Ga>r#VyrzOOr@Sj=$D0rZCybNg_Ze|e!3E;K|}=h}p!knCvs%oii1LDaWC<;nH~)W*_?^6t4}c&X!U-nv`4$qCl6} z{773YcgNMvza}|qefGKB2RTuNcF)8H04=~h&Fh`PFDe3F%Bbs(6XFAP9Isp99)8=r zqSxVNIjztjOTwl^QaI{G^f-}9AFAPW^eFJ97PZ9Bv(Fk5kfFqj@2&7gUH5xl7Ivk> z{1ccJKf7whseQbJDXXNl%;9O*?zz9^-;coc1YQh(5I%WShMCLLJVv`KyItv4{@= zLVTv^G_n4^`IvMYF9!egX0|T^2`?;sbNRz$rDImx7EKGS?4Y0pWii|y?MH~_|LFAf z8Mw-(G(W@$IdN;wKC!l=8$$`|$ZE{k_wb@)$&o=g zM3oS9O*4qqIXZ@*y(3lZ7>^gFV~kGvr(DrhgT?hgu$_T#?luZygytR!FIK(9M3A-h zB}k97x^l1(OFHBQXe=FIYrEJFr-Edpla z*X4|`J4E-6b3skuVMdcvP5N*t-7z$-uh=W6GN)fmh^dQWv9HXxOz7JT43oQ zfqVVHlYG{}EUK+8i2q#cxKg}fbJPEDVhCmmrs!Hr=10u7Ct?%UseI3gd8M9ulXBCu z1jK}Z{H?o-*+};if`9f>!fnvToj^c7QG54LaWB@_*9P>jjv<9;;($2PPZsqfC3<9`VgOG|Og|MUu@G1ZlL7jdZPsG%8mR53~og0%Th`)l^=-q^P zsb-@rzK$c>XTo5qM0U$bf62E2wAfc9_5+d=S1rXyz}sl2*?|7bYtPzPL9+g_Y^>AD z?L>iC?X=TOrA{EBTT7u^n{6G5e}tUZ@Dy#Ajf9p==ieatYKr@6fMpeM`WDd9m_q1;#(JE%uBK;m9xsg>1C~l0NW?A|{7T#4ky{$~ zihvTQUcrKHIhg_8l&puV{~cL%fPzSV4TK) z|Gi$=+qemWw^ov>wlr>ATJ(6VCqH+`G`ZolId=ivYM;ER9tvCf?Nhv}MXR zL#4loKrnBq@BsYSciI~$XqYHNpLJ#QywcXOCJlGO`hKUhbrcWzRmd|Vu93zg;uM@z zcjyI&>*n2`!GLEegsabvuY=J}H*u<4^GK9?t4*+U&CAsY0ox6MDzrOBtgGo|Aw^DI z46AgoZ?VX?NF)>52l}i4enZ}CcCp@aFlAivO#o5;s?wDi`3BYS^-oJ5gWuJ0XROJ{ z_deFoL&Z~u2EKyC%_D|zI!RpB(HDACgBdMes52mr!7pqW5iF|P^1L0XpqdY|tc_770$t9)oJ44v0I^E^N>7~;#LW*+LH4^5^w(a$ z;cJZv9^(H&+S%37hQ7WC_bPOdKneoz@>6i|G0ByjY=PA?Vczlo-CyQQAEO!FZlFd? z7}ZUEw3jGilnY_MKiTYwZJrHsEu=L?v*cZ+<1@CR9nEZ+v&w(fhQeu5;6Bc{1&`K|IZiS6+=6g3WL zfPuqJwj{4f@v)mA`SihSouY+0-b&aM&m0Fo|W4~EHW3PT>T#$K6X(3O()0_Ne<>QDyNi*i#d6@y>|JA?P0VCw-`(!Db=x<5&d*AvFhbqc!7eXjW9 z6;$oUV!#*d3k()J5so=2M$joAZ^!i6iY1XVQv%8gfBxrdv4t8A-}l(_ROI>?E>2@0oyu|g&!bk@MsSmMCw6NW&-WWh)|70yG=3=7qr z0<9JL>z26WyGTuSSXVow$xi>3X>v;mVkuvRF*ERKo>sA*$C{>8V!V}^UxKQ{!_O*- zY+5xv_P1LPsazP1=hr9@xynBJXI*?s6%t%gx&2l%EMnfKhwB*hikn*m$Dx2PC~dDk zy3YHE)`v3+drF%RT}F~gM)%n~h z*|`%d$To>VqT)>n*f%Uy5U5~#8^t|0;Y=Bh+MM5(QY{_Xd$0|??@@g+m}ukKFyxqT zwLfuK2%wC}ma$P?{6kSo2>qZ~WjERrB96stj+FyW%)EKY?j$AgGWA&%REWWo7~nJR zx=~Go_3FMwEb&6WisctVGiig}4I5jbcvM@Do z&n0sR^rVgnovDy&Bsz1HfnTbKa08sj+T{w7rz)8&mp_x`bi7wW1o@%z7>1NFq51yS zl)$HsEbc@bhTKVKGhi8wvcxo_>Z1A}#n&%c#Z26#)mF`FOoAb5+xZ{j6K8Ur60nyb+ku)qOt%HA$sVSBDB?( zMt>h2;0`rfY10^;$Isfd4Q)3H<1(<;q^&ptiJHeLmQS8%2!qoXJ&!{Vq_fDy5W&no zors^|-Im=~G=Yr``X7z;$cn8fz5^?a@$$4yCjzh4Jubl2N|%Fz2v9>ruQ398h{wew zn>DGFZcdnEfZ7@h5}$dCa9j8uvjGVUR!$RrLW|JqW$Sl8iebF|%G4g?ammbYFb$9# z_=9@ywmKA<57-%hcH{!cY4o343_4IbU;J0$5hd^njAhV;lRs{jeqBO%O|>#@$;1rn z4Ca*g^DZExf)l7Csb3Dh_m|#~b7K!ZB_YmP&GMj1jcBj@Y9speogjBWca}E$+Ar~E zeswrtZsV3}J$V{;s(nyaDT%*)8|krpCRKvv)osRq%QGhWbFCEc5p>c*UFW=t{B}Y(LXf zw0Po%aI%D>!6bL+wsLwnqRzLg0fV~yvr<;l9Ag4oqX*vP!gnrJ0l zJ{x`8&}M5P{7iW|zw(@oXoQ&9sUfsS<^Zx)uDb{}y;VChQ{!wrks-Q$p^`2caUNlc zqF5fYPWQ)P`mbQ6KooonyI@S+{YdPn=?^+S83CF8yXrL;C*ui|(pc6p^L*Ncx}q{b zS0g85%wI>DnrzmDXrcvPBxR<82u@4^3OnyoPjXpv(n`bcYE~zuT6xW((=ug;oDtv4 zN^bGnt(qeS232bR8JF`YKTG&PH|2t6|9A||Xa!lmkLcCSfB*$ES3~saM1sKBpg?&= z`G-sn-B+uaQ;rUulfI1%x^MDdB@kmnezJ;MdC4#eEoa5r)&kAr2otxYI+bog=1NGp z=56-`t`nh`YCAIegbLYAw9tD^tTbrsOme?Lp7(iSnH=-8gP8VP1#8x1$KYiz+1|jI ztqAxYnCtK;?|#?NR`@M^Lc(t>D~(ee-;&#=1A;MPo@5BFMBF@DLba1XnwmxK2+X3` zdn*MS1CO=5a>R1=o;}AP^kEwMAAh>p2>IC%R}_9od0-^TwlzwNGNbw?qw>yCYnhqj5Cdg~gi@NY&{>D&4wYRCc^8`_3V^2+zVidQNgB`}Rj8!p>ioshr4FC4r9nEf8p8ika5xCjJ&uEY#%H zA9CUIQX_5-7VH?{jXQud*s5`YOwbHdD(rH;GZnxGd4;^x=FaIQ5sTZHH0O_sx za$#9mXW$}NOW&&;tqVtW)NIBNND zEM)L2ECo?fTC;0I^ZgSz>ONO};cHYkgwnv%tE&|EiHciLRsP;R`;*|n5mHscnj0XrB3#~ z!p6nQOK+AO&vv_jsw^NirFj4&1|AMrP1kVhVsZMh~R&O|9lZ?uf zi{$}vQp-+lJoL3~Qg08WdIIYLRbc?S6a7xN0@0hYl=V?&*BYa_ceGpin^aAjt-c-o zwBqaYd&$;`*PU7)xQE!>z&&jP)qY-}Kn=G0n9C+h)iI}r^s>;JCI zAF5in?Ox$7ns?qU`mL3<6YH!iRO?WoHa{Zb{?afbuoTX4rxsq9i2;}hPd;5e_?=%E z+p@i9b=HmzB`_sXZQ-fY9V)((n~reEP%qloBT({#A^NBzAiAOkC>vPk57o z{1kM5#lRy(2RK!EvHagSr~%yyX|Yg0E0+F>?c6SkBIx_%~rrM zCp4MWbkZMICKKGx>F6)pVkG?f>1WQNso_&~)!;|2zZm(4qwIfMa~9{OeTC2EntXGU zrWBafDw9X1yu&xU72{cOfVbOc#e zKk+HA{HMUM=ic8a-r1E$YKl zt7?yJp+CU)yfxN>SKdKkx(ZWJzV7@XBhJyc!>jQAJvD9gVZApsr*M0|TU9o6Rxv5fNEmB*G0R6kl;(lAcYB}>BD{Qm`DW3}2WGEXs z?hS+E>g5mAYQwtV6B=dU0D=>mizR{c*Xp#73S0)u$@%gyj2pTwu>;wU(p4O97B!3r z3b_U3>I->GZz#6ddztKpa-fuHbjqCZqhJ>0v(RXw4(DZU@#wy1SRu=u+G{hFAho-T3@@$RtL*!bceTHh0^nKU@;nhYOlegqmur$DFB3$5$epB5O({qO`~3dMpd z6%J8FjCU~B!hlBScu*0m@JwMRGnuQJ-^^r*77Q+)IhB#K4^I!o5)qVrfFBW8pW2Dt zbC^zUW_I^wE!@L;8*HEFo0mzDRD-~yAvbib)RSQ$I34!%Op-?|n5Rq&Ev}vql)%Bz zo+U-F^(KI6=nWh_@_|30#JPpRw+c07%~sozXU?*R&|O3B4s z7BeMGO<18mMpOErRGO>C&i zeER`*8Rq!#pJOAbbv!yBN>x~0*LEVaCZ}g^L=_wQN)?6amq)F;LtYrZo^;?V%C^#T z9(Sd14BJb&S4EP!r8G0Q8-o0=V3~#;-7rh|xIE;;zH*~~G^RW&OSFIe$m23|;zb+s;gbtUUbh3P z&|hX!w0XWdB3$)EB3T9s_3;And8Ywys>E{4UE{AVqO(U32Ci1Wv>&LMAfv}XOem*V zECXZk73{+NC~Ch%o3OKhD!=xE_$+{{;+y7);EL8&UNsR9XpTV=X6;POBZ9@P`Bdow z7-aR>PR`+8qEK`bK6++Iy0&3yq2MIva&$53zmcQ(p^?tvia^9@mYG$OE{cz|V11LN z)B5O%rRSh6E8E`pu0B)J>m8PuG{D;Ml_CRsy*EzhkR^^U0WuIK4zxGXN9V!t=v|RWpjqWnQ)Qs2CLSe zRFn7GozPIhpbbItWW>M3vm35R#gAVOf>mCtF~bRO5FT@kK!#0yF@agL`QE|HjuC;R|y{( zpj6v<0GS}SV}ERs%wbD?xdLAXjs*+Hi!GR~nZ^k^WbBo2D@5qorNni__hk=Y-yy_t zo!eIc;4r$=jbU4n&7JpF<3H+8){D%IJ!dIq$Zeyu8G-QI5$H4X#(Nr9Z3HvJx@RHZ zso*|pi(?X{QGNZclDu4wR1DrhrOx6vQ948op~kh%HdQE;cT>jOernWv0Jmn%S^rQ! zDZc#!^rQX5K5oc>29=jh{+0<7wkQN)WAIB~|6v&Gj<9;5Fj5JCCC;2!93z&WMQ zt>amHo^LtRN_cR!O4j_d-Rpz z4oq)5oX!0(*SmYZs2Wd0MUu1$D|=M4SK3ZpYYt-fa*ZrU>5p9sN4^ z$;EwgSYeb?$H-*xxQ6|0v(Spr!`zQ!x@V}NMX*YQN)D1}^4Cp^r;kc##ZntEo(Mv? z!&MFj%OUMdMv&_daJwNodayz3=mx`k0R57_jxP@ZOl9-wMINlbgz07?smW9g%aeir zSb1)GP~Yw8&Qp4=Ip!SN%XneLGkT*A7K|uwVokosE!*%a zQ+JFfy!X!4{i0~nEZ>?xe`*O76Jj#jS;d&aCUGv|-Nvnc=t?R%E1Pk||B`ZdLV!SM;|nKG!%mT%r$)+P4`V~945tm>P#s6^ zd|N9)2sGsGQOY3AT2oUQu6v#RjFZCQ>Y${6VbvowY=RO;UsI3v8NL`7eFLBE5dvs) zI%(Ggq0J*HqX6(TtZx(*HeXe}3VeLksQG3ec}adD^BxA=`Wi!0KpNZSEdmxo@dVr9 z)Sv0p&@yyLaUlA)Uu0mAkH*MfQ$6~GsFBxmhLDw`Q1j=O zZcOO$KT>zN1mCSqNvL_HT##JSG)_xevkWPwEXuYMey!&uNCct;)RdEJinRU~QKSc- z66}A#im9;>9L=?jPC{_pwH=Fs0hJj>=lhJIsYy9A@30X4UrbfAxAxtAg$2agr13#yrZF1RN&%I+WQ{Pv&F>sup+#CbGVn)hYgfEB-XnmU6!n9!_^Dw|2ExvDe9L%uHyQ|a6w zMfZQ0)nK-a%MNa=oZgLNr!Yo>3_n`&&|MJ2fg% zz`AVd-@1|@Ja#ZLK0jsGYyC=h_l#C1=qkG!UnbR~isl4$f%nR6qrtw+Np1(Wyv~5% zFMzY_nW)yh#}kw;Uz|;ywtf!Kko#BV#}@P}80hTUK993DhF)3lD(dx2L#7^gr7M7H+zV6_2uK`h1`y`M|Jo%Xu@8d@mK4{LG^95%{xR# zkA=qIFn%yE4C%M6U9zFkgvEUr*_AbtwZ?&+OQ#4!eZK^5z{|b5cUpdBYWroSXO6@j z?Owv|hi%TXm5EU|ANXNdU_`%P;Nk0%VQy9nF1+{^>BpB;GVZ2k3mk$2J*w!N_H{x| zFd8uN5?blyi|6b_PoTX%E;LZD4Sj(ngRIDQ!75Ylt%wVcsWP#aH3zfF!~88f_m%*# zfW|GV*1UDIj)ItN1QKzQS*N=mjbQTy3=m^nv&8VH-6))yLk~B@GJfnOpk%d%y1beG zJjt8t!#g^;-a+r>@7n0xCHxHuuf1;`&WOijzU{XcL8{^M+40YjH->s~@42z&Sxq5%?f?tF2Cx z#&T0bx$~pT?=OGu^d}4dw2O?|3HONY5n3Uvmvc1j>S@ssVC&Jy>X5S&{##_Gg1>Kj z?fRfIm4cEa5yKE2%$Nl&Nk=b-0Q)Z(* z%S4-$0e)D9RULY`uld_JYXH?)7obgS#F86E)5K^=Lz~8Di-w%Hp^=e5gHCJ_bb^;o zLzPL8cgh-#5pTiX{@P+A+PPrdCwP^J^~dQ6tYDw3r(&pdPQ4~wdrX%rSz_?`@!adU zE62Kz5M34sP;^aZVHv`ZT=d+Ap@eVdci-{!UXQp^l8VYzE>(P&)kN5QCv*?$${{M2 zQ5V=Jjkpw!dx94r@sw8MA#~KDaCx^io}Eou&sX>wIR5In^|Aam`!>vbyn@UTSRO~= zMGT^49j;_}IHE?z7OK8xJ4Y{|;Cjvv-P>o-&wHaLdNYZp9%(1J=`+SJrriHB0=%Ca z`%O7_ejFLE zz+Ia2Gt{q9^yM-zd7gP(<_4gDsW(pLtYYw_4Z^ZUm3`@f4)K^F1gGZ<239?d1yFa} z6!1jMtovZ0Z~;v%12Ueny|MT^_Mz)Fxo5GwUYEKFr8WFOw%bhVc0zduR7d{ zs5|^UN%z(ywD=&m7A!h{q~F+mnp98ySvVHRJmS29*D`7D9%=8`%{E>@VtoMMgOW{e zNp!`_(f+BYMzby|-3JHU4Y+~3vmf)Cnqa6L^WZIR$uV#@gKvr|wh^0`q5h;G)9|{y z4@*G)Eblj(hK{Euhexv4U`_Os^HrC$)acW2x^iA`C^5B=$IqrxctzVh@87@d*W7Z| zwUx=7Z3A(uS3aN=-S4=6fC%Khbh3ogl=Y~-tjH_=K&E!O)^stf5+?HYqqwKvGqm$ z!N;o`{yIlyOy`ZBE-R&{CuRK1Xn57Tu2~rCZA`Rn>H`Mxk>x_^mDV$oc#5!e=8|gZN+)t!E|FO2R5DDY|=ZCMC)pe9b zp(rF@KG->7O$R)z*%Fv1V_0#4tm3X*0gMY)Gu?(t?_F|pjL40E%0IodW>|6|oE#bl z2y2I)xoH}BpH%;hO{7K0*BxT0P`a(7YL6mSYuYur`Sgd~N#oaD;n=TM?FZbI9N7({ zc6WW}vYH^je7wVxGbb(8)FS8uxtCBO8DpcwscDJCm*XhBD8)LNTa-jL;}z(tD~ng| z_q5M<=PVUStH#2I1V+>F=M@>92r~LKGv2|06X|WNG&zO&csmYW_4v3BamxnZiygkr zrpAN;zmgC>DfiB&-zv7+(G_dW8x@k!O-uKYY~?PjbBw&3P_gcNR+Zk;HbbElkVa~; zWm=z0N4~SnV}DW8TX_T!2+u?v7F2s+nXh4N+bxX;HcT4d`kH5I^3O-{l zt8x)Vc|Nlw9NwNQO}-DwC2u4MXJM|vu?wt&h*Av9K8q{bkl0xdG0HAgmny|0tN7F= zPTG>q1PAb~9i@ep;$rLVr73w^PZ zLUxkY%+h&YWM>3~bqr=e-t6O9%%L@@9R<^jmjOA^YgZ1vF|Mtit@HX~dc)B_mKlPj z?g*~-><7?;eHYM|OaOxdngK1wD1`fG^H04O17rt_)93 zU{!lWVrOyz$@7^Pq9mo6Y}Rmj{RQu{<;c<%TyL2>gLQ|3=NYAHIZY#0ttG z*7fjMB6yk@L!}cv$;e!$#UzPtFp1K#21we&ej&AV85DO+7Rm1dO-<@L5Y3c;s(+o* z+OV6Ay7h|=S;|H^T2U85qm%f;P zp{nOkF)jBQEK_W?9+-qyxW}P+;(=XUHeKZJ@sSYiKpv$in;%X|RKP?NW0vxP1uBay zb!HZpb{18~O!QRObij5dD4YM&lV{`yM8Y@{SnNBSt$Cwf1^&-Kkk;AwOrOd1E#U+b z_$3K#nc=N!X*C3IoVYKBfV4NRoK(HF+f^rae5iLh@HhJPtW<3 zC!^iON3vBD7#ll33~^pn0nk$@XmlW%7nI^x*i3WDzf4&Vd?rE%q)2`=?jh&P;lC$ zy%DXmJpm}6Rd*5hme0fCo%>FVNf8~UF^$|T-N+!3I5_{)*ic!`CW?rOYlRE4EIKgY zpt!hDhQ>jZj=@aE7&cy@1JeH4zftwGJ~*0OMcvE^S)xdj0$8%A!~oh64>W1O5xXdf zIxAATHBCBD9Lbc&l)ogOF`*-HT7MSEaa>FaPa^uMsOoWzKRuULpgqw!=23`SpWd}J zBdSc_(PRVSjn3XT)Z=StSFZ;NLcTs90r+R2s!Ton7B4} zM|&7Fq99yd9-2Q_I^-&Az`r>((hl8{IPy)d--uoXLQyp_-nagh9Idu=Y=bhoe-D&7 z5yw-bnsuVQ_`Kqbhxhi(z@h_YX_6r}s=(zF^<^YPq>nbp!B4NH9jWzS%P-x)2V|G> z#O~`Q3s}b&-h>Kxb&tHj`8_#Ry}?^kHi|#5(fl}LVu@-wv&&QB zkD6Ap>9R5n?!8*2b*byuMW+R;a>N~nX9Nf@X_9+c00Jy;>cCehd3kBC=l7$GRN4DM z2o!c&X^R181$Krz+n>S7C#b$r_2v2eF|IHP=)O#!xV?>=JMLBTUl)@=BB~vL0RH~b z*HA5^x_hC!CD+2_AN_mjZOIs|E}e^e_JyrMfmxJe_0LatxTTK?VfNtkN`+2|AEm7F z`;FV3AFNVj`rztS^{?#3q^0s$Ae(L; zeeV$Pk$6{!%?AJnd~$}>iXI*{J^f4HVbcc(M{6Fm2!gDZW# zN0g(4e8|5*=O`j0BI91IzO_ei-e&U;+d>QYDyNBVKt<}M4PIB*7^`A)i_aA^& zbkXM3vqb>v+xlBjI)uZ`cj(JkU}lE=`wY%qZY64G32Q)Hz~rjKZPDn%u@${kA2z}? zPT@tIGcZ5-`kM&IfuLT80Rb!ho#MEfnA7IAbOWq^@IqA@6t!h8R8vvgL6lF8bQ92D z^05L~hpXad3cIV`%JG7RlG|mp~co_2qHC1QJNcsBuXI@*-AbGOgD@Mi+;J>cu*T~+K z2d$zfUXyODm+=W(p0579YY~tTlo5p&v@y76#OwK+>*lWId(8P!nZAOAr{|B#0#g68 zZl1*^^sp^E)yMc82CBp|3tlerg6pV#%KNZOdeuYDlLPHNWJ)$%GJ7czQ@)60lXDjb ziXhONfZC-4)Vz7*H=pjG1Gp*a#$U7>|8&qi8i#YhyOU!Tg2aiLPsJNHFjHLud&^iI- zvET8}$m~fHAW&xn?yvyLE`9@v5s81~*e5?3g0k^TmvIicm_#;9_x)S`V?oyb=QH05 z<9(5Lupcb!` zLH(h8|B92?Gs>spBndsDNCY_YZ=d7e=e8z zV6CQbwfCxZ)^*I}KT;k4^r-i6?usE$VA3>v-(zj)pk{l)%i_N8+d zeZs2nv@;Xn)|NE=xG>ItFQu}7Av=wN0HOiZ9T9j$_BVybveg^Ee~Kbo$qbCpNhNd;d|R92YuiWoT)bsJ|ir61i_#N$SQc= z$C^G#r^s-C& z?uL72lPgp5T)o1tW}8_9<6s%cg!s8^uA1 zp&`hgj48f|Cy(8BUcunIWP$`$$5VThY@5MTVI<+1PW=cw$Xw5+X~?m;;wmdsD+ z8Er^N2F#WgC6F-hO{xDh2DU94^5JCgXyKQJ-fP`>E`OA&1_vLd<1{3<>Q1LjUu67=s1%_p+~Fr95pO3@=9Oy zBEr&5-o23=c0Ils{6*elga;^A0K{}}GQ5a6t_|+U2{fxR{O7nMGcX@ps|y1On|MdD zEYFJL`L(MUzih0pmcxL<1%?6qLCyxe&0NvNdS*`AK_ELY~Bnkvlc8&=6=- zyRvn5XicMVc51iN5}^n25LmOa##>E%yRw^(MLi8*F5~H$HpsF0Nk8u6+wZO-67O&~nPa1)O`9$}dDyKWz-eC)?DgU(xNA8qQx_!Kxi`B6U%H>3w%U5Jhr3sM zkOFxDB-jt22876=)S?xYzZMy8Cf1QYwo(cGNe zy|aH&s*IQl+VJZ9#lRJW?wHiwmCZrS@Aszxi8)XRGEAi6In7UtuhB!n*JOaAQ5&3z z{2%3Tk_?=WgOK)uqx9*cHxE_D(i5_aFD|ua-uL+2brWGT+{NLx`J?(&fPA?@8M~>& z3j?viB3{i355BLlAB2^ts`?Uvh8mBip^3?n&(0^?ujVPnDd+Ph8|gB3o$>vvQ>MG! zp&nSsw8X5ZK*`^vkjEB@2)E`cxhTq0cWhXPhp7Xsp(z#9zuNLy5d@`K*60Pn1Yw0I zQK(&84F>}vGnDM#(dy*)_A93iMQuK0eN>wA^e#lJmMN;j7D>?Thj024J)QF76{vX$ za|jBy&cO<>vHs#TNW0Mm;N&1Ru;So&tVh{Yk?J@orwIg+r=k5JO|CdNSKmrrMr1%b z;wRgaJEUx*i9NmLO6@oXgZg>Cb3Aa;eNPQ02oBN$;Phb;yhxSsvE;`H)ZB7c7jbIQ zVe^N;!>!qum9Z%n6E>H($uD)&8Yxe!>r~a{cgpsUP62c{m7pas$cb0zoU-F)7G2&_ z+@toogfGtHB0PF|DgiUx9%X31&8ZFRv3pU;Bdd(xo`c+^D^fa$40(qJY%v6{MR5I6 z{1@gj#9;c!9pmDFVe%Du5YFIgWMQK`2Tm^?IJ6L|2u9v~;rLJd-~A%Y!#S}A)!5_J zF4pZ^iN8%vuND?8L=Ge#=HsfF=NmIFRillS3fMwvho2lQa;;fK*SFC$OK^T?anzmz z$bF^|UGlIW{~69!T%y5PL+wL0#IS9}xAm+|-^Dse#EprKhY|jPwdixqfBP#+f&=q9agtMe3cML5Dyq2E-M;G#b){vY>7OCr6TJ zBYC4j|A}lXn`1)TqOo66$R#=6B1YT)X8q=%$&{6w5I#V@*)PHIPd z_*QJtx6dq6sWPwXWMz#(T!EAcqBHP14E}%MihqxkKUPw3oLCG`Mo7jBlz*nsL7My= zb-LW1U1fL8$k}?im8VoW*c@K)h{Hi8Eyi8cjeJ}QDQKb!ZQ}?_^985XhGFH~ zBBcVV}kqYAH|3&*P@nySgG8(B5$o-=)x}CSC z63Q=&Vn}@Zdhs#_0lu2q?-3jg5kr9JpGIc_B5(9{veoN#GNH707Ftz5h-mgKAI-U4 zb6xxG`d2wDv}ED+j0)i%GHnDc@*^v)iP#=SnIVFWd$N&%EzL}?xVV+OpNa;e$uKKl zbxHmU;MdQWR9@ZEyP-Q8O#hryiM(?HOl&0+A*54IU0N&^X>z9 zu(On|Hol4Q`dwqILP}z)`DbEvbpoj#p+=q2_f#+2e;qO+!%Nv>kNArD3U3JcpZdta zuk{q-PZj_V|GZ}NGYVc=>Ek;Ws~k_l!#)-Wz)bl;MhP`WWOrnmuue8cokguIYYsJ+ zK11HVY2yxuR82Bg@xoSc7fPh!96Udd(q-l$@cip6z!%foQeMBM``6Yr?n zL!Gsch=DGA)C3D$tyLgf+;ba`91(q-JTVA9x3ye5d)tI0L)i6LF((NUHYhL!G;1^~ zwQKk`?aQ9`9XxXuWdTQvU0GT{!C?_w$zI_Utq)vTj8!ztjtP$yVC*VHGs=6~F0S?E)4+RQsQUiC)K|w>U{n>q0l}q|0_Uj9o{Rzq z^9pq6LmM>Pa5^yDEkN8aoHq=c$h37Usz5BR8k=RjR6fnhIO-eaoL~D>7RML`yn#_a zH&y}lEp!^w`pKXBjl79+9dVn4S)6(d9$P;c?)e#ZBHUpIznIP3jzFrOB|`EWwNOP&b7-&|p|>EF%oeyOx} zdT-yH7C(wbJkX{&49Z)4xY^Ps#6Ejx=aRSmxbHgs(k1F-FeYTuRU3C{!EMBVU?JZs zp$_yP@TZ%4yT|cT6xIW#N1mMY)*k-4gf4z=y*bPB4RmTag?Eg~kN60vH0qqXk_zjt{{u2-QM7B&_Q9xImpiERk_cs~SYm z2<5&zld)T2@Uz(Wgy!jlY4&X6c>(PgqF5=}R2ID`k0N8+bASUwug>IZKY zEZbxEw=X~5eGOGmnhbD5!`d_-9Ln$C%3){6*mQ=CU~uuV5j{5l8DM98L9HNW za5bc&AHbxeqF*vGA; zhQ*ZwkK7dl_aOZ5p!$$Edo1Y~Ni?7GyIPQvP-J+R;^$OBrv6Ya<#=!i#uY27`eKaW zhwcY;cM2VZQm5)<-YDF5kEXlskq|s~_B0C>W`UXt+?Oz|FO2Q{s zxx26B$=JM%hFfkLSXaIJkDmTMP0uFKu2|hg1S-Dy$wmiUNp@!QEdvsEQ9N^!N)r>z9(T(%57wUu;K#tD@?$^-P*&k~rNDy>Eodf@a{a2)?-|NJ*)$2Iz%R zl-oma^nKKP?*TVlYr+MFz*bBdc36SvR(AQ!i87)rDPI#{U`ScmNYkn7`VcsGH48BMxs zYI9LkUix^2&lhLGgM8ki`rCI^8qjPqOxi-KsJ+>XclVXYlTJ+&;?29R`-w7s_KJhx ztSFJA19~q7YHuw`0gY;ix%^<1ub$lZzL#Y~lX_mX;&f*|wm-u#o+ip_0fVV}=Lx?A z$AVr&A?=;%MSVULAs3!2I}t0_4kb~#m-lb;c#NWQK)o}J_XqrQ#D=Q=sOWVD-(#A8 z>8VB61J^5!uWyad^zU|$9@uaHM0ZP{2=s5nFMJ|Dw&GG4FV{M+VXH3++x3t{Cbe+o zVH}Qeax~Du2#vE7rKe##&i=+op%=)gq;_ey7J# z@6E>?yAf`cyz%wQA|9Vat9Rp+fKJ0VH)t9l??sb_@_&Y75J_xPzw~%O8V_D@aCVW^ z-m!|oTwlw`4y&m3e+5gb*)-Xo;pBKd{)Q>l2TpON6!gLW7l7HM3{_!0z#s28GOE-= z&A}1Q08?Uq--z+Qdg@2=9W-TYTHaH|RITei^$Q1NX?YZQozd4~t+`T&2VeMG#KBhQ zEu~Zvi<)b|T_?#t7?3TX4wIZdU-~Npf?r_dboa0+evjzIY%K$Mz_)dfqa!KUw2a_d zy5(e#r%Z2GKv<1O^U_MKsdSB=%*-FKaH{A*3yCC2D&={$2$5LC6MmxNEH7}`iQbWu z&UVoYAD_9aw60enG$k>7TWrGwOd{umXTRWwO4 zuJ~N)xR%QS7A&r!VD%L<*Im8%?^8EK8kyBa(=)W z4xz=bQX#I+RzYprPHl2zp1 zl=+@i%e!x(BqKH*V6q`d&IQe5YTt_VWa*M=i+teN2BR1a9`_EJm20h}PaGtph9RF+ zbr0&~4wJKl)?w-tZ-lDoK#~PB-C80Y=2^f^?uv`RLGc~`zmT~w@^3hXBn#<(+<#U; zAh>Na&9IWP{4mfZ)_oZ|l@t8F5@RGbsPFQ%>EzMF$ z9XKP%y!;1c+h7Uf3A&?e&pMsF{O-1@A(@`hZk7&kzix((N&O*eJ35S*DA?GShQqdRJoh47gOT@ zl=QyFLiCG0*Z0cUaJLH-+;p&A@l5^~GhgQsUTScD%5ZG6Jd_XVBzR8nc^NDMta<|b zjh$H6^U3#wJzHr8i-MhLfMh#uiDRm2Yl|PjsVQ@}fB$VmDsFz{R;QJpNBk3_l$LZz9buQTkR`}o zR6XUAjAgB`;ENSXh`vx(_3usy%W7pGJV9hnFcA?$d+=ny;l zfm*Ppro28Cs81t8wJ~ zcHgKyc!>W!)LT?Kd?~Vy(%t^L4zXHjXlf5k<(%iPu*c4{S#6kLISWvi%#4~vK z@I(Lhf@xl@Tv++IGyXg}@4TU%R?UCVdPXds-~OG#sWue&Rl)_S;0i0~9}dRkzz5Pv z`~FpqX@m@!rw9ci11d8=lgFkPB!@zSHzQ~82g81qSOTBmqVspuM7I4NpG#TJ4I(0BbWQ;y{9}*|sPuD5mR*LtQ(e2}x$XMtWfCWy$#i(> z+&JOo;wL;YxUJyW%Ue%zz0F!V@)e+4VTfJ8(Q%((ngcC=%}qP!;GA}h{>53L@JZCL zCbv|+pSc80&FRpL%}2iFP%ZSzXM9{wik{qA87EW0v_^e2T{&pVSj$w|FNf&#-?<*M zG1`<;mE-Y7Yv<5PfNUIvTKpm=NG|MlVf*z7R)mx;L~k?5SJlqqv?l-Mg3w42u@r0O z;O>5k*qEyxB6i9V1FyYw`nPOHhBX%u0#9OXee^;#Em*}&IPLX2G*&TZewAP%_08To zsTq8%LPaiHb51PoS0x*Y36~|mPk8Bg@)QarIZ%?l60EZ%&a>tSOsCubNyG}5JerT` zQu_>vu8&sA?=#iAeBm7oi?4%G><7pzh-?8mgr=FqRmBO2(1=_5ys5bjZ4{Z`0014) z5lF-aP|8yet&A?78ygZfN%SW-5ATbtr-~+hU=EhLY4soV~_M2>RRY0VLd{<^K*`&K0BprsOzbhFWjCEAHtBzYy0K#q3apYL9bID-Zw53~MZA$j32#EZ7 zM}NWR&w$3tV-#Gqca!bCje~u;s}lHnSn;60Bit85Z58VuD1c6{9$b211+?xPp{arV z=bG`sVdq`pBCog;248KhXbGz@?<^-|gOL6d|Gz1W`KvH@k@shb6rK8)Cc)v&QUK!{ zx5bJ4revQoSZE_sTaE8E4vZwIJnSP2m@ROajWpnqv+xTNl{fdgOWeWQ9y{q0|e@D59aV+XGjM+OY z$Pv+}e>s~_dy#_Fo3-z}cwIpL5Gl5vG|(p&FroZy1ENWU!-k|O1BGw2oGs);1Y(e_R*aV5~P*$mH^{oop!FKBc&DjEz07HR$pjc83!0 z!ue;1e{(~CqWtQUB?76U>ZrBfyzxn6c-9?*xX$a5+PnXAv=fIGB}96+K_JSRy(joc z@MH@G1vC!O-=JGVirM%_FLDxYh*GyM$8l-s_-{1Aq3uVg0_Yl(bq5yPA2aAp+=Ax) zKMj^A$u!HJr2+VLFoiB?Mzm5m-Vk7sV3{elr`cO7-0G7#?85Me_+?QMrmcXM!m3@% zf{q6q1%r_(;0&BBqWT!Ms46_nt5>jzWT|lEN2sufEJUp=!QWrbUkq3ZgXyHDcS-#4 z9yov&@?jD{<=AEm%OxFJ9=$gKpU>A4r&5D@Y^-dL9RI3z1f#N|UpL5><-VY6=6*P* zuR`mtyCkpuCleURHPZU9QnJ{)!#G9f8Ck-88q^;khvlezhm+RB|29SMDL+wH%61jY zraTkZgUR#sQ(U+|t16}ZbNW?rG^vxcWiwib@=^9LT?|<-xef&gM-TdTlU$uzWY(VG{hzK}{Rpw&U%cM605fSImhWDMOzk@-@>u`K&pc zgox^9w@Y{FW*5%~)c?~5s`AUR3*kPpPxVR3s&!&}n!R^L>@r#HO7R*x>pHd3_ayYK z!N8nA2Z?iQ36%5xd%~o2D&50vlzJqE!j2XdS9$4x?qd*@Sl+i#5sxH39?e^aec4zEAGcc{cwAF-86d2e7hp z_G&vPJ!Rhon=bTa2`HiSzyMGObEBF7idiT;ebOKuV8C}O!^yh^)2=f_p{_yk-TFpZ zCHbU_=B@V~I~o}VR)!;kUNCm9$%fG+BZ5~c<8xDHlxevXLKc`B~ksEIV( z`8?$bFNlCR9vBTca@Cr-bYI=y)^fZ)QyGTFnXCZK}M+fKp@m+_o!xdYR7z@q;ZYkVRYTe#6S`Qxt@^4hN0gsE9pW z2@Td~`crV}T?*)u!c$by$8W{W8kXR@s6?N=ScTd*h#A*sUB02Dlc!siFH;>T8Yn%xw~V!V%SwC83i#&B6nBWTSDzK2`S&l?8T;4)981 zvgxR1xmUDq6^ojSe~`kFgxOOL*t~}wNGyGC%#%$}S!aO?PmBmg!S|A7gPx>rpAC_g z&GyP-F9Q0&Rr8NVZL!-}f*ESLd}k;S63oyI=((1^p39Zc9v~Y)y}gjehfkT2*%m%=ue?-(N;R zPKd6IBBj!au5=%Ma$AIGVaSfij@&%z#~H-EFC9vauVb97hKE-ah;!Ta6V0Ea9yf%UC|IkU zrv*#h#_4%~9(zw7#9I;3=ZKZepbHaHaYkXWe#`fq1UZP01Olq%)u43gptN}Rcla^0 z;?b$Zzu~C};sxboJ7a1f1}4+WNht0P#o?1uile4Bh)?`@_Lq$dm%zl_y10nJ5WK~5tGJur0(+}=< z;DP_30z?%+(n#ZI(L*;RoFLPim0e|*?<44mL`vz6vgAMmhHF{~V=Rhj1=4FMTCH*q z67m)c^u3z6r0lu8RYN6V=b2;uLvk}te>+4dDSr&mhffBUlE4wDKNUJf1a`o!Ux_b` zv4#>kFSdz1pM{U-J|RDkDuxw-R?OmOM&c4%=ku4gi8&?-XEilX1)CW?>?~O`O2bJ1 zjPi{&zwXB9vI;DUlV#sfAu!gm7*0D6I4^lP2d#eLS29#U2!5=Ps)~KVqtxmr7_*S_ zS2MBOEE+@T46{+$v^pXV4(jbsNfiM`j_GXUM{E8*+^a>t+<6=X?ph4lwj3jB(oz_> zW0^9Nurq92jq6YptClh`B#0)9b<~A)ZP;1g#wNdnkUH0tmJrzzuJx*(M!{c2qb z>%RN%P7sbjf~OH>qnJIxBe_4zAi8b3WY9Rb_ed}q&Uu%{NgJ2)Bm!1S;y9?H=_Z3L zaItlBQ-kgGs?a=^4uZtVq>>6+4`pq@$k*V^L8Do+JI-dt==B3LwgPRb%X!f8O-Yb1^Yhf971kr<;|()q6dPAjwX28$$m_f7Cn`a0}S?Qk*QfGGzFo=&IM|WU}ow zYTTGpDafo_+l;IS^?cHih-0UvSGh5sIY6Y+55rdb#r2cSTkG9ZMN_}PurQ=h`c4*V zNSD1#>D&R&KyRu$ZXLKcKJ9ZX;}O*SXi2Ezv;xne9$S#Z|9tSwm_9Ua9XU0)=?3fB zl@~VhdD?Z`Zh@eJXA&MK^iGY>5z=ILK?2HcaK94is?fgn*r$aM$CNP|803$&kS>2_ zlVZ`TAW~0p@KD&{{gD9GLFgBo-6DRWKwSr+#OQAf3WwH#;)9cxG>S=3YkC@6#LgCb zqU5w+oqW92yl?EwZ(tU`UH{8v0AVr*62BClhq*-rDZ?%BOgyI|8){|)BRBfkgXcOy zG6pl0ATVjvu$Yz-#oiwavUS@na=d?}^LYgbBLD!R;@j|{py5~0vm}I5PO%l}G=}Y< z0`Q2SG_Fy9mTqlAF`zA$H!`wT57Fp9-m-~pR+~~J3ZAJ+_@HN|QeW3x`uyhgaPq6H z^_Eo)Ki1Vk8e5VT>dwi$E{J(-Md0)E)-kf5A7J4 zNqOFl)vYy7TNU3Uu=_j>UB@JMzID;Swyyp2Ee*-(>G4_4^NprkjEcGLgGCHd7%S?C zMUj!Uh9$M52RN5g$wju@rMP|q8?d1Dun-Vg;Ep14-x8hDL=LR0 zJOif5Ak>jLP@A0ITde+WPH#Rs5Y<$j6`m>5fi~SK{rsXIDs-7a#H9ZBHcu4 zYrC*g?+V4z$U>BDV~ZDT?va2#b+fP8eCC{W~78(BKKH#nU#ge?|7NG4L&kc~ zCgK4iiCRBUzuh^thWTV4a(mVRM;-BGi!H?^*cI-8lTzpH6GWtqh+LZ!5@D2&+qd&& z%*()g7vVa{I$)S2Wv0*iz_c;z)1r2HHS^8rkJruDT=uj@1h&Y6O2#z?sT*eW7Okuj z=O(Hv4A&Ki^zkm6ojwI(f7X)3UwDBC=j2#f*!8rC^O24ZUR;kyrWJuDyJI(H1ZT)% zFy8)eAVITgR(k|8@uP&yobCmOVw5`)D$&8@%IlS^K|EMMo~KNaseumV?<6!$a1RdA zcL(D!hrPT-J>?rRn$Jj6RtEOFR~K+d=zMxhPJC<{NYX|qdfby>N>uXCufz%fi(GBz z5bL^^w4_P%RQ)tw&}6cccWMSuT^;-LWom~A+@aS4__1};e2#WFvaUDq zxQYe|*k>gotV6J@mi>8Mi}R)tC`{Z^UTAlOsDoq4BzkiFt$C%#Id+GKX!eVlH&e+s zJqK;?90QQo>F&*G(?l2h%(4a)#7EOEC(u>S6=Fawb}LD zoC^hn1PRFBf?GTAi)U6HTgSM<^?EmMw7WLkaKX0f3kC$I(O7Nn?fNKV!;I1v$6sdY zH?y2A4kGWgpvwoMSC_`F-ZZ>R+f>4{!#%l&;kVt~R?d3eSy#kZCJ8b~9>$4*G*vNi z8n4r~F4TT8bZTpdh9e&rF&k=G6O7#0N7fuKkbPM-sYMX;P#nL zM4k+hkwa)?&Oq=y*a*STeOrI|)N9WqX2N^@ay|di&OFNpQAQV$$oGLZ`_OYCcmkd9 z#ZB&UCF&11dF5zRk)^&?Nce*R0e2TKz{K76UM_x8wv`LpE z9KL8pjub9>UQFTgHtH-`Wr;SJ?;94;OZw1cqg$}AZrEo$F>uwl%XhuqkDDw2li)6Wy>P{{I_TfG=ovTi(;F;U))ZJ z5V0>uuwsvxQG93x{wfF9SDB6>^2v8R$L65N(uZt{=ibs6yuzswA+&Ib0%1Ovd(_UM zMlnPR`afNMc_=Gs5(5Sp5y%sb;wJ?(=3u3j1jG2y=6LtKkWw=zZj5q*9O*>j&a2ZW z(R6*TAJ*M;F8ib9=4BT;op|ZQ*xM2h48cXzEYRgXqLpVJX%@6?tXVZCOW<-((k_Ap z%vuHzH(~APfPu|0`q>clLrFA9z5Az%5@~qoZ!hWg3N!4-*$g-FC`X|owWpJWSbZYo zDEEx~QMp(z>f!b_S~mJY#-|^=&&lmOVZqktTf-x(Vgyt(M)oyI75Ctz8g>O=?;*Vx zsIHeqzJSlYkbgfA4a8)3ovx1nxgZ1nsc!WcV_Tn5I6L7Qr`T(m3_k)sDS+E?s^8h1 z;;G)6KgCmyLBQEh3271$Xk7PecYbAcPAT)j7OsuUBGK^bT_*oLa`Ivu&C}3d+3Wop zVfgbGH5lfH8*DVQID{m|Iww@qiWuseKp0pDP@Fx`RZ;qh9)$};+XHh?`@~}g;0`)1 zKi~6+|E*UKkdV=S1C5|@b-urXkY=kV5^9@%Bk8(V`|mPxmQYnN6HknapvU`&=O{E; z1T(bIw8pwF_hE{qsBIw55n|b%h$j)kwAvV2^N*EPt(rqeon2k$cEQ>dg+QrKd;nZY!B$CpqDZvrS30-` zqc{`tTa@#@+Zx@}0^)}Q**|_*czBEH#wMVA6?1eolAV07(m0fTZ$b~PS5kW^MwHL$ zFA4vF*-{xa=Qv zS4uE*c9W~+AWdI=m(2@~R_SNB73s^Eu^;QtzmLN45&4?EiP4SY_x+^hU&ovtqTDBI zGE`~$YVHzE*X)nU>ThY+Kk-I=Jhx_uU zyokSP7{1|=*<(-9LHe^F{bu`={+fxvO%lCF|J03Z*WI0OM(3CYPdZckG&;bK&TCGytfd8KsZ5@BzW{)L<8$GdMiy3Aej8%GDen@|n7 zI?fPjA`vO(D=JfXl6grNSXnaEG!vgjq^#kk^-g;rbQ3H;RdKP9*Txpgfu<&`qS-3N zyP*_E>k!NLZZ%DBLiyd!X2ue7_ugIgEJsO>Vi+e^-bO~`$D?S}?$Nq~d&+}seD5hW zHna9{?PIsU6Q7z*qMs+mTZaIN3b?~xMmdI*lD3z&J_iWS!`et;;-*^p#CKz8 z&-5#DwLaUY=LMk~D4mJ;pa*Kd3k2J>pP7}>mYbn-u2HWUxSM7$AxeIecq~U+en2uy z>GM2z#R?`vABi+Bqom@5`5eUO+>BE|J}9PVhjQJ#oZ@oc3SOz(+9J=+d9? zF%Z1sSC4D;J)_T?8UnvYWXX;yvtI5&4pa|fneO8{S7=(6kk^p*z&3sj-KVU@__^wn z#6a=FZCN^cF`rFG@6kfVyi83}8Q;*TW|TC&K}XB;nRul)R{Lx_Qj<^th9C858F6T_ z#PWZ5vA06FC#Ss>ldR+Ph>QPITNNe5|Lzf?`^KHHrvwyzfE7DTecCOh*g8*ruI<2G`a-kik*ko!_sx#GE^NTaowQ z;Q!&3`p^jWu`!)t(j()=!V4e!rRyc;y^vpXF58d*DuPT*@|25t)8v5P6H)9mrKoX# zcDdd+O)*?w z2+qU2DpHW#zf^Lpj_b z%UJI{O&!*S8dGj|mj|rBM(rC+f1X}Fq38`nB)or3c2k__(D;l1!-yWwTDFGmPs^{D z5EI?)f)qjk9yPhH%ZJeAt)_^?OBEtBLhX5a!aUbvg|lnivN;>*1g=pJ_m)rkf(m>} z8Yw@tCY>$n5-FiV`T}col8!W*i{u1ziOBN-809!0{m3RRG30|p8 zi|BA<2CBJk&=Uo`A~@HKNy3A%im9=toGS>4}|2*}$=GzVlu5sNV3w-DWYy z1f$iZA)_NBpZ_nBA4V(%+8xO5c`M{k=V;kV!T6QW6*I(_F}-V!m4kIJI%DcK-$O?cR zka6CaGP{NRQ|h+KNoBB(WL%;-8`bX@w>y1LqW*lhrIg$@+ zoI7zAqGgR_#GpS1KuJ_v^kj#eJ@|_M=ds&cTvVNw#6Uhz>ooh&w(7YAx(XyxIE@TJb_5WdnHpZ;xVYk{?$;OJxN)@aa zAIlU(jRT!?4sEP66gN@~vN{%%{w!R^ks97b+8l=7cU&TbTq1yzlP45>(u4Wd+s&s< zyC*tP&F2IusnX7;e&;L6Q6(sZ*$r>+5BhzmFrqL>J5qdH&*@L|PmXkc9a6sZB$Tx) z&qcGyRp`Pb$IK1+19v6tlee$&{0Eb^p6}Kw=x%qQ0?g+i|NLIwZ5CQ-$I<3|cl3|{ zy#oN!jybicPI+G~rOJ#IDJ-n|74_z;*ss@XT|6PJCE6)S@BBe~M^bfOcqILG186++ zGA?Z<)vDTX2Iq(n8yjyakc#INJO3XFy*C%D?{UWsJIBl<3H3y_Je|r=L5Hr9R>1J= z7q&jHUf9GoN2+1RhOPqns-a+T?|;j))5*wEv4N|^PLO#!O{pjNTYQdW_W3h-M64d8 ztIDUnx;kB#*A2*^x4?<7KkPTrUBa;SVcp&C@j&<7xv&7o_4l*e#jb@%z1}CIuQ4~h z#291oILA533+LBJLFv@_l?aBc-=hP@gZeYziw)RT5j0n3VD?rHbbtUsPEAIzN&o-= z000000N()s4FLc{0000%Oi)OF000014>11`5dv}>NsgrO-|2W%>t2<+cR)n{C&0hc z$rdWI4&sw?;?*QhwCJ@7k~DS_pY&6R*R^qA0vg9=804N@rk%b62NS-R9D2@^umY}7g(ps63-FR~h*ns;p5_979KRNOG3{r(&FPuKJDKkfQs_HX|mxF73}{2yODQ9mL7kJv-{ zU-^Ij9@fA3dD#9F{%^3i=INQ!dF}oulTnuj2(UaH;>&Va{DOtK= zW=we%Yd?Tk=i5oQmwYS!_W6B5Y=V#phC{^w=uAL^-7R{M#3lHl_eVmUS2e0{@=Dw> zn+{pH_(t^ZyKU~H&JAkU)gcn=W9!#>)E^uEzLCVSi+{x81TLn`XCFQ-Z-ZDcscj_r zpomWu!^ZFWFylIaI6s?dBcHPgKr53SI;TZb2~2$j6A>!{#IW8y;{>NL0|0XKt#;s# zba4{I-`#g~qNAMi<%}RYH`x+hU)=)**degt*oBfc-SG*A#^VSIkn*JHjSCUI<2)9T zC=h>Y?GQxfnPY!2ohP45Z;TnrQYl1h{OT*o`dWW}7UWF6frAgLjfwb{ZA^33>2ri}PU^@o3PL%Z-r z`AN1hm__p0+1|@Ghxc!e5X?v*03nJn*~^Vh)T8-l+)ORML6>WAxI9alelK)CpT*+^ zf0FLzzb6jdG}=7JI%Q!_LD;8Xy`#Z|GB{V+h&>mtKgPBR83o&Mx@_fIvKq9LilbzNR(WUc5vj$OuH-SA9LsF6p$}Usic?{|9#B`N$41JV3_Zwe7!N%>|J>9NCSLO_*2Ip_YY zR=;559Q$>2s1fmqP2Eq65~b4_SkrKgh$YMa(x_JK<*#yJj^;r<4TN8?$i}#0aqlkv zO}4gt4Ddus`yf(h_HQveWLk~kKnL{FnsThGU`97-$~LH`V+4}&->0V4qHyJ0^OXCM z4XnTX&N#}&Cjk1j7rz=o*F6q1DVs>^&7J$O@9;KBwa=^aOedtOIbEFoxCfA zWtZrUSTm%cfrdL&Rli!8ryZNu&Jp@d!5Sp|{_bKd;56!f3Oo2sP$13i{QJM!eM)-qw59w_74ODP~Y6Kj`8ry6C4EIlFQZHu&S>lNVtKmaQ?f~qIUJRouwaR&lH;5Dj34&7 zjjR?Tfob8+o##$mSKbcEXw5-)c*EN?uo*@@Z{=jB<`*LAHq4|xCqWYzJjFU%{jv6q z3T+E;b~KPp+%v82+wz~lWdF#)`iHC$q=v+8+kb80xbHVeU+-n^;9xmV-h}K8qkW@` zhkefv94q_iX!8`WIYy?;mZml4RN9j-VxadXAy_>CIY7q0e)}1s?1G}He$UU9`bn_l zBx5Cb?tfDi9NM^v77#~MyCOg((f*{~kY``}={F5op{tj7ZacP!_xTHZg}EWL>=i&M z8}W?mJ<9-G!RZ6l99u!TL{Fi@Tcz*A;^9TIVIEEd4*Kq_4*kcgB2P0Xws?hTA;37_ z&oPXD1$h0abJt)kN^bymz1#+X4&dv$zn>S)e$9+>TK_$)N_>uh*n;hJH;DEb$ALQX zJ(43P-~<<)-OhC0M{B0Ok83HaB1LOv{rK8O$04>f{1=h+tOW&f&=Kf*Qd`7)wX(P_ z0XJ2U-F!yQLd#xyJU;OMofTcC^4&if+@QSEUuBL=aaon-Tvzjuf9u$FQqaUztH}=C zP?##QQ_j=J@AMBg48L#3(5vrPllqH6=F`4xZut>EndYMG)Jb*hs(T+ ziFXedTZ&e5()!gSsMmnd?Wh>_PusBF=6RaSW6OUqP4~BK!jN&tJI~C{^~JsPXyT+D zC%^k{J4v%PA9ZxGMBCj?Dl$DgH23(5&p2oE`;X{85qO^_CfqaDc2~s}G){h0(RAq-seQ;$r30NRh^gXSPN&qy%Y8ID0?grC!2QAM^{Yn zO6%&c;YoY-ajBDdx30kZwh8(WUbcz3#Su$~gS)K9u)e`;raB9_M}Xc{A@q=q7?X_4 zW`*%tBqjHh^|n=2WQfVnd64R#x(D_oOnCmCpIZ+N`+K&C*IvR$W{DS2_(1GA4D&oQ zqv_Jg_|UCfES)XO z+;Pe8Od~loj&mcRpo%)*9k+tzbFIW+w=CA1jB$##ADoR~n{6S;AZx#=ib6~VyAavJ zguCeC9o%EENW%tUpnBKN0el=AKRSJ|?=+h$1uhA_o{G7*OY(>tBKX0Cwq!$O@;=rV zy!YsUba0=7x6x^;d83iR4y(H8N#W9XyGeLfw&kH)K$&cPaU zYNX)y_3!<7$W=;M5-r!qvUpJ!za67%x>s_-mZC7Ci zZfl%v?aQqI1S@xU`S;*5hLWRtls!!gHW&tff{cvSYDxmK?rpLl)}N4$F+^0uA9`t1 z$A!1(;k9;fG@<7Og-SrmnG?P_o3L?nlVwG);z}J2sb!YGgZ>%7D?)N{aC)ASNHACI5wHOM{yR3)_1mIMo{+}4 zjiDj3AFuqZ8P`nh_-hJsmN;>qdc_kIAh4bUV|#2DQ3L%H-BNPwpeVf~oo0*svij=0?n=k(%_LCYt5ie+P#)vc6iJ_c9a3{g z5EKKHfU-VT*T|&6PSN*oB{Plm)xerb^nMmm!h%;lhN)oekNtv+Wv%#gZ|%ZWTH2AS2C=du_cJunIlB2uNi>vJzti!uV=Ss z361i6Jl3CUr78LQ;3jR~zmlPT`v^?ZsqO;QEMO z!fBnjIF5^w#lT6PSijStjr+%OeCY%$+=ih|Yh_(GKqIee78K@D-OvuyoT`M<-KtW` zQw49p31TP%hbdhY5njhM2}1Ax_k7UU*yhC8M=h{!#i1k6v?UCq>TQl5v!GLT-N@aU zZrhscX27e`-s;<02B-sG*g(sMrCeB*xLy=X0d6JVDQ=e+nY6J*6@$PcitpPAw#-lE zL-{}4Fg)T*H<%PZp%lh0)#y^w7z>)I)e%JiV2S?mc`Xak9%}r~p4^bbV{)K?z?vy} znoy%oKFuiJKOOaQy?eO*PaUcty?Q%wZGhaI{9+5$6~Kw8P~1mpgsi4_&#U^@E|Bd{g=U9 zc?XIa1mzGe@CNcxU?9a&X<7e!2tNNd{pc?^Iro3JMVgLV;oRLyGlGQl7q8C;7HL(W zrVDj|V{xOS6lY(E&Q91yzYYL&p7Ji5$2(8Aj*JnM_4Q|7FhnUqg(N)y37{8Wl|e=| zDh{gfP5sf->fF<4Htx}a_^twGuxeE7?jh9>ll}&BzFGYJ=Q?UC2!@ z?PSSW{<(d2FYbA|+;0rOKZR~J{jqGcS2DmNF@wiGTo_&YaoAdh18q73?GsVHq?AIQ zX%qFsw)|%^{*%M3jAn_2x}Ws0+(787ZGLV7?6RpEQMyW317UxHn4i}nHNCpW7WfCr z<8QINFNycv`mCb-5;s0-+S`6-AJdO9Hj()ljBm5D^7}IdeylnASwEjqL>i0Wzi0*3 zrH=dXyzqeD-kl%v+rxIX4avIV@lEgg{gkj{kVGEK!m+dg4Ue^eprR*ype5=`WK?On z+DxO{9n&yw$*|c0tO-nwL|I!3>tlDInaRs>t9VSpC4LhhwH@hR`DZZ?`B*dVyQ`Mx ztj&BbHBw6sXUcc#VJ@9!x*Zt;aXJ-)xCG?_Mu9qVWKCJ(u4#*d(!zhbx14hU|IOCUycRi@Cv78u@Lmu3ER^ z&fbGS5{Mi!*+D=vOf$-s)@!v};TReBSQdMxAJ7fUz`_iite{NPEr0ne7byaRwEafn z{eCBlf9#0qJgGDvwZo$CR`isvuZ`NO+8)k{C-g`kniF25{rkRa-GO8?NM-jQr#aAV z;!jQAKl(?zA#Gbv*(4+`*ys6Cl6TECx#%n^`cFp_Yo zwq3Cqrc^j8886W6mVe{xBhRaIrvO-GHFurF!fSH5jQ78R9l35-j^EZMU@&?{m5y2! z47=gV3}N_JzQ7;a8vG)oxb&75;0(U+?vtD6&Rp41SSgF5<=1*b-pf)TAg`+rQuH;J zu@Up#fc7r}MfLsh_%lBdnbw~xmUp(bF-ZesR0*q&b@=lxsT1lR**6UuDjCZW2W)X( zl&L5MFE-&9Q`Nnsg54uFWFZDbuLoB`UAa$xCaGB2{VN5uChy&=Los7ZUNx6U0uWvE z&1@Nd4`!$c+d;-^vgF7tMV6z)d(Fn|%hNbZkZXBVEAspW^@erj9zcfh(=joE<7t>p zP2|8B(d_YZ=z@7#RpY?U;F8nKZtPL6M@pRpqD8OeA&ODOf7>H5c~JS4L$?k?1n%2# z5xxYx^~VgNYw(4^V$4KMDYC4BfV0>Ef6q7c=mIg{S{ksB^@4 zr##&i2z%_(Y)jJtb7HocJ>t5&>j)CbYHiKMfQ-mx+R@T8#$_+IvRX!lZ3cD^!vOj9 zqeG3Verc1KZp5^pdSU6k!=SJ|ybjZTY5wyL47FZzr+d*JpuDnXn_YU+#l9^shvoGl zsOT63d4i#GCS~DiRSj8%zx}PgeI`Q{8fM3kDJl#z1Nmh-2+Cg^7N8-Tx%Dr0=GoUf zU@M`8(A$)cBES;$2xjqd(pnH4Sn1Ck2ulC*G55{nN%`l` zC5tgM5#+9f3ZMIThSwJ{<*|DgslzKU{~NS)+J=3GO-mf#H&}`LzaI6;hG#z#4Q)_h zjnNDBvBW}QCp$|cwBuuWsGxb}GSoVN`L>ZCd^`;NQ z>Vf?}@cSzXvndi~FUj`m$4mi$fD^k{d!Vd~QhHIt`+`CFB0kGq-mUu!*MV-_&hP3LvhCO@ml|H5 z*GJPw1n^ItDe-;`jZ4wRTw?&g#isGA8+L1a4VD+AlFH@9NS;eY>5R#@p!OJHbH zaTj8MM9%69r5uLfywOhx%KkPw}BI4{$q* zx#{mX*KwO`1{!Mh>bA!nhsETwc#=I3F#T5CRxiyUm@h?-hxUyxP45GwWy6@c_@caV z-WKTqGB@!OiX-!|#CQ%mc-|ViArqVv$4Z%rOlEVbQ6&w)HF;cEGP|~wUW>hW$Ain{ zJ)>%q>e61%tMrY44R(!`a!3R}Gb0%-m!QgDwA9zz^vIy^GUCbef6uaNT)HX^vsmuK5ahNt5{XxMQW7 zbK_8iGqiQ1&h1ArPLXyvjh35&_;7pfh2!>`I?-gq_+^^XELw2!;DU~akvHB?E-JF> z6Lu0!2rE=YZ1MxX_V?mZbWuueNJTM!L*R$OPdeD0R((|Rg8Xh-PAnx(725X2xZtw{ zDZkItX;2?SN_d2S~YQ^HCNAam9 zWZ7jkTb-O`kYIq7Fr{vJ1_jo!offo{ay)A$pZOfUB(DXAFOtWbsG_HUZ8bZ4$>55L zch_nl5N4Z8%|%2uFiEn?ZY++uIMc6#*L%PlJiCJ@QD+uZxw=S(gs4LAZn=Kg7*mY4 zmIvRy>sC5gQYWok5bsmXV-W>$kf>^K5{BPQ=}~Rhh3A!>aj4@y5f!T92w|^@EhT+K zhvhDzR_}KC1cn3*?j50-aC$>dnruo#!0@;5nnQVTeLor81h<1*>G|QZ_+~a zZbh!{m^TwxJq+7$3omT0Bn@jn$%2ZgPSKt$S=n`Hz_cazf}fc!E8RhN$Mc%W)}jgQ z!ZM*(?^mE$wsa*}Elx)5Z1q$`N<|H!61Q4BUfyc&9{0d@*#R%6F%7{XIH{`Z+y_@E-o}6@t1| z^)-n@e!Z$?yYH9HPA5-JAuD~WpGrmzJYMHgW!fDN#{rk*+2H4!O~ z5)6O(TxA-&x7xYwi$aI;ut!;6p8WL5yU9^hGIYA%d*tX5ZbQPY9`wHLIzs&JYgkNYhyAb0Dc5?4nNKNjCmUBx5K z^|jE}{TBXO$Ucpm4ODzD=FPo7cgzqF76A={=tTbCXve#DMtuN@gJKp zjY0V&pv4s!)iT&@Llwj(&`d5Vg7J)yXyAYAisAr2LSR&jO=-LP#wT-=V5%`Kv4(XT z!H(vFTvyW+VyAqqg1(oaEsXqTc0eG z{+(0%oWvEL`f>BXz5`r{6+HeulzRA_owpP?`!x&xvT~dQ90n6Jcq|&*yUp&HBWji2 zq8vrbzwY}JzTcPm-rfX_I6*Y+APvlY*uv040BTi<=L{DY(VeO?7w~$5)>?h~KCnP8 zMhFM9X-aNP{ZOF6Zmz9ilV84g3uyE#DLA6TfHZ zA2#m6eBsi2VCo9}UBMjMcB(MAmo*h98xU%P3=9_9%=bFe7c~Ou?SdM3zlxh5@I=?iM9$c+@85m}kk(y;CM3RDMr0^myefMkvIcz8^)03vMQu`K+d-B`x zA&f;(J(vf9i*(?(9%Hdng93G-=W%%Bp5aQAQDgo0aO}l9+G*mZYg{8k7dgy99P^0 zNHhPe@lG$oeOBjIOnuRx2N|kCZj@0;B7GzM1wJ#AS5^_+~3!Fdtsf93uc6s=Q#b3Zya z!A78)i3$PX=gs2f!)`!0Qky?4EZDhz)9~u#{#tB^lkkeYrt*L?)&n(W~$cIobj&+3kgMF^Qy>(V5Q-`mu1L7*R?%9Bs94=0hn&s?dO+cM;PaQ z0YwqTm|zZS04(K!34!EK6N~KhP7p^?+u}0P>Ko#%e>sRR;KP;K*~qpsMP;af?8pH3 zmg%aS1oqp$yUNmm)|u$g;ID19P5%B8JA4ANUVtk$KtW8OV4Z2YI{s@ZJG=erCmH*Cx1o^j*Nc%O?^Zuy*mO9h(^{q#Ue9$qN zkb*`PG3Lf<>|`kEh?YqhM@&1?CC^!)_>G=;yd{2mVN(YY;CWJ0hUC4EF5(Cw@X|=4 zAg%B%ce7MaGiLUNbg!+gwAK9Aoa^n%Vv1J~5mmpxstqdql=6Ay;d`s*L7ZdBer_=1 z-RsjNbWxnbM%u7dz_UryCco+`F_bMZ6rllwx10wibiv!59Du4o!ydjG+9UccOn9t& z{3qHf+r8*^bioHwbTodm>eJTqM^;c@N3qRIz46Bz_lmYZpZTi>TsYS_GMTg90F@Wp z)zAVivna>FMc04L6zY|7&sVi;tNtQH^lr4A&O*fZjUsbfEFwr~Q#J9GU3Gv7#jeYz z=dBGOE<&4~FCvC}_xG|OOo+Zgn@cX{e=3P>5~1sZ!VCu_sUn%h&r^;;SQ9J?mv2<$ zX$%@F;ZVW`z8^)*T?89$6(%UESz-1Y{$mv8}_Q&>Cd z+jIFYox?7xZ$6)BS3Mv+;uGV#0$hkY>n#VW__AB7i)340nl@lm;EYlR1UG3 zA9F-;-N%!viT;dC3kYAaL9_KUQO8k4qeV<0FQSg z5JgHQY~T_pVIyEI&4~@|a8OH<9U+Z?rkM$)%w#dnrAZ5^4Tvy_Lx6mj*?}>TzG%d| zWH5xxdK~oONbg63_)%@!OsVtEGxcnOqg*FdpHN5rQJpHjI@v{uu2Ie<*#mv{mIkYJU1| z$Ty>+)-4$Q)fU@Vs7tV)L{y34+v>zF=_W6u1Z903U+hidyD{8NxH`Cji@X0Y{e{Fs z5_>Ev=);gKOeC-PR(Iblf?H12E-Kr_R>`h)nrN}!-Rh!Pz}c6k@8Rc@H{k$$doXr? zqufDe7TvS^j5QJ7x`CbG)oe^)pa#F@_~<8B8)CFf^#RtFL>D?-wc3DH4C-T)->;li zinj0mqz{$}gcSX)IoiG_2YUFW_unYVTCJt+Xe0J5GvpKYfvV(uH?ns8PLIJC&FCCbtlwec zUnj-4$vw7vxDE|*Q>P(Z(n3Nw+2gqi|LR&1U(AjEh8;xs?#@Ew`rg%6PI6Bn+m_cw ziMQ5l{^b_Ya_ucFk~^z#Prp@?F!;b)Kv+=>4cag&*u7k)5X9;A;T75kF?=R^tz|3P4gPSRTl^Vy+YQrg zPUkf#UsJUp3YVBJq1Y@N&}Uusp~MhJNFC}k=8OiZ4dL=gvZ3e6@H!h?TwBh!=~b+dD#&USj2a2!o;J!bvC$H2mRf_Slai#xZI&pO4>_|F z*FpmgAmFV|ysEap$z|~0MByf6X+oA8Q#;yBCeJ&Cgp37dN&;EWM)2%=Lg8Q{1sRd? z_=)z`^jGw8ISxWjA)Y~ELeh6Eb3Gttg#_yBnepb6MHZowt)3)M{Q8}K%K-tJg&P0Y zcd4(*pv~}29=tE&ODq__r+$&!tE?9itvAZlA~1EZnD{Pr$bZ$Nn&-OP?tGAx%_M{s z-Gt3tBt>PTe21)mn-2%L0_Bk>hFljZ!nYup;nH-meTq75N`);n(Fs9I*W8{}=_m&ZYCY8k1Wk)VkIv9MX#uWIE^vHU&y-=+4_uAj#OT3|xjf+afLkiu65ed*? ze2lbx2xsjtuL~m!hKG`Izj*+wn@oGCaCN)nv1uel2U~j?51)kJ_yhl<$361TcHrmj ze7>N%<@nj1i$91@KOFvK9XIFaIbvLFPnAUAuofB8$@|i_Y@`R~VNvR2v_nSa1Fg|7 z7cyak0@AS#bbyIVV@#{3wi@GLp~tiLon#h&q$PES@J9FPj+bzSYzmVZf|fi!AV?8~ zj%hA<>JK!EiKY(&@jJ?t!WmE{n{-T&$SBu`i8Xz5UK^CKBBjJw6q8t)#6hHw-t?L8IXuCzv1-q!?Ei%#fXl+Io;vYCX3 zn(ihCI{7pAKd90DQ zl7uxy%4)YIEp-}jjwQdc$4Aiz?8}#KZ8M1aV*+S^l~(=c8tyWvV=ss36Lsx2IIqRr z1F=ay*kO}29OFv23FXOGM@q7X0e6)rJ%xxVz|br9(IINJ#feRv-9{)_=F zLCGl~aIVa<8@V5ZJjbIaV;aTnd>S!ZC?p98vYsh;AW&idj1`0S+f0gYtY!9+s|jP^ zGzd~zTIENNham}r)heVe0+NeXwv9e>-5$+t$V?>kasA1bS`&dovLl7e5~>_5p#7UNNwL{iU0-CtRu}^iD9c`%PKp z52egHn4l~&e0Ti70+Xkwh3o176i)xHSbYeO6m;vuL^VWZupu$Q!0p+bT|Px{Ct(<_ zkwtI#TGp^_+^#*1?X=swb$kep;YySdt9D77KGIT)(H5C`v4?i|4=L(h zDFmI)*4eOIPa5?FEs6MgkIz_SHIYIn!osccerD;#6C48*2VeOv^IQ5W|3J3R@mjt+NQ!zQiD z*{lZg6B_2h(D(9r%}wK|M}`be4nZhkQlkLzTq_*u+QvJ>_p!ZZPbShAX5~fC>$nL_ z^3VG75;ZhVj7fD%XGEl>;?RFsjci=*qB+IMiG7~dXHPP;I`5}N(ZWl6%)TDPH7xyo z|Ir5VdPLM&#SkT<75=w%$3^R<9hS#~0Qfgp&8h((&h&nNxqy}r?Ajuwk||?x{E2pi zT4;WO=Mhhjiv_TLdd3(S^JGd2o)Z-(^)sJCoP?yP>8r-GAU_s2aN z3gxB`4~m|gqrVh8*OyTKPYBB=4^G`U50hkQ+Z6&d=I0bbGS%9SxVSdLb%8Co)A;j( zDo&7_wP{c+e&?R+Q(_S^&h&jhQy`(_fJ*sv&0Ud#Xp(t<{hwk)LSA{zyp-;v@ zOQzr$7qG(m#W7v}4p0wRIy8IFH^U}>Y}r@MS6GqY=RLY3jQ|9_I0F*|@V9{Wl@xV@ zHxRWQ<;*n}jIGLqwaWttyM!U4(7TUzQgmrcbj*<#o|1zP{f-YK$-M(b>t63EByJI! z?N};LY;!|PRt;-)i)2KpLOQ9hPI7T;+SWJ?LvT)z(ZZOwf?Zphg$~JbVF9T0RIebd zw|$esOp}$a%duH(`J<7E*Ie=75@2s?Wa9HyNoLl$l2@zjQB!s5%rnm-v91tu_#d62 zO+U-DG->yPok=)E&;^W&VX@8rH1>4v2y3A4w!x$E+56sY`;#ynG{jLjifWw4DxWfP zN{lMhZ@?!A8KQ2uUuwc34oxVu@@-}2XcQdleS%=F!N>nqfg1_ilp`> zDhR+e6`gfMiPoc*AUm}&$MVp&eNFq8WZ|6BPK+Qng2x=er%_E)qsd3Fr$6N5R)UnE z=16BxJy8|&OKWkyP6E@YztB|*U>QOlq)FcNUYw04VXq90u*3wLuz!g?VK2cZ@K8=RIba>QmxA8Y z2cYt_qii!Ved!Nqx0~o%KHL3Ji|FPSa>sG?8FlMAiR&%H`M!pzud&cAme%nPYjjx) z(Q$WB?1w*s3vPCvv;pCL&KRS&S-J&eJB=B2G)8_8xAa?vS&UA7N%~(N=q3os<2;h@5fX!M;^jZ$^Y3ez z`HkhOYUDYcfU|SC`oq{V1;LE;yjdIf`O^YjeC}%XA;2O=ItNg{5aW%qJx#$UbabG@ zB>t{Z4pUdVQ+c~}iKTIuUK-Jh9y+xBOrA6XfagnyX$@fe6+qq!-ebs=Kb@{+Y`(kww-oEDP{?G0AlE%t#<85ycOWi5A=d1Ojp#5HQ^U8}cfr zYD7^)6bv!KcFbP6;8`UYd*gKQSAsmax}-xOm|vlq0ECGoVEK&9(sGz6g6XEMDV!%8 zSVR5uR-xOFvNFyg*%K+-0qU=9(S4j3-owy)V5Z%Zouz#Zs~$GX<_e0`Pw z@s28*r(;zu{+7G!8z3|!5L;B>`uG)5!e+^0 zUdP>MsV9HpxY7bP&mv5?TYnlGeL`_zPpftbbGJgG?Zeg?Yj(QMWjGCosz zDS38RjHY>Y75%!UFY;6h3&RemrU+ z^^$FUZvEpfWHkc4IxF`y1ECRe4=MYQ;Jjt^bK2ZB%-zHAOr z5_(TdYh6%05@7e$EOq6&hWMju5ywOd-BcaHFFZXba&^*Yd4`i&83P!>BDP|ETZTvU zv>=Z8ZVeb3#ruRlWBxKspAXx0y01iTiQM|5KY=mDU~-mAPR*_z4j;6Zukq}> z`Bh7dUwm9;2MOg;!fo=*ONI!|R${vku1=PfID)9-b3wNou+p|`sp>KENK#f$COWLg zm-{a+KIASAFQci2U+VnQB;uz{*p@2~ZABn{2V17BOco?G{G4#<;5E?|6?eta~$HWuD<^4^S!-~?_3u!$)2oCw1BP= z%PfOMeW&!EGH-661B>O0*>jv8b@?n!i>K=_7g~%I_Ptzp+nF~R(TTCG=gZX3b9@b; zacxr#T#-xgxgvr4?(>sFhO(akcIwIPdZ^p{>VVcKEXs!#-SFZKx%RrBK-N$7}cWmqPhb}7P@lrcjn9!fOV@6fG@KhFwe)&90F zleE4(=!a?R_CmAoDQ$vuKWv#&);j?=xFI*gHGV~Q)P*16-J08QBk zOc2>9%|Rgqyy5ow0erUA4MB_I;s@E)Tmf}*ES*g^G&6Iii^lHXvh&bK+B{B*P7YBWK+H>qo6mGuS!p@$LOlf+glaUV7hu=XrtKqpne#pl1Kt(yUqqOU;~ zar2QLRw%;ujd6ohcUhZT1U+ydh9Gclj2L);0X(TQhjrv-@v=>J-Evc>a`^tzY)w|_ zR*rc*ux7l}IrRNBNxvZPp;!|g){l&8s5&S{pyowV0X|y#HGobX(w2J?G*DnC?n0Ku z2j&y^$)n97>4~8t>C^gu>bKQKpJ|4!R!8SW`1tF|U_33vkT2>g-IR)(ecWDlzMX9{ zc*mxf(JgJAfr;sGx;JMgb}c(k&IHCV-C7Krg0h;1y^3cvE}AyfIrYM^1d`bDb8GZ+ zt~++tDSTQPpiiK`A4)y?Q?S(O8+8SzfLU`p|FCIY54>vEb=Yk`^Q*@Nh%58qO09Ru zRz~>Szn_sF5(qr6511W)V%Sf+-r3H;R?0#G5amvfjqT2=&&3 zmK{-w!(bYen|;d(#}A6rEY+x}AoW6*kiI3E0_F(Akqu zT8DUMUpH3nz+Je9e!-0CY8tXhb#?O8 zwP{uj;tSV4amGbtdQxO~U|DwKWr%hjR`CAf04-%qq4lLBtyw=f#yk`M?KO$QfqxH) z7cT2?76t!Q`ey>Wi*~S1!ttaQrT(de8P90q?vltaC#$aBi_xqqsWqVt`$Q13piN8%PY2|6X}@Xe6XODCRWQ;zL91(LU}a2YCmu zwHaozz!Y0O>L-7m&L9+d`mYw_PR!~^OZGE(50t3tuI@hz^42>!GIm-MwPT0@m=}N} zNr-)H(xeCkmu42ctQ4Y8Z$gc%&JAAfl2NMzz3QUh2_d+HMU@cBfQ+g`}3uIvVJzpQ*^#UR;;zk01sBfv4PmBlMZ=?ZGaPuS-MhVE8j^gC3Hg9n3; zdfw|ww?0O6&FPg$oDV6wOze})dsDJXy6y!@Y0kTptRX-w5s9atk~py#_#7%s9>PZR zc!%y^Ozbq!E+M>Tr`qn&TS9Ibl#(fkxVG-J=;2sE@%+@mrrC_(SGZT@b!U$w>iaBd7873=jpbyo z?{}5PWerm6HiEac8^-l9AE@VPKmrIa5X3-_`08!p>QAD7Ts#P>w&D`>Wc@K1ez!Q& z5pyK1R==JwOM&^;Ej$@edS5w7QA>T(9tXhCMjiceg`+j_m8{RFjm!dvz3@@+ zRyyba6YS68v^amc*=L9zG zMh22?c~}=eukdDdcWGdJKdS!m|A(oAN_JfTb>)p2^ru;4I&Y4BrCU6|D4=%Kg5>ew zE|PeNwBRZ=o+n)yKM9PoS4J|}QY{j%)HdMTD4wJbvU^VRvV-{36;7-b@Bi_)3~NrX zLA<1?5KL*!{gbJE@J%G9i0>v)yR&r82-Rw|{1K&n@&~*i?DRGJ?Nx>UweA2`$lV4y zHQP5te4=8ISQKDuyilD&iau1s9q^;uoXa5%6R&gMMYO%;yS3pr3;8BM#iVkjEm~x? z&W`@!LN!QX{C-IUngHZ)gEf*sxce>jr?I#3+v?c}#rw`Dwjfq|tuNn6znYZCFuOIa z{&gE@W^4y*l;@6b2N(IVbT4gqJcLq&gi`jyzTLSw=i^XG+nsw|20N8lFfyGc|YMO zsRJbd6`+T<13fE$Tj=xK{|^41$~v2qTT3f`L%qz{`{@$+WHR*i0NEEQ3ixJPnEf$$ zFio_2KZHK3JeU4*xgP)D=E-@hCHij#?Fv%(zuo@*&sdqzn5~_HhFwZ_HW%3To--<& z)j0D8Dt#53+^kiIQ6ZJf#fb3Ij8QRUYZFv01Z(;o4qY z$aASVpQ+YsF7ECmfGr~VPf3~cA~ znvP*MwaxbrF}{!VtC;d)OFGH~rhaPb5ZOSA65%(rvC;xia;N;62=fF+uQM}X9Iq5J zDw9z~9tkq8b}*KmOsB7pkSp=idnc2I9Oy;K+>`}m4evdTiSAXLX*Gd4x?5g_RS!^i z`5MDeYCg2Ap|q@#rr9&YY-MB$PO@1FSIGNLoz+)j&wvF%!Ji94Wiu8TbFu0tT<`)8>&-h`BRyuwwf5*@ zP_4Hvxx#h6K6g7?jeVV`!)u3#8f;d?-~n-)Fk=>>i|{OYS-a03Mthd0&k(b&^HLVP z5Iw}jU#D~=phi)GPD`vc(M`jmrFK(wG2>XxRm|u!*DgQ-UZAOlETW_^sXyeNH7itT z=UbGi`;;u8AT)m%Cj|o%VZu^R(3GzQEs31*R_O+mfngYd2y63KOEb8zurkwW88NS~ zCU9GVS_(A96!^~25PK11r2>=R-R)T|78A>E=jLIHvaUk6n-45!w{Gnm@XSxj7iQp# zYWC@2DQ`!EtSA67yX#pgTo_3jNNP-!@UY#&Tb->*ayH+dZrfJ-yFXhGSBG=8^qL*tR!VY^x5 zUIOe`F2T|`$lTGR0VF5|?}azBc2WQK+P-VsFh{f~3B6bB3# z;_+Tygz?}h+HYjf0jkf5{K-vW}&MZC%?p!p1}|*0sWow+UEzuZ%y;`C;tpKf{&oPaSF1 zipdjsYPnFIx^T@Sg1yDN`_zMo-$DuW7v`14XY%939na6iZr;|uhvrkp)D`L8P$fHH zK{9ZWrHi2ji8CVV*I6#+`0#n3iMBxN0peS zNiug3Fj`-vnAU{DX{_=;11Sn`KG|r+l`N^S>Fe^Jfn9{1tbCoV9(=AFl+nF_&xJHP zGv8kZM4sb$lNjxnc44n|8}x~Q>>A~r=kRq8Kv}l0mH%(avKK(au5PuL#&Vgs>#HLs zD;0bC2Gg^$|2c9+wZCsI_Rlzd+J)xe8dt<~zR9jzu^|G8vO9Qv^!qKpp|yAzBYN=T zO1*Y3GSr=!snaVb3i11)KZX%_${f^lE0q(z;aoLfOQmJly*C=mIu@dZ>=00I_?eZm zz3h5Y@s*QHB&*#B^C~{&T~A})T7!D=Dr1%M;%YM^B*Ht96L168)T^_;ADZ$Xn@|l6 zK~V?-i05B}#7ehoMBD+(vNaj^k1;%^*LaUZz?41@Tmi%Vn?=r^o;7$XmDV?0@bHys z^6~zI49i zpzZSDVl@VaXGAkGc*97|LU(BIS5~3isC?l;CcHw{pSaR8s}th<>;ik6B3&ST{1fon&i;d3vbPD+JG}U+{8t`{5k~ zr)=WVdWs!@eLs%=%pt(thMiAr31vuiwUhW|z#fh#3~O=WLL9z9@YizDF%DyC^H_L# zlGKAj%pMlP2=n@X<}M17?v_)QopX$U2&Fm+|I+X=Kxib?onIjro)o`1jIE!E!&YNt zLjF}>lIWFtz*Q>Ht|?=Y%aB?Y&A0c>n<4)+e4LV9mkPhNj?c{!aUv(uXzHVDYvl^8 zqd(^}Ti^3&0+sj3*n~|0h!-fIN?w>Gf|T2@))pV`kl6~+Tz3ae*}0!$wm0bucQFQ% z#=pphCEJHJj9xqHa#PsuiI<%60DO4&UoEO^xkXmcc*7V*KU%OwTjj`OAaAYf`T(-o zE7qfNa{4kKPUlp$+|eZTmz;hdj+132<%Imm5+Egz+Ipl)zn{Ej@Z&JM0nOzENHK#B z9G0x}Yl{(GB(YS;q6eJblkD3cHyBo|UGNhStZqRg2~jNac29qH>xkYD7#P?9NxvQx z2i~}IyE<;|kLl~oJNUJ4w$7(?9-AnqJh(r7jCf_IMcy5nYu=Y1Zn<6$OWV@a4@d_i z(U($Fej5W0ZWTA~on1j09tl$Ugg)n8_8v~Vz6Y1ytLW&5xG1=f61b>1$K|3$>2L4P zXpGc|mI=ciN?($>ao|2ND7Atc%3fqy(vY8w2A-picKV}8s%oAUo7?mHYb@D3>)f2GL~I7^3sxnmIcl(=I$q>47yspI z8%^6Mp<~fv%}{U;HR4!sG``Jba~p+Z4kSC^v}tz_{_^>~+==3UKj8`<%?OW}+Pok& z_njA}tv}9TxGaE<@ATc8qtd~RI$D<{0^|SYg@SrEueU)CKg>$k5pHTu9Dr3o{|~2K z?*7wwym_HKKFh+___Ajsg){XW`FX#1?ayP=n1gP)%D%z{cHNgn{PbbmWF%@U5&(d4Y~tFdl$yx#hPgy(+>g*zg_ z9BXq;G=1R5-NyYXH_4o)3iOjp>oO%&ndcrzj$6elKFqb^!Qra3QYHZsO}>38n#>Wb zBK`#fjALP@5(8KlQ}UR`QCdDDAlTT0CEVif$&!xAe*SpJs=ny{0p?w}G5MsAsRYLP z6uC2y%@1VS{pF2mbtRo|Jv*V+Oq4pzbjJ}!cGN>e6~vvJkQ$o9d>1NKq*!i~N~)FV zsYT&jI~v+2Uk`{;Wnf!4wP;RqbMos(pMlHQD;;kToO0q)Bs=GREbRZ9$Nc6^gFXe;_u?X2{7j zs2Q}a13P5@hY!k^mZH~2(SgK7BqlM#->FrXMG>l0+xkaY#qAL=FS&x?S6EmN1EHV=ZbI>wb+6ZkdMuIorn>wMre%*N0yRc}y_?6orS=?%pW5b2~_l|WHDwCj9j1k@}>PWLeWw(%xs-F)=J#+BiWeJK~ zD-{KGYy5@t3c*6ODlW*AOc(7%X>Zxt+zY z0jljf9-%$0c$N_>Jz+JY+5-ep*r-djE5}frD*Ku7!^@xrtotlmLynIs;Ld6iF%Nb* zYge-1OdqX;=Uu6`gb$||7dYhSk5Bn6y+}cWo^r+? z#IcvYkKTBZ1l~=u~E;PsNa|_L93JYar!CYzM(G-4=f7rq_z_NN+tj*0`1+DKZmK* zm=*bT$b4BqW+cz1kw*5)uYt3JRSu3MDcB5>yU-+y0U^Ghp{6Y#cD?e%x(i&le0(C?3YEQjzF|}FiE;sZR z!If#~X}O7lUJ$KBICUu#`l>h<_@P8*k6r2wA{(Zl;-Tg7I`MeCabEeNedeZec2X_oa^G73=>VA;>imJri(g<{}-y@NM3&Af1t(|B+{>k6-v3lRFm zYbBlhztgNQjH)&<)qdl#cV@zh(2qU4+{uzfDY|e0VmLL} zb(i?tOD_m2Evdx(!9}DDottes7V>9Lw%*{;wOWXTndaU2*4#l}L!I`-tHxbgCsXeaTOtS*1bxK`;R#;JI!Y07 z(}K;JVL_Fxl1%zSHgeQ@iW%=u{vYXTj$WEa3@6Gp9HMS1RKTs|<0NUM#ZGOj*8X-l zHBZeP9u}+ek7b-?49l;RBG2NE`sJH{uHBCeptRKyGY0Y=Jhz!lJ{45?n9V7sl4WKZ zY-&^}Tr>>XDJq*RZ2uF4@F}2Y$X`lC|HdInM1=_dG8tck zzc(IWbt3lrwL4;v4DW~Q3tn)@a5mrHY;e!5YZ4?Ll(6QB_eA-jYe~-?IGkO^5iOpN z4B&;gJzEd$OZY?nu(AnedA~+Os)_G;40Pp1@$P%GZbTZ5bf3lBH#IROghtFVRa6TY zok}#wl|jrjS7<$lDb-=AMby%2eo_r$hEDTa@O#7Bf8Si zTg2l^CG-tEjC|)W-&(Rq{nWCtDC-$Q8W{|%*~wZ-vPS@yAX1e;cHff59SOp&PnC}h zbJ_k{dDtgbLnOi!6*NjS8i8}I$6;0DMR z_iDQxjr>RfA-OWMTPj{l4w`dE_lgN7*@PjkOq%~$+>7y&sD;&gGkYIpM91+2wW zBP~c0@RY_yifU0?v4t*j%XD}%#*Hi^SbRygG7FSuu)$GhJaIoLL^d8k{z6gT4BUl*&UFjg?H`r*kT$q0Qo13U;>Skcb zfyq?iMKvk4r$eGTEL~UcTkUroYf-3YLdoM;Y@4n=h2z&`zAU4+%|x|w?wz>D{}8?6 zGL@HDQc2SRdJUn0=}Mv0tqilkkhC#+L5lpnv5s225Ek`M_H`0M{M?+q5T!`+q6J>p zWn!oDg#XZ*gMMXYYUyO`f)d{$+-z*8$!Ht@<=JUo{F};3;H0}8b=-XM#p;Uf*!40B z{PHmx<3@e=EqTe$7$M~aw80Vk5sPmXSj*8Ieox%uAv?&1+_RL;>d(-l8=8XbLi-F^ z>tjvPS}(h@H9+X@$vDJbEQ>*~_YA-aJw}G>6e&ndQ!KrKj4sRo*a<5I=_DCY74%H* zy#p2y)>OhK{KmOu(g$!-0MYh>Q=a`xitiLlE|=^~Y~5xI{u;YMk9p(B0H!V+!8R^| zz(SzEi}w%Oomdw*Bcs|*y>i8+*)3r7rQbrP7#h3#fFC=jx|~yo85WBj@9*@7_5K$& zbPJ*NYJe5IfRyeZO`luJ0CGk_F;DyC2)t8@R1CY(3Ey?Kz z`4HdLoMR{M{O!qO(Mgh0ixRPw)OmgYOUIj|Fyi!(-%A^jM&QD-fFwhx_XFYt0oUaw z_=3grttd1-<07v8gwxY`rmWkd5^f7PtW8Su-_}WSqWp2<3W)e{uFeszrS_6)P<}1? zFnDwF6q-u?d9(94w@pURJsm6HyxS?5AI?;L>M;mp3>a=PQHt(820_+06-@N2IkmhC zS=y-@NAZm$&Yig+E_KxEEhnri{)|io`}Z8@*b2c4wJI;7KKKDcTqZVnI5l?lCWAYQ)9}Sx#nEp4*Pm>84GrnguSGfa{pj))S+ zw}}F0m~Gh3YI=ut;v93o3_%tfFb#h%qpi2!S$eQ+#kXBXmgGLG2Pez-?;8aTi4WQ6 zEPh=0nC@Xc?Q0*LD(CWbm>4DInct*!^ksH~yx(I;@Zg1$hMx=tb#&-J*_XV^ z8@G-qng0sPWh;|yn#^gqW^uMVYIlcE%=8-_6Qc`=%N2g3*22j=R(reb9#bj}rH>Fi zXd|Bv2>|kup=bHmvDpiUtWFrgvUPiAfbbs>ob0u3B}2L|YyJQ!&f#!N^1AH@9EgV` zZ(DRsA@|Z(ag4noNH?H&wYegR+S?_EQ3nKe=Yv{2>5iC*Q@RFBPD@bUEL{0>WxeZ* zlZQElo4v}1++K#lpa1m{Q1@io=z@iEle*S9ckNGw#M6cn_|igHvZDD#zNj4@4%rAX z+6ldlcD{@TDJvkmS|+O?3%`u-e=~Me%3yBPA|NMuP?GPpj5bt(+0am?rTOY1Xh=A5 z4qH(Mj*mj$?!&@?zWlEr*5 zQfqd@hITr^g`9X1yN%qan45YUjQk@g6vS<^=?%Gdf6bF@*2ry3-Rs9Wz0joUIxE>5 z!u$>V-+6ScR^fUzSx06{3&6`x8X1=Wb&X^-2PB=M&bQHPBg9f2MTH z1D+NUXziK%li|yNR+A%TL({}2{GmD;|lo*vWz&CS}U?0{Gpv1999d-XPJq2>s zJTb;%SaHVpzTgPRYztE*v!Go%!g9Moq|)Yc%Lje@+>W^{AOFQ^1Y=iD2h2U2QI#C| z)7o#f3P}ja?y%p^aYay~0BJ_T_A7n6xz)W%*Oj1%u$z*&*QL|NMSvfa$76iTY6LutA0{n`MFRxSsYIy$4+A;`9bomeCeVvX4z|VjyDG*E(d;F(hMCiCS!tH~a z9LA-4oyE)lmXtSm&&>XhnZ}|9RQ4r-I;)u7);;6^{0qPh!`9u_XNhSVi!Qc{ch4sU^y){|C?LUr^?#7?6bp&qP-%mM?WNP{oZDl8e zT|->XBJRHv+{oUX1Nor}WPYf73IG^RjV|T*lK>JCXt@H2I&urdZ)A;o|01D(Q(8n7 z^n$+{Pf!zJMt@7078)OnSgdWlr2C`9)Am0f*TQUX@ux6Mc4;<}aX;>sWA1hU?U_q*Zcv+BxDW0}&mz>6 zX3O7)w=LL{sXw{vlO)3N36w^$#ajucBDsd?^1-W?UvuL5Urvvr&I%!t@>GlJQz7_^ zq14q*7Pe#zcNmBGdY0|y9iR-`ndLCdk<7*PX-!v;-S@v;)bBgL|N61*1<(6clOLYT z7tWN{e*G)s&{Z^%UpR7_WJE%`4=_2m>4ro?C&^U0wfp$_-Vk}~*2xMojofGGf|0%I z9w|VS6m{>E+(uuCiXQE$bn9yQvM49wdADyV|Ksoi4jt}*99#r`0}}r>tgXQxlKnq` zE8AG->SWWI0Euod-kMZ?|w&G8qwYnnMfv z91mrz6%h*_d66oe>-Naeni+8%*Q#wsk$3a6!yhP8ISuG+pw{Kgyf_1k{;8WcP3C&8NN{HsZZ8_!S^I9a0s3VZ?BrNgr(@CakQ;;`QF08^pqbzC+XK$YE`vn0f~^bC8e`LjxWHf+ z_-oA5L4QX*cgkNg{N!yLW_y%_QQ0=)zNtg{ieyuMs<#XGS1~zkltkcGtBgy8D^mLO zNv>SO*U9R@zOEaYrHgzX=}N=(juNInaxm0}SC@ytIClT}eNwVqy`+ErF4 za)&icJP6wp+xr)sA7RZTC^IQgxn9zE=`L-jZS-mYLo{fUwr@KFK6Ad%@=?G-W@&`d zS?h(|3LFw9j%_`bw`J}|Mk3XEsCoRbF!rcFgWQ_eRT;q0h2Uz)hwI`PyUcX?C`x$K zlypVdCjPk@&ita;*DMR_I!_!YNzZXFo2fAoHUq%zT^Cbh8g*Utf$RvOk!*FWrPB*u zjd&X;GXr=gzdyeXu+*P+DC^+M1Nm7ljY8mlAfl9J$T8^IYB;s3sPN3+|D0+W2U_qt zJ7prvj*U0SKAvtTJ@2p3JXpN(Czl7g37HDC*&!dmn75%~bB3m?2wTI{dKrD-cIj2V z)Oc11smiX_%Xd|T#_+aLUSwTh4I;p-q3aTQItvQ)9T3a;88b>98?ro@l2v=p_10=7vpF$5q z_oO=|*Ce{h(bO_v7xxP2gVa#5+IHx=iHdMO;P1l3*QbkPe{bQ6;{Mv4FLySD5{KiD1}wONB* zBih-dBfwpby*gHu8E%$Eo0+G*QKX@gf@{Swf8E`KD2p8X74gJkOYMnPgHTY3?4VRd zTQ5R~xXEL>BD`>1;i|=CTyGQ8nOF<-c?gmNvi>pd;CSol(nRdd2-;NAM(~BJt8syb zsUv|{io1T1Z}_Eg!m%&QFg~N+=~xff&cXNK2wQm%EpsxD<6?*YZo?R`mXxX8=J-d} z^Q55VSikQocaPRFs8y8RVy_%wY#7c|## z8=pt{Tfwm|Wr7Os7Iope*4)m8l$XA|mz8Y6<(U)n#5Zp-Opz*-3C8V+E#2d`(|^f` z04lL{z_c~+`aweaqSYr{Q*9!6G+)?kh<+{cUPcS~JAGkcbA}2+q}J4IyMIz!;^{uIUt6nRE0q&fDY57^iP@K%J0ZI9!6i zQ=FYur5xNEC3v7ZE4HQo)NB;BlynJ$Fuu|eFd^Mx(# zK&8Prk1@R-9UB6LNQ zeB4;ZgWxByJx`&Qv95C->DUvM|M8k}xmFZVM-8mFKqI*iY8*;o&HY_&`x6qtdG$8C z2M3NG;YQ_Inmkb;32KpR&K@Obn_FZlT3A`~_@Ic!)`~E4miu>%?4r8A>jFU7F@02w z#{}DY$-$Z7*TSK5z#Xg*P%DKaYOgzB8f-{ur8z8^3yE311_Ael`oEOb^WFJN<%ta# z#DxkrmQ6HQ>1d&AQ_N<>rRAb1?u9{>qNm6~r3C8mzY0dD(q*+_TRl9kH8)ZmN9u3X z&B@3#73WB%)&pD#!$3tV1|UedOAJj0KTP{wCZ5Z{tgQ)=poJdYK<+_dzXGg z#R_$;&nLcPW3d?55;&>&UZINCYC(h=ZzC*uSk-0!Dt>U~^}Wt*Ll;adLb7&i08?g~ z;jOihHBHfkbI4E<+5`n)D0^5#>Bj3qW&;=~JL><93S&mHN*!&ZdbNknP z>IYU{Mci}I2rY-7X_~YrxiAQ8`2Ndq{3&6)6+^@jA4|d4(HseA9hQRKE(MgbGNPDG0wK^_#JSPAO;_XXM)Nnw7ycJ&$@ zF%0km*!J>c8e}ato7lAxmIv-VNtdHUGolkw8t=J9j)HD!Tmzn}Vn}r{QPt+j0&aR9 zM2;-&2z{_GHUZj31`1)!) zgB>$yObV1QB4K?Qg0=!atjNrDCO0d<1?bT>X~V#3Uy}i7UFM4El$+yz7(aalsu?UJ zkuk@~;xVKYuVr4UGC+41S(K@6_c(ngo~SXe&qqb*CxnQ*pv*Dx>OS4i569t9RP=_) ztt4m#23|ZcUC{QS{XH*rPk_c?to1=qS}+=BBXNg6Iwdo~hCqfF{%QY>8<@O9v+o28 zfB6PR=j)p42jN+?`>d+J@)iC&rqn*(H-CuqODmBTm0eBYK(m#G9=uOqM%RrVi^M1r7wzvVDso-I1ez&(*#CWs6H2@_J(ouM#Daxrjw34|tAtIrvwB{}p zanyUt#s`}0rXq?Q%YEcR`Z%g1r8xBY(7G@Zgh3jJ|>8yBgE z%a(_17d5&{q(3l+=k|XqQ9`1S)RceVt&$0_0nR+G0L^@V{Hk0wM3e;PNg+uk<)pPh>LX!6oEZRT`Fr zj$z*5kC0FR5;uU`@XUMSa19m=J@2RSlr|e-^{MG1K>(JuwT)MzA}1v~(=)Perw~_2 zIP89+%3Qjo3vGO%l4XG?Oq*}%j4|n}=dRJ-ywtjsk7ds+Ik-^t!F$r~*B0%pMi(Bl z_=a7@=d@!EvSV#+6_gqkl)0qrGu%yaZ|?(-xVd$va*Q6y9vA`q7kd+y+8}ji@zS2C z&ykO_5ryUX{*rQtC;aGI7Wh<&AKhD&*Q&j71>CmqVt(MIUt59CF#uN!{|S_Y$h5{^g3G)AQV*3r25H3( z_FVSs=WoZie&<(*kpDQp(8s`M!}Iaf`r{m4`pEo7`8Ihzh$?@o2@Nk+7mDnAtP|K| z#6JJFzENdZbK0Fh?2DIV?&6tyJhaSTGOx%vd{1ijYZ`m<0Blt3>18$Oi4 zuR*!~=rCmYq^poFnL=6Fv-cZg3V=nUiVG3<>f9zqHaI%d*$MwjnG1a1A8f#7Om>|v zHM1z1K`Exp3!Q7f*Okvpt9`^X+5)W0*>1s0wl{OE{XY&HG;kJ#QIE>2f9cMPY;g{;*}efKPMHqWK}wVOs^;I;SL1;a;tCjyhuA`A0U#?DVPtZrzy_;E{cInmFS zmRlKaCPD%aR~NZumzM!cQ=S;{bpOVW z#~D)*^3|yN8QN|y9b^mfQm}J+NmaHJdyf2&;|3GLCrJC`p+e}s*tHhO$Kfz!_vE{5|m9y-ol%YKS$3i@u&t4`P_+_q0Rfd_ieK2;Q z(Ot2!OV;%*0jJ}kEJx?pPTR{v%6o~RAByb7?8K&>x+HD)Ip~Qo_tlW3f9=7BJgS&X zquikkPr{54P^Y=+zyU8ULkxA1>^mO;Cx6Hko8z-s_#cDnpg zIH9c>=Q7?)>3H2k@O4cZ@@8sHlM5z4`9Gnz$+tgbtSan%FX}5Q0||qg`TOQs!RngaHSr)=IwdnazYs@vz7XR9*apLg-V^D)_h|l2iO0p4 z7rtP_DuMAWycTUeVc%L{rK9AMV ztQBvGrJzn5z-3Wi_iWJP#UV#8PDp{(6@}yp_HjDsY>9)15ZcY)B#Lt!IXNAQk7P4c zc>wW6LYP#9>gf1;oSIj97d`Bs2=mTsGowfElyo4R2~1b8I5O(U$OYX!)weGYT6cEi zmF4ehxwo9xVl|p%k0Yo6VihiEmpxEkwgAp87?IWMZ6uYECI#Yctk9F>IQtHHGpH`K zk-4w}Jl7TD^M=PF0~X&=99|gwcEcGzTKlZ&Y_w_boZ3ER_LX31aiWHt?1v-zC(Rq` z-OPr?Pd+EpG~4b`8{}6a@_wh#(&Et576(k?oPD9R#9xSzsZ5_*ymzAJS6DyRY09F@ zYl#8{s{8@s>go+LL(!lG^%|~<;mP6(EvBZto_p)U*CpwFHUb(`fv6OygJh2#ppZp% zIetG~?4u^??%3@n>nRQYGK`K~*thfJb^AsGYL!apaKZ%mK?vF^g>QMIV&oI|WP?%h zxQwqFy@NeMiJyKqhZlhh!kh+x8@8{maUnJ`)ZCJJpF|Ldg{9K|R7$)}{T!EN8~%*8 z$6QGzU78)Sm0!kJ51JhD%-4PtqczOe?<(EXG)wM8A2Iw8T3DhYXaHi%5x7@LLBK-DOVQN2$ya_H4e9H9V}2X35B4)?ABoStb7`{5t3U5V$NgU($ocfJpS~ z>=H|JMdzcWv%nS$k>J+ddw37ewVqT2_`a@)%XC<{93s=;DiTX8Mz-|25-Fz>(W_tj zq&ktrXQ@CD`4eoiXO*v~OS^NRFtK840qSZMo>uESF9~4uHXq*ba0%N;7fWUEr_iA5 z6G%wa|ozOw)I;cFAbp_s0a<2%>#5g=6YLkuc~OBH#e`#QNN!vi!o!r z#Gv`wDTu*Bd(-QWIh1=A4bOvdO_>IdTxikug}Khf&rk;^sHp=@D?;~zs+0E8CydMY zjIo(7SoW0&V2r(nKeXNocE-@wI;oAU@V?C|SAGvbE~XUOT)+o~JS|<`mmxa&Ujep6 zA$9ZE#Odf#cmyTBIKkS&u{NEMNoOX3DS3@H*t_qne31)wHI917aw!h;MO2BKv7QAo zYarkd?w#Na2kxm5;EI`rH)Ys1U3fxow`*v(r&4~kTdWLdG^!f)^cM-OxOk>Wr}?u2$>!~$_RQADdFBc(whT_ zCHCi-{xNNk+cIE?JGEbMqf4H59L#jNg;1cuv7w+|r|bMgmAol^xenIrQ?BAw(Tans zEOH9uX_ubzxc(Jk+*?NiAxHMRH>CG#e9wqjBfmHaZYxin)%a!+Py1bY27%2=Q%&P4j;R3T?Don{1tgDezhw3hgdQ#oW zQJ(SfpZmZw;G-6HF6)rjCuI^(LzgDGw0{U|XQn#yK)gOOv+yxP;g!o_BerZyS{P#A zxs_(m^WYY+Shw2ZB)e3Km+)qi2?0v;YvWITX~-y4>8}tCwUoF0CmQ=^(uKdd;dc#s z?9+|bJNQ4r0Ehn7B1~*2p=jPj)Eudt+kd|3La=vVw3};tihBiFP z{D#7*q!_$d(iT3|>Er$&`5!@a*kkjv@Ll1C(wVJ-P0oHkh!Eoe43}2{IM;IWb5xPu0150Af-h;Z5)CoF<4hAVsO{4NrX$hy{ zk-SrqF)Nkb#%#1|kCtsoDCK4X!ad!OY@|IYL|OvPy0P{rC&sj1sKm3tHBV+WE7DD$nur=K-Jo zivj@}}pIzSl6cO*P#A3`>WSvtkD8oLv=CYwt8<##p^L=kND4Nfe-^;9t-bV2|L+&R87rp4(>S* zY*tj9%fL(}w9#Aa`f_sK68b^C4*zcj zY2hxc$?W<*+PGhLr0%bIGS81tV-|gLxe&|Fr^krr+xv5@*0OJob|`Qd?%8exsWd=v*ac}uSy_?ND@Bug#d zOh2&Z(K`xnw>Jisa2vwH;tKs<$Jx- zZc5KD;LwSbL}Cs>tvy{$aqbhWSARre9euY$T)B-drII#HS~44iNB{sD(OLCH5Te2o zG!FMts3L4{t}p_Nyj_J#-l*5&__p}5%U3mC01XT1w_1{A^l{C{xf`EJ-}ucLz6$FND!`Kdon#w z$BlB&CDuO@p+~(n1NJh+WK{|xJV+>tv}S#80-cpIm}B>>|2wB-pRJlVOmA*mhNYOF zw|$QmsX#_%ndIQ#W_r`m-VgM?ElqZRZT-jBhL_vuV;>&h0i%t(9J03PKYZABRP&N! z#ubCs&`R1+Q{uy>K*OWaZo~8xQp>sE!gQ=6ypJVaQ}6#|d*qb$YGPLu!9y5eP6#AU zjJT)oce{eCb+#Z&Y1k*-|5^0&V?IPzV_cPP&O_@0Q3)0k^s1Js;V{+z9 zCdpR2HcsaUF!~>D9OYqt1XVjUfHik^O_)YF_42YK-&@Kiu13j}S0=sPj#odQ{z2Jd znNQWbKplQnC8eJ@US(gtvRL!r+|at=(uk8%f-VtDCo@-~f#dAw7N%eE8Bish5-1Z2 z{rFoQi2B?g>Sar}bf)d5_4*{E3ah>Ix=LMHqHJal6yOLv1hKVHBy?l-To3rjhUf0y z&5(SY9!sa$P=OwkJIvOLIAP*VKE+SGeMS zVyBsFt0Q9+R^FEFxE2c!d^VjjTF$=sFYZ3=`B!T%-4?+?OVIN6X*J@S;U%r~Shx)z zmPlQSPBBQkqHI#5m=4ia{w5z|SwRXTbW^r@Z7D#jajB)x-#m|5hQAbziJnmTT?hn! zTFAGw1({wlnQU|9R!f>ZfZH_o$5Ahn({zYzw}fcumzY{4MsQ^vfu7io_by_kLwvGRGKV(UQH(u`l&z+nh~;LX3C#&x zhss=X&sF4aZ;hsfGM?$6QOy^K*?Pw7Q2e>E%`5)awv{z7>I=g*qvRN+t1~J6#}jdB zJGt-p^isz^BGUYsL`7 z#Ehv9(WzZ_Bin}EtlrZEz?hnQA!u@7*2`B=goh#KUEI#6aVmGl#TwUG&U<0y5`EnH zvV>xyeo8wjdHR+zWm;_zuMo8>FCmlNr2GYFYIcBaK~)&8a*&uTBDxjv*s1ozEERA` z9vs!uM;1JG6D^Ff%U(Oy>oHYx__QK0@Uft0`tL_Rk_2Iqf(SPtBJ@t$`_b6A%30d-`4r{YauUboC#0NDXkO?T-eEKj zX6$1{lmNetV^& zu)vr3(3-X)TH%k)lIyixH{Td=P4r3mZojMCu*haqmWMy`wDX0jFeE6uE@wKyEe&$$ z;P2$QgTFMqwsyAzobzt8GR75pN!84eD=!%N4--W(5&|EQiAi|N8jR>_mC)mEWRxT} z9^bfuSZh{wYUCsWrTkp??{LS{Ad1e!z5SWtdAd#hlwcS6id@8$*M7;`lSZmPLm$4HUPPX25DNc1dga}tQUumD<=?L|OL67f9d z?qyHL66)Ajxn1Cbmnjo=4{mz_$Iemjk_{ z796+SZLPGnWIk&7*jWigBoKCxcW~|cd%BELRD+~iTMDSCI)rlrxdn)N2cZYLvVA_t zvd=jeX~is~oa@z^fS>ZjL~O+5!br!w~ZrOvkz|4zWFy0KP^;)e0XzlBTwlD zUkBK16QIwnjOce8nJi%;3A>B4r8tcSkhVE7S}Dqbkhx4Gb*kD+tV*521X`S0B__-d zK^DyQVWV+paxCYa8Gq^RN9W76v`ZQHCK5Kf>9@2(}XPrc_1WPJJ^qs!*XG_9Dvxo&OfQ zWY5S2Aix}`V@o>18aJNxv`sGrpTldq+uKg455_&C#Iut8p5S2vZ6!LCc!FLLw|Z;N zle`hXLQ&c36!mhfSm5I%+J)`uw_wbv8F}h}Ks|_tCKqrMp6CrKIw&`gauv{;vS401 z?I}b_lYa6JWv`U2x+el*HvUnZ?WNOpUi8}q=kW}?*c3j_6HjW{ZHwJn1k<<@r@XqV zUIcO*%$kC+zwu5I`a3u6K|l3$6O<3fCT=Hscd`5G*jKmP`vv@NXU7SEq-h-E^b+O? zA$-gj$L=`05KAGspDSk}QKgXEgOEcg_V;>bzcCmnad_3;cBEBf_uV_j?DNC@%4#18 z87wuU3zvKzNbkQ#n?jtu`M_%iDwD2z=qGi?_P`d_&v>Ku4WJ+LG>RO<35dBQB-`fJAa1i2DJyA7MZ|az& zPal(}7>MU;iP3?-&+_Y$q~}0s`S+b&$?YXg)OkR9bw(ZFU)vj_yHHisd13{gt!9g1_iLMX_E=%EoUgI zQ5KGU4nK>|A}2Q$*J5q6nQ$31`J^0)?+WNMUBiSbsI%^oVm)NCwaFoGjR(x%-5g?` zA7JARz@SB36NIG4qk4{N ze5c^eN-RT{2~s66TPMH*<1YGRL7M`p>3?a8Rd9Ad<8;?3(iufGJs{<9hkZ1u6OC1% z*5d4{HAC4t`BM7p0XgW>a^BHA3_0*>TmKyo_T4Bmb(HhbUZoqTT(N*pki)uuDGweC zl+!=~m<)<^UF9}{(diABZVWgn+42CF^#m+6%zL*};E;hmBbp^%w9?)j)zUbPLCROK z$yd-CU3DG8@8HCjSj4lbcfJ@*=)<{B=F2C3?>h8u?g|WB_d}@tgln=8$Yo(HR8QL<*G}$r_x7k3PNVhP8-)F8g9}l;ZJ1Y;y;TE~eiea};Br)hWj5-;K2+p?9*QM~1OFM?0ZY1g>iFlwM8= zB>$dx2jhQw2{+WGh?YcXIj?_LmEKEYq_%Qamm_HaT0o`0j1wp&0Ej=VF{sGGh_(HP zSXw|>x250?uo;=RGu(E+wW}i-{XJ>Obj%IvB1MyBte9FU=pL0C2WcHY0(A&$*Q-{3 zNvlx!6>}f@W}YWJ;UIZM$FD!HXLG|BA%d|`{79d?*V-&Tt$gcDE^#mA(_H-T@?OKE z?!1XhJYTU$L-~m8xEv@ZbuXvq*PV2i`K<+Ouy!7!HZ4ZdN7!}Fr%S*hy@$cfLHUg1mI#95gMmL-9cNNCT_=H2SS({xHa<`Y!U0^QK3 zGPPq%IjSgwk!E67*xdsdS>3VdR(5p3SG6y90X!0h3=wmXrrZmk=*`y=2Ze0YOYlI- zg`c>7=E41oyeebrqVVV+glkQZpUH@4frduX3$Jk(lc5LIRHqAZthH=A!ii*2GPbB- z;A_;Sy?E#B-*uLPy59T$TRpz~6n>JHpSGNi^hYP;T$??sd)FvSfb|NczzoLPf{Lv< zSMG7ykK_T2P8k;~ZiQhgfcBEV%K2gaJQEeJR9N7-+zz?mv0iUotoC4JU<2s39qm@h zxL6--eSq{)oNUsxZkvLnWxb)5axiUb^EzA#YSr%gd+Xx$VQBdk44QNzp)ok+7|y?yBD^hCBK2FnJRDWUPQ^Zeg6N5 z_ga-{d_G}8QU=sNs^-3~TcLa|4NkIhM_K6Czbwrz#D#-J@RbNRGGH`q=ah>2<@ly8 zUM%U;rFNE1r&1~<0I0skfxrh7P z{hY%FsT<~kjimDe2j>CJPAUjtRDrX86S`cRI2)UaBh)z1yCikMKPJR7rS58%8Pfoi z{xQNtXd9Z1jNANv9zFYSjr&}R^H5^1UVtezQ zO&p!7zUZ36Qo0OWKx_q*iB(ASjK;_AdN-I9S|mT>x+z2c3xXm}Oi(?B&C5Qh!Kcv>Q?7b+#A zPmqL~coWLP=>lhe?y-V=29#m=T>%F1#Q2h39M?`66AclJPXCu%3-yP_T7Ai7p(+?V zH87lGTj%qK87*=ku6yefhOn@iqL`-tw=`rmnfbHChNq|rX3xf(LPV?6T8T+yM$|F< zEq>^5oBp;a`;rANGLeXR5JTYEfTRy%oar}Ky^M`=Y9M(%xab-fGo=F@NKQwifKr5} zb4g4ZG2cKU=}OvJ)A*=NM&@#pHYzhz9(lRaWo=HI`y|s?p&y0mGLRQ67a*Yj2L>R0 zie=|)yCnR0PLpbm(%n#ROf9n{^Rrd1qm+)|@#>$chsK`F=S?(jTD`5?rs#XROv1>a znsoB{THMytu1M|kOYIddqc^7zipnPo)_}lIzDj{c30HnG2+!KM{|_}dvOxliTY2@k z&w1&Nyl{gv&hN#3f+1iVNyIP*l>_dR038g}Y9}6=|Q6?E|fm+v-2wZ=IRCMdlGB zN%pJY*uN5_>F+zHowy#rrn~htx+Dz#Mw#6azJqNeYao_P#jopLjoYNolD__8CyoKY zkamjy*}_3}V0-OtbFnN7XKA-}<)yXy36lU&xA;iMeXE%s8$5DJ#sP0AmA4NTV1v9g z3-Wy!!ri5&>DeQld?JzldmVa9nzB)>>*RBlh&Dj~`(X3zWkeKyqKm`{J0i96Vtl`C z6D#E3>m_<_nE8D%dGKE;*+ypE|BVQ}JzY=EcQw{WIViKS{cg9jlE5uxufZxD@fEJ! z$B5eF;iWFUG_lv)bTCED(u^nDCa{8E@3R;U=VAf_qiqlhZ{WvTLd%{dT#3J$SrbLO z8uV!(DQtHXv8B)J^JA9{gW=5m+)kLNI{e)~mt`x3WOQJYs{zPdfH)r|lb2!Z`lo-b&ewSZN_yDZM;%1;>>!=8=A>Kd$P4Xm8S{ z%w_~NL`{U|$lP7{Dktn$oj~ejyk-pECbYFja$5a3`s>;L!E?wVgf{9818UVY>~)J< zftVdhV3<$tX>v#YIyV-jI%f}`>x>pDAhBY)>=;>D;_n!v5x++=?@8M)%<@bm!~ZLQ z_-X4~Wc2($ceAc`4c+OQQYLkv$93!=^flGa`AJowWyKF@IFu$lUUoOW55z=kdDx+C zq8!3U7}-qr)c#(@44a%?T8@R=K`_Of@mGbB|FLmv5DFmhB0zx><`&uJm#-o4T?b{! znUAF+f!}kQUz}H9RJy(f_2=h2e@IYE8Y~0}XkH=N$GJQ&ouo{EiQ_*QEACPJeO90d zK0Sb|6~g{Z7Jd37!q6{P+@%aM00udw^6@zw$m;*WOm@+eRb#l(Mw52R2CR8eGJ-Ux zYk|crGC$n{4ASbYQD{I~%Xp0maux9Wkw5T;*5UQVp0fCjztPLv2%L9fGhR=l)fMKh za{$wAmROV(XNgD#n7M%yZ~Cqh%80G)b+&i{fg7??N5s!nIlE!z)jP9G z7Y9mrgJ7C)ywf`y#r6?x>A{sSYkwABd2#&R)z{-sX44Uc=MkYp;Zk!b5$cZnkP2uD zxtcGTDEXs-TMSR0b1X`vdsrDJzOjITwR5X>Yj(oa3Tgc~2q-ZSP8{PZ$c^(~kWz3= z#OuK#ZjyEU*0r1SQfp{#hH-7cmpKoS_IF$^io2i0ekwCcSG0?fy||#fIQ9pDaWEfl zb3YRFL<@p!Wj+yEpu(oJ-+;EtV<_@h^n7mci3EYaR75=I3!J%mgRFyZPOJC4e@~_) zxss^mhoC`xNERDt>5$i>h$9rOg~A9-L7G3t`J1#K2opMNjA}Y_A9XtV6sEwCyn>*7 zq(w-qes|VYVF}svs^9B5sdb$0A!dMLldM{+$*0NKBg@`sXs>)#%*4DD;p&fyCw53A zod}c#FI&mZ@p5nVtvy&?LNH}Yam7oa^6JtSKFHtS!^V#}U$}O1tX^)H`D$Pp@L8(L z^cnW+Ih=ivZb#i{dKNY4LNbU8weCsLu9hy=dUL?Nv4@<)*^S!N!TIK-%H-qL@0sAm z`JdQY={>;-Z0LRt_EN(4{vw>Y638pX_7*1<^iayM4C6g=AD@{LDQs`^VHy14w40t0%C@yeinozq+|lG5_4}yMv2vPTQAglNprpa8tDVQ*Je^bd7hSt|9WL zkM8KZAL|Aw4~Imj*RNdgD9J-t#Mhn}002QwO-6o40000000000-vIy(0RTb(0034{ zI3Q+6002}b0i6LV-~kQ+K2ai$MFOWeumAv{mUg*I-GKC5JwF4ynp^&tI`=gvU1ue) z*^N>(y4}H~35M6nIBTvz&Gm;{V!hpR(}WoPuNfT zU-@)`{YroTVX7|q5n!*Kyzry~T(OtDwV$IWkf^a~}4%2`zMbo)!G_#TO*o{eg z_+DcT&d?(weU~bz3I`0jYSR{6P$l{hM!W@hF(y{;XsGi|loa{?e%FhaS4eY@!XZ=c zQBVfj6KL&Zi0myaug#yiN1CZU0&2N@U(ymn>>ZVIBny}g8AV2zz-!l)iV<((ZE#a} z@tgo>QP?If*?2I$skmSQIr@qoG=z0kOe?i`3dxylc0-JHC#a&$UZ(2?fI}OALeQbv zMuGlZZ|PfV9yyK2aTCW+=XY3*Z!F+S*u*4lX7E~jr@}}(fn?0*n1S0|o!^C=JW=1M z1c?X;K0rh^q_bNu1WkD{EwP9pqAZ|t)&mE+DhQb?gT2$f0A%Mp4v0V?LE?98S- zMQXAWkA&ptc&$8a1#E|aS`w*}qWx-ZNMEpBg8FcOd`W=xQ_GiFnkFSee+0$ZTt{M9 z>#5RZp3$do2w^n+|2quc*Zl?RsorD_r-gVYNVE`(GWjk=R}l-emXQ0iOBF8y<(N0H zqcIS)iQeqY0~}wL?r;mPBn}ZE%qCJm;~GXnKX;Pp+~Ake>h%9a+LCg+L`NTHM{2dZ zjT*}04p$Z@_`?!ayd4Xy^hbr)5)Rkw%+N9vhn3$8Lu6)KR1;ah!dD{c-c)EW7ZqO< zt;5K72TE4Gv0Y|<|4q};w448L1?N^2bg%B`H1d?iiDz9HWPbD7)etrn!!$2CTS)$w z;Ca=uzi&v^FXnsecn?U?>U&p37cpJlTtP07gTuHs7$iTmJ#0zst z@KH`#@$x%VvS@xf*Dg_KJU^LeRP!X#)6~E$t6uLxgryvE2G5v#&fAC*&okPP9N$zb zNMm_l9lG2}&ir#E8~n+e9i_H&;+HW!KJ~{|37d4YB;@`M6*`wwA?Geoss97p5nb|y zQj|1wWU3UZHP&Uw()zm|Us#V?HTEY&Y4|bY|@OM10 zURA-R!-$6Nsb6y&mVxrWO<0t{4yE8YuMCF!nr&GftZs?D1C}?3TUyivQL#Ai)D-owGyCQ zQ94QgFX`YVK6JWxc6T!*$hp(lgnrE2iIc|HA~LA49F1QDc;b`w0=5P-gunA&)0bx7 zS5%V;%=Poa`E>RGP|EZXcmXpT)b+;+eDaRY)_(2K-eTB>sUs zragiQpo~*!UT_(gob_htSZm;FxFVhrR60>sMyQLd4$oyDvk`Ck$QQerQ}B5*iHWoL z0KBlDL>_SDUO-3$afPnau+L9XF}j{m-S;Vxg1~bBoxyEx=d~b(tD93v!+=4nl}|R^ z@jaC7VkIMT!)ZBF6D*d5CH6IntLNXA6G~1Zqb*2iOVK8&hu=;ORgjmQE&NnKUpA(DwmJ;2rJOubpQnFF*xYLJ@JKJSamRm#T zhxTf?vEQ#zWnpAL9h(wHkUPl3vJ*)Hyo2rPm&X}QF88fL+X0MmIpR2DY~2(>>Y*a) z`jBsL4qnfBs;t=>sE-ImyEd9|oxP&Z8jeAb z0*w=10lR)s16asF+4}l9#zpryjiRuUVkSfhotctY0)=9J|4}q(ZR+;>}D4XSCy;!TGIrqRvc~HG@jfcdNzHdtGgZ%AO?eNEKfuvWAG`~Q_teDl3l>_)- zj6EPOeS4ul)}CNu#8C{-`PAKq{0%s*Nl`o?)sp9l8QhxdXlvR%fYf;)Z`V=4}}05=B85-9;##|$RaZV z`{Gaz;4v`pfa)rS3Q7b1p^A|egLCPQ@e566r?{3RzGUlUkg_qFwT zZqfunK!d3XkbiY~Hb6T1Xp&<5PD%xvC%k2-nsoh+<>~SlN#IUL6V{SdvDK3m*byZdPPoN zr&f1;s=K4uYr+QfCO;7)m0qA(_Hp-uVI!NNzySX3kQ>uE<6!Qimf03*iBW{s0G?B) zXZU0h<9;|v*@2I*)|sEAMx@nRk8f}#;gBkt|H?!^xl65*Sui*^b6tS1-rwm+yeEK>PwHme%A39ZUhzqF{|4-rsYGjr?jwX3BtvRUI z`A09H_Bo1SC@3SKa`E}NM{ZA+UM@m(M8W(P>obvWuxkM0Feb5kOQ^KsSC&)n$_&DN zAXo<+il?}o;b7P2U!Q1J!?Zp4P~?Mh6t@xYO`c^uf*XZkv4CqO*(oSrdT5~5|Id`J zH7?DzL+I$Z7cS;q?~7S0@S0(T6ttat;d667_oj8ii?9BoRvZFYW8P+Z6cf-_Ppaky zVM)4aQy%*g1<}5!enJC^0|bvnsC+c1E$&~N^*;U`I^d=`!@;bXo)f!?b2Nl@n)|Pt zcjvp&gI6M5!3gTxfMq$C_K)rWnOEs;p%I^OytLWpL(x@|*hMeb=e2Ve+G^t0IHF<2 zfFft&fsLpJ(-aBtx_nAgT4}nG39tBapT+hW z$<^VMcAT)qvrCR?259`&gTh(Ag1-0(`i$*1eTUTRWcZPCF%1vn6~R-@fa3rx z*U*3dKgvb1cKiGz|0~v05RcHd{gR6`Z{K{4FD^NO0uWTg9tm61TqpV5$>h8Emd~FQG6!w1kjscM4twk!$~v4R8r{IGhWB` zSS_a~xcNgB5bnq0NF2K98VCrCrw`Ys!Y6l%RZ(3{bqK~EGSJHX`Iqt;JmM; zzB%oxGzdQ>QC@e3nZ>gvUK=t+3>iInbEje2mz%Aua~MQ7`Q#O%w^;i1r$he@HKdc? zp{{61^SL=p{VgC$v_GmxC_`yJ9+VEv)1yp7;CT|%C3j3)`L>7B>%QBW9Wyfsd?xP3 z@3il#GNAzfVvE83ser3!{`t2&b-%M{PLlN06&*pf9UBrkAlp3ELJvc$?@rhEHjOD1 zO-!=iHnh^Q!3|?!=+uS7kUFz_b2G?g@zpZzi#4j7u#-TI4IaHI)-2zeX6mM=J!Y z1hrEzijwM~-eP6o$~32$mLybhb&@IdE)C1+Z2jsX!((GhC+B}FJfw|}9{g$zfRGf9 z-!&0`H|GDGOL<@4Ffm(~y;SQvGOLlR=3Cv$vF7fyccl+7X-V_54War`3JujV!|OAK zQuomaawi#z?h#l;7zytr zf}qf7ixOI6Rr*|DqlIttvJ~&QMY?r)dzL9*rg$eS*HCI<;JVX1{tf~s{6@SqyaeXKRmgh) zSikHSy_>XQJ_i2!gN>W#Ir$RFcmHIvg7(oGkm%+-)bVAc88 z7Ady$-*_UmHBHLWxyS&9E$kJNjXheue{QTrq-B;i{sMi(7IH$%8e|2t+zArWWc>T`s6dNfHEK?%yzxe*!{ z)S65ua=PF&b>YWzC%U292Ca&dsDgj*00mGqZ>ZD(uG+3u>qIQx&wpS{x*LK#OCiV+ zuA(eI$<)OhEOMz^GLT5c%!F8z7q|f$unC<<40UHov0$4Z$pBDQS+yVQW0`Kf+w5-# ze{4tWn~2F_quLa0!Vj^)xWNC;(o$J2v~}+F4W>)0!dh1TIR3s%o-Ov{$P5mKPOo)5 zH8DJKpX}=zX2Yt0w$TKu?0jB1c}h9tM^C5NJvYUf645gDyjy+AZfRq3*3=3I-!<%X*uE76`bVPeEcTGc1#COPdQ z{CEFBjbQQSCfH$w9)vI+qGX{WeSxWqe2Z~S<}6&g>WkcEH2zWoDek88S~wSzvTwtK z2$J-CIL65{^Y6K#k{Lpd9)3)~wpB9GyOA~`plQ_?EV^OvcO08As{?kS*7=CM)=GQW zir!|d$^bsXomfG71wo4${vdYo!xp$G(!7Hu0pF$!FVwl_TmQdbM!U949@Tr?%Ggx= zzTj`#63momu@e+;j~djTE=$v4@`XXo22e-@0?&mKb1jnHF<>T^sj}Sn+wztTVB=#& z(t@QELz71pq~%krYMF@iV=iJ{()IIOge-J5Zz@8;Y~{kB_7VML%Xnann=Zrb=bL*{ zE2=BGc(3&c1>q_)woP8HLK;pqT}&XG%EjPwYpyR;oczkEialtOq@0m-r|sYDV-S1s zcoQorZ$>_>8Hd<6X>g+jBX}JCz~Ey2)rP;qt4|-|w*Q&!QT560tD*%lif{dX0w%&? zzCP!TiFfB=v+o%hJ=!w3pAfCkh1`V9_NsWKEDsMp%;A()^8rhz5C$kPATt&t$A04| z)EfIlHCK0lkq6cZ45u7O_MzDg8u}6%Xp=I_jYi@G;*tj1gW%5Xr*jWv>7sSEIm}Q0dhqP13H3J<}E)+ z2w7Xt?o7oU7j-&q+aejMj&y*jZlxRNgQs@5h#58}XjnMef-sSruxiH9GdX_QM2liy zS}NRUdc{it7#6~>P|t4qgc4}f-u&fM;G@#vuR(?S^uDZ#=b)0%8O*mAt*(+GV)nq6 z*8(qz67f3Rh4D!80EiZk5xk00YEUYfCb4H@APGfykkX3b7*q-a*T0(Dr6^G=jTeq2 z%}!AP**ngqqdtf$Uimg~DkjibDau*;OAD*KyrXC@CvD{k(QzZETnrjKNlrY>6YEH* z`;n>6zkMJjX&sMrN*N7HlW|Lt+0XGT47`0oYu1WQLGW}hC!Ly5aamr&eDYDMMX>;T zQG)Eh1D|N+s!Q#J*{_O$m9r|fjXV4>64^loQ4hz?OKf%J-gv;+w@pfX?n#)^mX~RP zXaoBfve_24{`vJNWN<`i!n*=AcRo2^^vuXaJ?s)9kbbZmVeq`#DmpX75SN(}@1TrR z9)!65wiUKsUe$F(53C)Wrs(`X*t{iw=H6pdd#(5o=}@O{vIDUxxEgTKeZWj+6`E^0 zEs9f^6R#u)Oc06G62ro}MDDNeQHMTc1Bn_UMs&}-`5g^nZa&y4nGn_VF2zx&w871m z#n8prn9u2qQCgVh6P-s-fGmeQ9gnQ$y5U}%ztp7j(i!FZ9$Eh6CkcZOMuV3Pn*Y=` zZmN!s0gUw}Qm%&E;tJUsdW^nfJsuUyKoFVV1|U)lxNh^fEtcZm7VRhfH6LsUs(lj|gfpCc=-h8q~~uk11^GafM~CjsI=X51UwghE%TJg?)QTTWjTGMC%#4{YqE z(gNYKuNB8G+gz*EiMhKr$s+Hkx($4Y;MRE@Vn7I;e@xd@j%& zC5@Rk+1FTe?L4{-z(21RbGE#~7xo+@N5iwLXUQWP5Ew*e&=qs`gUxbq*q-Rp0kDcq z@SDO)>Ufxu+}x?}0PTy$0Otsk{_{YXsUbM2V-?e4z>@Fhff-UWQVE(9S(F zrxdR*qseQTME8(|1xo}pf<{*xVry-LVPMG596Nov{a{o^QcM`O+=G^4-jqMN%iFxG zTfK-eEw9QRj<^AY`1vcNpG!5kY)E;735iF&sx9dWbCOlEZJ4asB`OwsFHC86I>k#G zlTG)~qmlfOGxgz0e;fhjGpk`F1ntB@wTy7Ox-i#W=grQ9=b2Kdxk?ojjr7g%TSW)o zbP2T}Q4Y@IUjkCyX;f8u)UKesq$hoO5ORig|D86$6~J#V``M<8KdLrZM-9=lCcc*h zS|0z%Y_Kcecaf|*MSOc5nrD!&G$2(<54J(9djuq%Yv*~nFfKPH;9uNUd-9r z8w)kie?UU2sM7=>`*|1G_)bm)PRn#X%e?XY6swb-!D*&EP5Fk=Z{_S-d=flR@7dZA zj7o={|D`H{tB;KT!)URT;T$OK9Ce`iLGBP)ICc?5=Erw6*?tyf`t_%P!6IVor7Tvy z!bt!g4sOl5m*U6S9KQWPGwGPG0<`2HXZB8l*{N1sQ21%CwezxVl`RR8B?euhdgL9Q zml~xXeZ#omx6h^nF>w>=7jyAAhW@OM zW|@kNo_ygO0rDlj&IdPDEG{)WTNiKX!qJEw)JJzMnQw*%m##J=P7%~Ck`o|(Fo==I z$7d4uOd7QpR8d8EkoTX*f%FScI6Ipxa`)}Lr<7wq7~Ax_8Cu{y&OYpL0O)xImNX{F ztIct2;~p-8_v;|}GeIuAa5;7s@E5QOBo6O)ab<10#@$UNwyh=BS71k_8=R*?q?Fh41c>&ZcShe{PZgiO{Pn+OcJf8=Ens;szWeAO zo`XMED1jo*{7dDZw&d{V*Y+*U0R4eviY#sY6xA5_oqq#|P11Lz^;vn@s{aLVeAxKI zWIaxp2cE9l?#gjy%xH5#xTED<8gR7##)7uAOOhCjQZ1|2tL34zf}RE@kPEk#lRu*r zA&`5^T$r1)x%Fvie~xx|h5yEVCT_CySwcYW01keJTWYm3b*H;{6YeBR4ZA29ff}hx z?X0qiUp+VriJm;1<2++HJW4~G@b&*2-3>ZF==^PxkpE4b(>O4mt_e2AiE!$9Q5LvX zh)w3WLDT(N&Wms`nuXmZA9_UQiZz^P-!d9(#O^zS%vUp{8r-PhQgzhUE1M?kxIr!V z8WjREjdx!Z$EM!tmp9XP4C@CV16l?&OLFCpM}8+R0lf*c{ji1y1ArPN=gLx8U`3(E zdAHG9*bhX73^Fs`SM;Hqgc-Um^Zd|I@%>s^&hOSy>5UFwFEhzdsL9h(0=Z-X$m*eM zqLGQM9TCb(l}2$i3bT0v_y;ao4J9O$s!qZjUag!1P#tPe^e$Z*kaC7K4+FPGeD*GW zV>XdbEdEF}wMaC=;6&r{KMc+aaNzW@yo#(HDE;B}s~nm$oWOX1zP-$<8soo5uTmZ9 zc>g*4Bnl_UOYA5%y03Ziu&Iv@-(-pH&*J7WZn(s}R|E-ZEgjeNv<9moK_$OMiTG=G zhmM||NK)kqfQM<0LOg0^GNf$fl#1UZ&;7AvHB$`oKg_fhjO=CRu+Dr$xbSzyX5s&n z$eQ@R>bi0#nAT z?sdA(cul$9)>Gf~wDH*l!Kh&tAl+}mT2PjhVD0vJZ;Tc*d#i`+s~BE{TvQs}x#4Pf zCTCEc!5ey@;_nsk0g31!4bel)wC}ca)c=M2O?A)*dLrmlsnqcFAVqt;u%9-O5~_WQ-1dkZDhm2*&1Yk_odRDExcn1M zbfyzxinJxMyB!OZwS&M;!CxeQPxDz-igfbDp(ph--J@1$nu}D1 zrvLPNzJ4fOz9cV3TXk>(=xhl71vFN=5}{z`mO#zYtE-&%a;tJ*@p?K7%hcpx5;*U- z8ahLP;I~6`91)!JMgqj)bwr&5lBx|a}@s^ROmkgUm|M|`>8x) z!YUW)14|XFi2YaHjX`X9NDBt^~>Z*j>Cn=fSzwrst z`c8tLIt>=GEy3E!Pf!zm*4WhZw&4HIxG@;QV4DOyUsZ@13(XUUR;c<&ZDE@3sD#R2 z=7*oBaBd-skMG;h^GPiUg0x0%a>bs%3lZMwdjLJ5{&0osTkZkbL5|KsBhvfzR>Nm+ z3j6n#9&lOGPa-z(-^Ohzvhj){(F9)gvO(!zZqngj^cjLsdghi@N0?)5p^>Ye!8aH| z#JxI5?%iUW(|2%(xYWAUz!5SYlF$9SY0N|-kGH3=gOtBi_#el7V~g|8aT+u}0gKqr|d1VTAZ7meJz0&{RAqsYt zP6(a+Y`O^6Mg!^e;S? z5Hlu}R~5u$U}AFMdK(>=n4j73qLZQ+B96XnLZmtDc;wsl`X^@w1P}5d5&0zb>%m{n zCsc%zCB>IX9GYkWu_XI zUByK>hAicRq1Bchd!5@lu;2osINSFGzJ1mpLuf1^^yU?;CMb5T3>OTfEXr_x|Arar z$Ga<6l~}x}^1jS4Tpt5Es(nL$C6iZi179qvIx5WUZQ8xNr)*OOIX$5m(^$nfP81P$v7sjSkcB9=wm%z~ z;us8vSDJ60FZP+`aZV3wnHUg5B4~o~gA1jPrgc1}qVDydu>|o8IRRKLYRV>)g?OR~R^sjxf7HCn^g@J8tCthP=6%Wr_K(=L> zzksUSXM#S&55iA`?|D-(mpcM7DJ?mY;;c0x6@LmiqE8#!?uNbh`vaNDD`X?&7Zo_8 z(Ba&3wXVzy2k7VvJ9;R@yBKzb>|j3`=tqh1IKoWT59C@|c$#HijHp0{*neJ=wj&c$ zo)XeQ>SiN0dK?@@VndhB3bAB!;}t$Wo~%gF%-fQIxyXT*NlVFlITy3Wq1dOJRDhB%59AKwre2H z6AMv2Xv{d9&$gy_y4y2+7apZ%hbuT(Ad&OR#tSm7p%O(K(zOBg1H(Mc`%?iIFx&0v zlPS*v9SEHXYnGZW4^2A714*YbOf;%)XM@_pP`&xR6C&0YfHM+&S8ahF(;^oFStDsB zdKa0I-c_i%>tnwu=M|p1@L!pEE5p#^dt?+b8;3H?O;>>P8R4+hQY# zH3N5OB*z*9PaG01N(+?(yIRDDxuL^vQ$i>EpHg+*4 z9)+QJog|KJ8-ZeYiJC9bGv%$q3>QC;wsj!yhEB)`Sp)0N8oZ%E6qP^p((=;&do&1) zs#&4Qty|;VfR4!zcDJ05Zt3=OeE;@;jnJ|nq`D#l*XQls53ioz9b2NWPGgSOT=Ni4&DfUsF`O*+|hxXFrRpO=#_>X zM$ps^b*|-VibKOGNrd-z*lqzZv~za^{KBllL-Oa2zaXEB0F)V!RHCHlXiP8&3e+>n z^g_}13bBJ_zKLoO-7pOw{jxhVg5OT+O7*wP^|f|2*$lj-xzXM3phAW&SXqXz#BMSMzt|ghn z1kJD^wd5MB9m-A<=@nB9e3x71+zWg(8X_MQe$xjp&QI4`dNhAtCy4Q5AE*!x&9of5 za!_+H=2QZ(k3SkBuIvXeBSt$5R#cyHr|W1_BbJ`@mvvoDIR0)^ODAv)r0{YyxxbUE zF3A{YwBAFW3EQF}2Z=e~w$%s8<&G6f=NpU+TaDwa2d)hz3^Jo103Vv6udv)wPb8W1 z)N7FOrlDT69-EsZnBzsVkaGqGI6bFvo+imRH%a`xV2Als_#pgvS9aKo+4v()IQdJ! zDLEt|BF!AxNI)eBTTjk|j=Xj2((J@2`~J*f-GK|Dq$2!Z=^)pQhWhMVSoWOvGf!h| zbJZZCH^EvZDceS9@dtl_j+B_>1Yz<_A>o%34<8^S&+gNh6=fEjkL^)P{O<$nENN^Jobg(n$o(^4sS~N@{KvJH+ zXrj1ePO;tw;AOPbr95%iS7|nM@MDcY zzXKp9=4CwjU-I-xzE0&N5E^0_a`-D%+>pWp@5#OzbFjC#HOjcl-4C97Ya!&A@_OXv znKH0lOlKLr@>0 zV-@RjrPn}#H)rH+2m?9D>ZfNK)^rY2fg9VGT3xa3sj+P&eD&~4VSp{CfXwa~Tq zMfn*ky92}4P=x;KSS|K4tOHTVkDEn$W54kNjh8vAeGm>A2U&$hidhH zRJNZV!Rr{_K+RQmnX+eoJ$-Ut9z}Xh!@LYCCEqV#cg?`LhPW=?fg(u+Lcua3r)=CY zpOIuN-Wj~pw(d)wDzQH>{PmEws4*z_Z*iX>xC39V6w_6#U4`X};sHRk$*g!Xx_RdP`!sMUeEnfN(;I2plTn%yyBt2x%wh zqZ%;mWJ)1cMhUB&S>gFZxDgq8>Yaao0)EJx%hx1wt>tS?c`W*B#J;_Y20g+2Jl{P^ zW4Eh?MvdJOdBoXpUAKX&3v(EYf?fI?Nef-JDCNCD@zlB6$S#@YR@qOypI%&s#tn*^ zh`xz0H6}rCq6b+zv)_?|4-gKZfQq+}`39psuKX3f4fM1TJ^tTE;cCT?8xMglY*!}z zki?qT1IpI_4%vcx=SxCXQ%_~$8NgO%Zx5R4yfDkF-6a{Y_UNm*ZXzt2PQEPx;Bv4R zJ1z68+z?0?G&8S9s5{1jHfQk*v5B6~l3hCtWA7hDCBEw-cKEM}j|Y(96Vnw}MrT zN3CUiXmu%LiJ-DTFA26udH##+jX^fG-R4C|l7tbi)kZ1pK4@|w8tff9xyC|LhQ30dCsMN-zGpwo9z!(dC?7K}M14WOIQcRbtZrp@z)m0A3vu*Kk!d4dr6uSr;^Zhd1#M5Db)T*lEL#_1KZqd;Ef!}D)i zubnu{_ZA`(Q2vf&fv=+HKO1TxYP0~yoiElO$$k?E25+tGp^%KJeC^N75b}!1j#%N5 z8*&S`swUh(!+rC6Se4?Os8JanFU0P)Q-K>J3-T4k5HNeN^w5>6jyo38cglH1r&($t z2?GDdt@)$Kgi0tT9D>ed-8C|cCPe5x5iC0#u*%2^$H~FMHSh;y>&s4jhT%2`GLYuE z=b+DJ{>n<(gW_LMIeAecFn`rq|9pl(3f;f`o*FUOYA*gEY|=?H#1;!MpZKUCV88-| zaygM!sHfG3B$cDUGzUF6&(Y2(*6Pgp>wIzGoS@LQ?lK@2G zq;sU?>_}Ut&TbH=4MPA4^d1j1my-xE@LO#U6%YYwWWUS-cSQ|G@mEAV9V-<Chx(kDSmE8q;^d2DvgX5Sw$DE=~V?%3W>^%0>XF+7|Bcv(ZX7 z_d}jVox(|ymMGBM3_?*>(*>tf6jt`2D6H>F^K(@Sg|yC30Nu61!n^e0I{uVYD4+mz zfC?O>`zYdAq-D{nG1PUyZ zyUm{S&KtEnS~HD|qH0#yh2v*(p0d_nVlDgGpoQ_RhJ(#s6fBX~e^`sDUf6eM_=Q#4 z*LhRKe3R`PvhvuO^1I)#Qru*g1dOkryT%8pNSXm3X@{{*9h$*{H=dM0tyklT|E{wmNC1m$Jv;ef z-26AGfKA?#2F&$Sr^-l55Sv-|M$OzBqU01RMVc|!=IOhX*BGMTzH<$8ny6QeA0~U& z^!_MI26cM5BkOEYD>tdyg-0pYinCe`-Cp0ldWdNoONe*b3irNbLwoBe^-!pu(?F!3 z^L6Y9r*C3geXy6Lk45xye_p2s^DrW=?w5pWpgKTfiqn8-DPAJaTN;x~gf^xCP5_<- zDfdv^O*J$W@{xD&zRXM;I4B5?@a+YU%!i?+meE#BiIb5XEEaKH{nHCgo$X;XR_;vT1KEu$X{2V>bWinCAM@Z`iC%gLl{&L3MC8%LS@Ru zmFdu3SA5kB57zrX)L119=w|-cs(bFC6;f5ia&S7`V^B7SkJq!CZ29jafrpAWDQE|+ zOskLiuc}?mR_*iA+!+~uIrVrxwI6&3H;p+yKlLbeOcByCI z$c2bbhA!TZIfn&&?k4+QF&argbaaD@_Kcku2VDn4Ex-A+kvA^5Bh_6Z=OGQ}`6kvfqoA&6i5CUabmU%%Bp`?){1-m7qlxl&nQj zgh_L*XGiyiDY`KNQb6P;CI=A$JWx|tmd$8`xJo0K!8#*s&bO@R`A^8ZyS(SnDY{l0 zicqI(FUegOlRIIsN(-yK-N^eD(s+Sg_mo5qF|rGaGR&D+G^E=760%!sE-DZ=bvJh+ z1I^n@g}PsR&uV#=Qg?|=yQz_L`88h84`x)LDTo@Y{;*fTKeQu~FfWQ@OtVVOIZD{< zrith$$P`gogJC|8iQKI``TqYR9fWg!ieVkn^a@o~D`=J*D&@Fgd8cND0=f`#`3X$( z8f?O>@R&0}W1c@l&zEek+hQ$JZtKc7G=BjhAEh&K#-NV1Brc$n4X`A?7~iacK$^ek=dQlvRx>j@eD^TF9!O(Rq}qu~ zpZC`mJ3j$t>#+Kv-IlgrCs|2ZAJaXNb(RdsvV#iS^p^>wG!L@5^IzI|%7SGh&JiO{ z66FS$(c~=OY-)PyS5Cpu%zhcS4@58?%_O~Y6@>n|7Vtjk;vL0*ughWID}%yT?8q0> zOcq;!WQe?<*zSKE1f_Rk35Sh57NQR(4HPZ?A6j#I)jN~zZFs>9VFwY*`I8PgAu@hpz;=cApD6!Up@Epx)l+fpy z6K{4CdFyJ3&{E{(W2;=u&N!_jvZB;idX#mZQ|9aS93r~#bczeUq@O-Lt8X=d{t-t= z#!4+&wAs#USmDDoG{SsuB~) zmaF#e@bO4G9K=As{ZBIK-P=x)ksu%Ug_-NWPi*%-lA0*Lbfr`r=%N{R@gSNb|e-E%?_6WZ3}-h;!V$2!5QPKM?XMmek|=4fT@lm>(Q;+ zs838o?#LrW58DKBFRI_-h^HpyiTl8rX?u(Rbk@9+`90HS0XB1SeCf!Y-0mp!cP9cp z81K57RB4aqZeC`_MpNx2+|EmihBQdTA%8GGhOENH1Ecy0*rY9>3X`YPcR6@@VVek4 zh3JV$Oqtc1r@2u43{Mqop5YPdhC5yE)3T752u|ENYbC%=_X_H!L*Th6G8(Q%v7Cyd zi5w^(%t5U}JdMy@6_V>A77I&%2)d2+`|y47;)S%W`2qy&wm!PbGWfieT!QjA1#<8s zk;ZM@4JhH!mnGa$wq(;{E`?1jAqm2a_*T3Gmqzx{PYiPGhpi>%S&2mV&35xzt!Oo5 zTlF-!*OjYhzj?lOrK;@*;!6pbzjj>xAdTs|x8MU2mlS~k}vJ=)4C`Cx3K&u2btpg@BE0qL1$ux;4z zL-K)d&53Wg`Bv6rVTn`R*i%984eTL-!5~~*<2OS3-?Np5tL4Az3pWX&S3ZIzNW-El zSe!u54R#9d`H9q zMG)P{3!R-*{lEXpUnJ6us$zoBH$A?|K#D@joG3zjXn>1O14XL z-XoPqXS!=vKXen>5J{^Vt_RSPluQrN7~;#HHp|2i)T|6OGRRci-@aF`nO6w8(%v$x zmS4h=Dml^k5Jx|UV{sL@Oi(;3c7l;tOgnA9b)*V+)z!yHnnq!M12@ zsU4?cxZd2M2pjGTCbThdZIHvYg$u|V@|xd%0p&HY!ImmvYJ#_)3)}01w_GUFFNL9o#^x|6h>&Zj! zF9;w^OvL%DjE4XLB{kL2Prt$4Q*_(*46qLP^qlh`ohlcD%q{{cT{|YZ_HHrDAd;b0 zKs01r1Q!69MudkH72_ll8Q!xS7j>4y_s}<6B-#WM;F&PyOV`gt{woOAhY=<#DtTc` zo{+`MqS$z2_K?zWUr9j(ske;y!uM1&f3w5Rrv(TZ+R;xWMc-h%B~SL5#T9-u3Tnp* z-w6}sF1@aho0@S0zlW+r0eT4v4lRB(l#urV&jVDQhW23U5q}-^e!&=G|JJ9Z^lJI! z#qji>8lurPTCSIoE&Ju!>&AS)HS+|JCq;+_PI*=CB0nWTyH1(G5!56szs?|5H+(J`^fjcWcMoz)2`XgTvt#L zYgqZc0J}$MyRPvjDvYpW4Bqmbk^UD_K^vU&H1=tgO!f`Uj@@5k75LM~awhb}o{gc< z5C24=C_*87%ZBPBiS~j(?iUJ07sa!6Jo3(ZpbV?@L3l^@$z1rs{K8mGfAjIyR61mG zfwdtp!^HK5SLT$k$EwLIkE;V{w+-?TsuGRm^>ty1iVfXFZd^ne#JY<)MQhH+dt^~6 zC`U8c=OGyU*=gt=p+Hb^qN)KL-~0r+w)9^DA#k6k_)(r21W;bvvcPg~tgVjF^r3N7Z%f&G#n7WW03$S z1JWt%7`Ru9r@#k`Jw2o8Sw$9S<#os;)*hUjeb!w@ow5Dre!}mDG>+Vez#n z;uI^1Tuj@aY>DTLw>I0*P(y8rR`$lj{M&D##YguWXkEk8r76L1Q}T`FLH~5BJ`B5hD|_e&Ok41K>L%95+x%iS|y}3n9v)Vh(!G zRGhhJ?|BcuLD>iIWP5ptyX+{W^{0*@vdMp2Sp6Wsa2qrP!=<0U-WG_s;vXeP_RKaG z`&uML_g2t%*uFwmYwI@3=v4y5%ok8X37JAp zS?>Ebu+wTK1H4w0tgFYzb{qVkcn&$x|4_J@WFDdgX_FUGP-!I$9lkT(9T{i;{@iWO zJk98A67cp`c}C?eM61b>*rk2*Iw7wy6|o*7Sk)5Xz=QJi&9gIC^FAMVjR;~wI@4JRC|%a z+;Zv1_E$BroBLpYGOnPBb)ie2tPF_cLSgI(koKtQFe4#VSb^kH0$>e!0cnV94BYtO zo70Sp-1Ak@PX2oGk&BuaNg{}zpGWlR?BvIi)##GbXHlkY-<*w^w0Q^7PX*5G(ykzm zg3l5|i>Bo)qIMZNPph)J@-c~cHK>!Ou7y9^#SN`Wyd2orETiysJ;cm0GDVmOy|;Z& z-aqFIFSx4in(=gCv>N*b|AN7rm?@{;=_6B*N z_)QuKtAO?>*>Au4t=9>5Qw8R^-4``FylVP5O`gKgF69JEbX>xM7m1CYeLbZ=kFLDa=`A4R6}L z#`b z?bIf2C2J;U9+e{ZO5qkPhkrsCkxlJ+CSwkTk)q!o|H^$0^qHy#Sp^W;y1smHJ5JFN zqcwUT>iv1hOXbXf6%;f-;8ZmoikD(gGa5D!ggWG5GtKpD9DQ?IqZPne&fWzLln7mi)T6Ak5Hzls-l%%iIN-$Hq24XL-ww0yw2c`%JIDE{rXqCxx{A_$Pz%8E$&1X5ey_^ zLt)@-2s%~;#XEz;)5C6$hAEDk%os_d(;oT!=v31)3p&CvWIG$&{if%3S82rN=qen4 zcGm7*M#*7Yx!Qdm@n?uj03AX;C5tzjK3VMjI>bXJ*7pu7FITmy-tnzjbEh!3w>2^xhp9NwE1XxxS zVUCzbhv3zkOir7ZobYIus_cW7=$}_VCbJcE{>G~c^cdH?Hj2L;Sxu+$LAg%x{Bp-Z z0y>PU{oC0s6Uk}lM!3QC1{26t%m8w-1hi{Ij6%20A&~vZ0HrU=m&<-1Bx70R+$n`z zfd1rBWR!}qWe&!EHyENLRxoZM*OnHvR^n6#b3y3;;WFHxJ2<99d4W)_8?4S5v3y5Lb1=-?4~JLGu<3xapeY}|Di^OUibEc$NQ%Fn@C(=vd_NzYO1 zsEEx>7fgbXMw-a?_1->^I0sXL!0g1k4h5i}vwb%sFD^8TEk3Fg zE$e1c4K4~iNsa#1TexeOBagmZ9!4x9^2daeyd92WS}G_0tbo`1+PO&Pc^qwH%!jgN zR4dPnfcLn9d5996&K%v@X+u1_*aR7q>X@rtxBRIKH1fNM&L5chY*M~+j)fjK=2Y=5 zUq76UimBgu%B~7O1IPc>&>vl(9GUfBbL}NlTls6+Pr-EbOx8@z7NJV*S935h%D6zi z9aOs%23_GT%O2NWfp0HvuMFGSLe8P#Jf0pbVk|pNz%?o zUKeY@KJJ;-G?b4i+d)7GUb#rWHV<1hnbx{y=T{a6z_slr-wW52UK5UR2XapYIssM8 zxKjNvus~3@+F3D&i=Y1ZOTAUM>RAxBxpH7F?eSDsKj1=^OwUfz$)0t*b@*mjd(T(& z7QDCFgXQR-0Gi+Hj$By~fF;JU(l#b#C&MEG4@`K^%rq{)wT$N;#KcD9GL*ov_+VaR z_N%XEaWQOt+FW!L4-qnSA!Fn$QDQSncf;OEpQdhN8P)&*06|VoM#4z|000000002r z0RRmF07C!(09H^qAgW0K0Q4gPodGJ~0S*B^Q6!E<0+m9sfB>MDcA)3QFS)PZSh8O~ z)#p_8r4y>4wmcXAi&8Yg81XC!>l4a2Z&WwSz%tPA+GX+C^>66^*#1j-)A|41f5ZIL z_{9Db9e)@8H<^EIKaKt?_j}|A`5p@X;r)N0kB|Q|zCxi6;y;6b3;$#MPp*IEf9CYd z?FapDa8Kxe*}v-g_u^mppZmVTztO+We$V^Ycuf8G{%^3KPBRoftB|SLZjy!6F=7`B zbwq-~#2ywaDKf9I{@cw|dyI@>xC}B)qYZG=3*Z4Uak_Lh_MT^kTF9+Smu|%5qaDOB zH{%`yWa{#pNN`yfkpsJnEH$SvAH=%sbrA+f>L5x9TWx%H$Fz<#`+^k(c2XkJc6syF zZofR_S6-LdyKX8^V&}-d!xC&h0Mn!)+$yR@=-Mo|JKdnnb>q<6SA{`E6P_%=D`v(X z>CZ9T(o35@F;7`TEI|%=he=Z{jqca}8ZA-8nObSw0Yi-c)%UZO87?<&ggCFv~R}b1% zcykaZ#dXRPQ5O>NxM0R=A%OULZM}GP2R-Z1-xrT!yXzx3Rv?4fOiL zTeR6=A8O;!ghY&qNN)`TVKH<4E|TNB!w^+th)>DJBBPR=Ws?wM!Y|%5si8_#RJ(rW ztg6F8>Sg;7pf6mIzY2M%)_Sj)KZqSJi)H98O;xz#(9?5XqJK6lV(tB=bv0->ca=J5 z)>k)(6??9}wHIbhq&#ld^2+i7_rnqWHafw z%L*`vr$>%U!Ukid3!Fp;I{B8FXCNoBJTwXT|9H4!7UT<2AnwgThhR2CBv#1inDE3S z6@EXPCX|wZ?$jP7aj_ZUpWe0ot0)(jvfA%M;$YB!;f7g3O_ExlN@9>uiVpwfyVvR8 zyI*mn_i6ZuLGj*m$fi|Vg85c=piZ2a`4jq=ea>Ge zT*@Agrdtsuq_#vRfh2TDZ`Ht^8U{a@TRh_i;dd0rCX>?_EEhCTNk}p_ze*#$3j1iV z&LG^4Fm~oSe0b?xjfNSWE^op@4yq&;`!iWR!>}1>Nnlx2INMEiW*w>Et)A`sQoV}f zXjBl=!Q5l(CX)pPC$FX2UMDa-%81G_*L*QruGDU7Lt486n(a6@$+!L=C@rqxe1NXi0E+Ye;!d20Nr^(As~KzB5(tH(1`9>Ahd*kdk(ga8r}(9bb>EILYPd=NW?TV5&AOTvSDhE1Rus4%zVQXosX)z3z}ra+XT5 zs`4t8cyN{%q!OgrZ*QvUL7jv#ao+CAy~TbCMisjy}f`j`BlP^qgzC zCEJL#NC@ntpZ=#iJW)&!YPn77zT(h4c&0d*JCOA&6NUN0!f>rs(RTSkAplU$$%lBH zVk#n!=F$FzgM_#N6+nAk0U}kS0I;5g7Y#{zcWb{L>9*U&y+>N{fH5`1a(jee3o3>6 z8G_58ay zPHu45_&-nm)@N|AT=9InwEo!;WSX!xvA2X0yxv$=h2hr~kdSrePe8~*(%wX~G!i*b zq85|C4!lmSu##FFs7e}MVLpYuPDeb>VXEa>3X)yoE}RT8#cOxGi5u-4Qo%6vQAKB8 zvw~*IMH>$^==OSGr_sHuh#z`y|+IOgK5_L!($6cn(jc z|A3lRlUv+@GMeW+<@zC-4hN4tM%QqZNMTFn$Z9?I?7g`fwJ_J!$38f6g`AQEmo*O4 zd+xrvRZdYL4DV$=@0mFFqZU4wMq8xIFjF(n3p)|v9^m?i4Qbw@8P0ty?A4#_!*P3X zZbO-{ASS)Y9FJf(y%X+X7>;YTp>EGSCjhxnyG<9nco>D1Cjj^!qxxyIN^DG5h_Diq z)YKm6X2Z5VbMoF(Q!6ZktySYtY7&zpVyyqY5ulJ|YRe*8;OmW}+&+!l2renybPRfC z*pWL{1@-Y+=>EA|(#p#cGnoy*!1Z#cS^GMiU$Bv2HV0}x3HU>=vhD@9d>-e@18tD> zaQ^MJ(AR{Jx;4YQTCxm%;uRfzU|TF%ws0eTrknE z18HSDwa1sQ_Dc!={PdQjBe;AJ*Q{?2L=JQAL63z&O{s)$3l|%nBJ+mm2a@>C28Rcr zxVDJtk>Z#R6gHsS{t${E>VT~UU;zIA9ctzm3iZMNxp4AuSc*pl5Z0ILcfbTH%SY)#J|t&uWH4FvoEj5Le@k^;{l|;#*6s~s%nF_ zAN1Sj&zH2oKCK6r=}8GpkX^}^4IV~!uHoO?o{?IGX`#0REhOF>xM%KymT+I7nD9PL zbwo+rDW==w zKkLp~!zFvn4J*XcFaUc@k3xUz_SS_L_ecH7%{cj{O;nZ{b8S; zqf=eOYv~Br`NOT(Fg7tvV@0ScIyQ-7$l|)2e=>Co{Fk(-J)5;5S+!zHngeO$5e@o^t9(=!DaqKX~6bOP;9+WRU3cz!F=*?$uhuvgO;u)5fxEV zXk{C?mlz@nmsMVq7hvybY0Y#x9D!NKF)GcG# z6SB=OOni&>Qse(^{=g{|2xLAEeVQd+(erfIAf{*pN=wh3sJ?Zco`j<|?tOmWQ_R7q z1#UnsC9F-TlCzYWS-V2mPE4qPUJ)L3eZ?mPJXDc0Kl)kScG#_>Vkq2RiMNs`@3aH{OIEc4oZKBb8BVp@r@w_zPh4w*FlsY8pM&tT zziw>K^M3M%<$^_e3%9R@m&ULr-P-g(NJ?a~`^2lu*B}?;sv6d!#)nbzc(QJegk(>N z@$CU@f4L+QDEOgkp^{ZNrUV3tZ*UH85(ezjWa=Y$NF_QPEqQ8bh53Ii9rp`Bw|=>> zE5HIJc*0EgXpYeGum`@u69NIeG%O^e%TiPqDBy|J50<&F_xO18M0S5jAtHSPo{K z!epw0I<*=AN^u{@sLJd+If9(-j(vf=W_j%^f z!wYR1R`^P6$oOe~XVJY=TQHC2foWNGi9SonL_?APjuY_ zKg46iGiSD6O6OPZfL&^m7c(xzAeCQF9c4A}g|^e6ymxki2K8*AV*xI1ta`PBGC!Iv z1Iw1l`|s}t9EE>?#zeXsLn*xV{J`mM>u2tl&x}<@_?St)x5-Sg=LN&PpD*FIzIUnd ztWc;Q)SnKPUSKJi#(3Mmi1fjhcvdC9jvkTnrDSqA^az>IA`F*^_7RYd1F|C2k@9AG z`Gx()mqDu7+anx_XvHiZNmmcp@@cV$FHn6M8KF!!Ykt*dHh|gL$I%oJ9Be?*ZkjtU zZ0hC!*I7hJqqaFOsm>hOG}WZbMeNZVZFib9QTau$wpoEgQ->d5TwpiwoX;wrL zH;%IUBJ$Rr(>ZPfvv{0%0rz)3Vmo(bQ??j7m$@cv*j!7hLa}Q+8Q9A!EFmQ)TQ>7q z%3lp^ln$-^TG2*TQ4L+e`XAeZ(0>(kgJ=&62W=evSaGS(@g=-NOU%^Cz2No1B1XW@ zb}--jgh%hr%!ss5LjWFv`a>TKyt{cCJ-KFK)_5~}`|^>nzefkp4O3iQJBfrsQT8Gh8+3int*8n2ddEwhRq6 zV4)67M(lr}#E7D_OZ^dRj`$~-rUBayNGw^Ustr$N7`MF&r6lw5G?9Vm0<4nYzao)e zR+@v@Zeky>g|8MGZ+fc#W>h%ZDQ4%yUts$goktY>IIRS`(eDkUz~QQnqiW;1bh(Lr zln7D|uSX`WgzY9|-i%N?1K})E4p(#r3wnW(525qD#c%B6H3@(jkmQwzKVlcT)}#f7 zEZ37F)antYemmn9P6NpKiK71`pv=WxBE$v~nZc2ZQjTc-?`1O>+lTsX=LMq&Z1#Q# zNO;-+5b$U_&dMwKF?F|_x&&ZFl}!05h`6_HhKJfsj=Ywwx^#wj%G~fd!0IS+ySnAp z%onbuF+9E)|6DiF(Ezc;Sz`MMX*;34p|BWtH-!mzbArt|O$T>fKc)hryNUz2(;Pgn z-vg9siWXHE186qcHZ6CRTJM(~Zg^o}>72D314nt8;LhZj2TI)Pj-k9{`{eF@w2iMO z9j*)@ot9T_w~-R4#icd|eWNSkpq`i`6QSumL6R$mq`sRz%ACp=6fdMGU@I>At zX70;{vagnKDcXA0@4bi1V_+(C*Cn)Qoo3#%v$FmoYQA4@i;Z2lVc6#E)*{&rni761 zwf2FKGAwXg{JrJc;T5OK-zXQZ*{9hBi(Q8bD5>~Ua9`@<`s^oXKWbK27QCu;pTa^^ z7?n_F?$F#GUF2!NGNe5)KQ^kMRldyUJ5+7Xa)&t-Tpzo0`;;pGV)^DH%o@KHeGvHJET7VbCP> z7hFZV86o@&&#Q21p{XznpUBj34bp> ztJ5Q?2^u=co`}2Mw7S(M>@M z_YhOKT;n>5(wD^C;MEKYL0s9OeIs?Pnv-5lRX>R{|`oM{7NZ5iB(WorfQvl0no)T%2=kSq(=B z8*IeDePgbSP~3$zLPX~BIu>wkFDzwT#s{m_7AX=&*+gBf8AtTZRqol%`DIX8FD(o- zq{j}hiAL%(3xtSGqU6D-Yx*i5+3!duzq~sR_Z~xCi|H}!Sy?7iGFvS#(!(B3j2`DrMM`7#o0ApS0ahQ?|_Q0EBqlrths{YEDJ2+!JDOZ z<)RnZbkd$5>4mh#R-@=lXS9X~90dguM8_8E{3yU|EY;l!`LV$QFbhZY1y}D_H6FY5 z{kI?36r8(;ERs(oEV$AR(e0V|%AOQuZA-b4oIs?o7|c#q1Vz3bT=^DyhLv0)@{!(k z9&JwC>`_n!mr9L2UwD;cPI2Lhqjx!c%^JL_b$K|$b|+SY_h&4|h5_E0TW90dnF(3{H?AMdImtcbe7-I{Hx z8oG~7C;Glw>`Xo0G8Mexh8d9=&AQqKg!xt?<33M_wG83Ml#P{9jGfLPFShyL{hZ9Z z*~ZPFqDFxpwIKsc3y$STD7InIJIKE}IK$%_^}L@R09pc|d`36w06=#Mt9uq+-F{;* zJdNZ#%Q9WxdNb47JqmMCZid*E_=DYT2$f<{y3Vv}(g#_L7IiPf)IAI}^4vMfM*RS@ zH9h6%MZqsPG=P1j0WIMg&!GpkA8YPo#*YC{2p_ zC+z~l*}?Fx-e`9_NkCJX<@k~=n>;+=s@o4Dm}`Oow)&&ji@fEq!Z-=CFyHm6$k<1;)Fh!WaBSTPU4xz)N-S2b&^_ z8B&UNsq_a`uj_zTdsV{#Km(nP$*h92m_T>1EFNm(V}A5=wS5~Kcm$*}ORxqQF5w8_ z-FqoIB3!e!_1iW{i@VCETG|s97FTf}2{Fqih3^9{@X45uYM664enZ_|lRSFIw9|O5 z$xU`=e_+p5Pvu`kVphuQ-Tg$eqh#9Dcl<0Q z;)rUJe)~7DcfILn!;bBvds)3Rj@2w9<>4xv8fGyOEy>IdZaVpZc*VV|`B+ax$XP3O zesEuP_x01%zYIQm* ztFr{M2>IM$d*X$7M98f%0AY1&r(HrcX7DYOZJTf@ZFE+ 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
  • $uI=I`3 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 z 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%0DGu5|BKR4$kh!k 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%0D%{OY%Vnv&7Ir%O1##EOe(KI`-UPgKrw7 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

    2T|FdVh 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 )NSw^p*%xo*j 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

    OF)K;hIIf<;wc0gwGW?r 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�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

    #n^v^ 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 lg6xeI%*4ncuT1KG!LG|T7n void; - onPrev?: () => void; -}) => ( - - - - - How to connect guide {step}/{totalSteps} - - - {description} - - - - - -); diff --git a/nym-connect/mobile/src/components/HelpPageActions.tsx b/nym-connect/mobile/src/components/HelpPageActions.tsx deleted file mode 100644 index 13cba78fd2..0000000000 --- a/nym-connect/mobile/src/components/HelpPageActions.tsx +++ /dev/null @@ -1,20 +0,0 @@ -import { ArrowBack, ArrowForward } from '@mui/icons-material'; -import { Box, Button } from '@mui/material'; -import React from 'react'; - -export const HelpPageActions = ({ onNext, onPrev }: { onNext?: () => void; onPrev?: () => void }) => ( - - {onPrev ? ( - - ) : ( -

    - )} - {onNext && ( - - )} - -); diff --git a/nym-connect/mobile/src/components/HelpPageImage.tsx b/nym-connect/mobile/src/components/HelpPageImage.tsx deleted file mode 100644 index 69fa38ca31..0000000000 --- a/nym-connect/mobile/src/components/HelpPageImage.tsx +++ /dev/null @@ -1,5 +0,0 @@ -import React from 'react'; - -export const HelpImage = ({ img, imageDescription }: { img: string; imageDescription: string }) => ( - {imageDescription} -); diff --git a/nym-connect/mobile/src/components/HelpPageStepIndicator.tsx b/nym-connect/mobile/src/components/HelpPageStepIndicator.tsx deleted file mode 100644 index fc108425ab..0000000000 --- a/nym-connect/mobile/src/components/HelpPageStepIndicator.tsx +++ /dev/null @@ -1,14 +0,0 @@ -import React from 'react'; -import { Box, Stack } from '@mui/material'; - -const Step = ({ highlight }: { highlight: boolean }) => ( - -); - -export const StepIndicator = ({ step }: { step: number }) => ( - - - = 2} /> - = 3} /> - -); diff --git a/nym-connect/mobile/src/components/InfoModal.tsx b/nym-connect/mobile/src/components/InfoModal.tsx deleted file mode 100644 index 07f98769e5..0000000000 --- a/nym-connect/mobile/src/components/InfoModal.tsx +++ /dev/null @@ -1,69 +0,0 @@ -import { Close, ErrorOutline } from '@mui/icons-material'; -import { Box, IconButton, Modal, Theme, Typography } from '@mui/material'; -import React from 'react'; - -const styles = { - position: 'absolute', - top: '50%', - left: '50%', - transform: 'translate(-50%, -50%)', - width: 200, - bgcolor: '#292E34', - p: 1.5, - borderRadius: 0.5, - height: 'fit-content', - border: (theme: Theme) => `1px solid ${theme.palette.grey[700]}`, -}; - -const ModalTitle = ({ title, withCloseIcon }: { title: string; withCloseIcon: boolean }) => ( - - - - {title} - - -); - -const ModalBody = ({ description, children }: { description: string; children?: React.ReactElement }) => ( - - {children} - - {description} - - -); - -export const InfoModal = ({ - title, - description, - show, - children, - Action, - onClose, -}: { - title: string; - description: string; - show: boolean; - children?: React.ReactElement; - Action?: React.ReactNode; - onClose?: () => void; -}) => ( - - - {onClose && ( - - - - - - )} - - {children} - {Action && ( - - {Action} - - )} - - -); diff --git a/nym-connect/mobile/src/components/IpAddressAndPort.tsx b/nym-connect/mobile/src/components/IpAddressAndPort.tsx deleted file mode 100644 index 4ecf356d18..0000000000 --- a/nym-connect/mobile/src/components/IpAddressAndPort.tsx +++ /dev/null @@ -1,55 +0,0 @@ -import { Box, Typography } from '@mui/material'; -import React from 'react'; -import { styled } from '@mui/system'; - -const IpAddressAndPortContainer = styled('div')({ - '.hoverAddressCopy:hover': { - cursor: 'pointer', - textDecoration: 'underline', - textDecorationColor: '#FB6E4E', - textDecorationThickness: '2px', - textUnderlineOffset: '4px', - }, -}); - -export const IpAddressAndPort: FCWithChildren<{ - label: string; - ipAddress: string; - port: number; -}> = ({ label, ipAddress, port }) => { - const [ipAddressCopied, setIpAddressCopied] = React.useState(false); - const [portCopied, setPortCopied] = React.useState(false); - - React.useEffect(() => { - if (ipAddressCopied) { - setTimeout(() => setIpAddressCopied(false), 2000); - } - }, [ipAddressCopied]); - - React.useEffect(() => { - if (portCopied) { - setTimeout(() => setPortCopied(false), 2000); - } - }, [portCopied]); - - return ( - - - - {label} - - - Port - - - - - {ipAddress} - - - {port} - - - - ); -}; diff --git a/nym-connect/mobile/src/components/IpAddressAndPortModal.tsx b/nym-connect/mobile/src/components/IpAddressAndPortModal.tsx deleted file mode 100644 index fcb0f1dfaa..0000000000 --- a/nym-connect/mobile/src/components/IpAddressAndPortModal.tsx +++ /dev/null @@ -1,47 +0,0 @@ -import React from 'react'; -import { Box, Typography } from '@mui/material'; -import { InfoModal } from './InfoModal'; -import { CopyToClipboard } from './CopyToClipboard'; - -const FONT_SIZE = '12px'; -const FONT_COLOR = 'grey.400'; - -export const IpAddressAndPortModal = ({ - show, - ipAddress, - port, - onClose, -}: { - show: boolean; - ipAddress: string; - port: number; - onClose: () => void; -}) => ( - - - - Paste below values in the proxy settings of your app - - - Socks5 address - - - {ipAddress} - - - - - Port - - - {port} - - - - -); diff --git a/nym-connect/mobile/src/components/NeedHelp.tsx b/nym-connect/mobile/src/components/NeedHelp.tsx deleted file mode 100644 index 12d9587a5f..0000000000 --- a/nym-connect/mobile/src/components/NeedHelp.tsx +++ /dev/null @@ -1,16 +0,0 @@ -import React from 'react'; -import { Box, Button, Typography } from '@mui/material'; -import HelpOutlineIcon from '@mui/icons-material/HelpOutline'; - -const HELP_URL = 'https://docs.nymtech.net'; - -export const NeedHelp: FCWithChildren = () => ( - - - -); diff --git a/nym-connect/mobile/src/components/PowerButton/PowerButton.tsx b/nym-connect/mobile/src/components/PowerButton/PowerButton.tsx deleted file mode 100644 index bc399e70b9..0000000000 --- a/nym-connect/mobile/src/components/PowerButton/PowerButton.tsx +++ /dev/null @@ -1,149 +0,0 @@ -import React, { useCallback } from 'react'; -import { ConnectionStatusKind } from 'src/types'; -import './power-button.css'; - -const getStatusFillColor = (status: ConnectionStatusKind, hover: boolean, isError: boolean): string => { - if (isError && hover) { - return '#21D072'; - } - if (isError) { - return '#40475C'; - } - - switch (status) { - case 'disconnected': - if (hover) { - return '#FFF'; - } - return '#BBB'; - case 'connecting': - return '#FFF'; - case 'disconnecting': - return '#FFF'; - default: - // connected - if (hover) { - return '#E43E3E'; - } - return '#21D072'; - } -}; - -export const PowerButton: FCWithChildren<{ - onClick?: (status: ConnectionStatusKind) => void; - isError?: boolean; - disabled?: boolean; - status: ConnectionStatusKind; - busy?: boolean; -}> = ({ onClick, disabled, status, isError }) => { - const [hover, setHover] = React.useState(false); - - const handleClick = () => { - if (disabled === true) { - return; - } - if (onClick) { - onClick(status); - } - }; - - const statusFillColor = getStatusFillColor(status, hover, Boolean(isError)); - - const getClassName = useCallback(() => { - if (hover) { - switch (status) { - case 'disconnected': - return 'expand'; - default: - return 'contract'; - } - } - if (!hover) { - switch (status) { - case 'connected': - return 'expand'; - default: - return 'contract'; - } - } - return 'contract'; - }, [status, hover]); - - const buttonPulse = () => { - if (status === 'connecting' || status === 'disconnecting') return 'pulse'; - return undefined; - }; - - let statusText: string; - switch (status) { - case 'connected': - statusText = 'stop'; - break; - case 'disconnected': - statusText = 'start'; - break; - default: - statusText = ''; - } - - return ( - !disabled && setHover(true)} - onMouseLeave={() => !disabled && setHover(false)} - > - - - - - - - - - - - - - - - - - - - - - - - {statusText.toUpperCase()} - - - - - - - - - - ); -}; diff --git a/nym-connect/mobile/src/components/PowerButton/power-button.css b/nym-connect/mobile/src/components/PowerButton/power-button.css deleted file mode 100644 index b73d01ca32..0000000000 --- a/nym-connect/mobile/src/components/PowerButton/power-button.css +++ /dev/null @@ -1,72 +0,0 @@ -#ring-expand { - animation-name: rings-expand; - animation-duration: 0.4s; - animation-timing-function: ease-in-out; - animation-play-state: paused; -} - -#ring-one.expand { - opacity: 0.3; - transition: opacity 0.1s ease-in-out; -} - -#ring-two.expand { - opacity: 0.2; - transition: opacity 0.2s ease-in-out; -} - -#ring-three.expand { - opacity: 0.1; - transition: opacity 0.3s ease-in-out; -} - -#ring-four.expand { - opacity: 0.05; - transition: opacity 0.4s ease-in-out; -} - -#ring-one.contract { - opacity: 0; - transition: opacity 0.4s; - transition-delay: 0.1s; -} - -#ring-two.contract { - opacity: 0; - transition: opacity 0.3s; - transition-delay: 0.1s; -} - -#ring-three.contract { - opacity: 0; - transition: opacity 0.1s; - transition-delay: 0.1s; -} - -#ring-four.contract { - opacity: 0; - transition: opacity 0.1s; - transition-delay: 0.1s; -} - -circle, -path, text { - transition: stroke 0.5s, fill 0.5s; -} - -.pulse { - animation-name: pulse; - animation-duration: 0.5s; - animation-iteration-count: infinite; - animation-direction: alternate; -} - -@keyframes pulse { - from { - opacity: 1; - } - - to { - opacity: 0.3; - } -} diff --git a/nym-connect/mobile/src/components/ServiceProviderPopup.tsx b/nym-connect/mobile/src/components/ServiceProviderPopup.tsx deleted file mode 100644 index 660fee4134..0000000000 --- a/nym-connect/mobile/src/components/ServiceProviderPopup.tsx +++ /dev/null @@ -1,37 +0,0 @@ -import React from 'react'; -import { Autocomplete, Box, Dialog, DialogProps, TextField, Typography } from '@mui/material'; -import { Service, ServiceProvider, Services } from '../types/directory'; - -export const ServiceProviderPopup: FCWithChildren< - DialogProps & { services: Services; onServiceProviderChanged: (sp?: ServiceProvider, s?: Service) => void } -> = ({ services, onServiceProviderChanged, ...dialogProps }) => { - const options = services.flatMap((s) => - s.items.map((sp) => ({ id: `${s.id}-${sp.id}`, title: sp.description, service: s, sp })), - ); - return ( - - - // filterOptions.filter((o) => o.title.toLowerCase().includes(inputValue.toLowerCase())) - // } - options={options} - groupBy={(option) => option.service.description} - getOptionLabel={(option) => option.title} - onChange={(event, value) => onServiceProviderChanged(value?.sp, value?.service)} - isOptionEqualToValue={(option, value) => option.id.toLowerCase() === value?.id.toLowerCase()} - renderOption={(props, option) => ( - img': { mr: 2, flexShrink: 0 } }} {...props} fontSize="small"> - - {option.id} - -

    {option.title}

    -
    - )} - renderInput={(params) => } - /> -
    - ); -}; diff --git a/nym-connect/mobile/src/components/ServiceProviderSelector.stories.tsx b/nym-connect/mobile/src/components/ServiceProviderSelector.stories.tsx deleted file mode 100644 index c7df76ea4a..0000000000 --- a/nym-connect/mobile/src/components/ServiceProviderSelector.stories.tsx +++ /dev/null @@ -1,342 +0,0 @@ -import * as React from 'react'; -import { ComponentMeta } from '@storybook/react'; -import { Box } from '@mui/material'; -import { ServiceProviderSelector } from './ServiceProviderSelector'; -import { Services } from '../types/directory'; - -export default { - title: 'Components/Service Provider Selector', - component: ServiceProviderSelector, -} as ComponentMeta; - -const width = 240; - -export const Loading = () => ( - - - -); - -const services: Services = JSON.parse(`[ - { - "id": "keybase", - "description": "Keybase", - "items": [ - { - "id": "nym-keybase", - "description": "Nym Keybase Service Provider", - "address": "Entztfv6Uaz2hpYHQJ6JKoaCTpDL5dja18SuQWVJAmmx.Cvhn9rBJw5Ay9wgHcbgCnVg89MPSV5s2muPV2YF1BXYu@Fo4f4SQLdoyoGkFae5TpVhRVoXCF8UiypLVGtGjujVPf", - "gateway": "Fo4f4SQLdoyoGkFae5TpVhRVoXCF8UiypLVGtGjujVPf" - }, - { - "id": "shipyard-keybase-1", - "description": "Nym Keybase Service Provider", - "address": "D55ksecHzY6vAeqk8MCTzCfj2pqwJeKCKZCUUGnwGnn3.FS42vXS5a6GNTb1qk3aVk5mjSiJCAuawbBVyQZZVfhvt@DfNMqQRy6pPkU8Z5rBsxRwzDUzAMXHPFwMhjF16ScZqn", - "gateway": "DfNMqQRy6pPkU8Z5rBsxRwzDUzAMXHPFwMhjF16ScZqn" - }, - { - "id": "shipyard-keybase-2", - "description": "Nym Keybase Service Provider", - "address": "DFdDtW7LNBATxQ4ef3jNbqs3cRE8b9wDZTCctHCQRULa.4AbKiTNVUwYFWHhy98o5pT9dELiUrkXoJQ9wHqPgf6GV@GJqd3ZxpXWSNxTfx7B1pPtswpetH4LnJdFeLeuY5KUuN", - "gateway": "GJqd3ZxpXWSNxTfx7B1pPtswpetH4LnJdFeLeuY5KUuN" - }, - { - "id": "shipyard-keybase-3", - "description": "Nym Keybase Service Provider", - "address": "6Y1HE1jJ92P9yoHer11TR4A2NdZePrLGaBNFf65MnYGe.FwXoh217odQDWNmViqzNX28fauYrjB3PYLrVvpqnQrX4@5vC8spDvw5VDQ8Zvd9fVvBhbUDv9jABR4cXzd4Kh5vz", - "gateway": "5vC8spDvw5VDQ8Zvd9fVvBhbUDv9jABR4cXzd4Kh5vz" - }, - { - "id": "shipyard-keybase-4", - "description": "Nym Keybase Service Provider", - "address": "3zzhLtWvaJgn755MkRckG5aRnoTZich8ASn395iSsTgj.J1R5VuxXbh2eNHiaRbrwbKGXrrEQcHKLdzf8eg9HTB6q@3B7PsbXFuqq6rerYFLw5HPbQb4UmBqAhfWURRovMmWoj", - "gateway": "3B7PsbXFuqq6rerYFLw5HPbQb4UmBqAhfWURRovMmWoj" - }, - { - "id": "shipyard-keybase-5", - "description": "Nym Keybase Service Provider", - "address": "CHuXdZJYQ8xH7ekgN9gAuVtQ7ZikjjHEZY5BSN7yc5mN.29dFvqicKQQQvoX1vup44mspmc249RH5xgLibWMwTYGT@CfWcDJq8QBz6cVAPCYSaLbaJEhVTmHEmyYgQ6C5GdDW9", - "gateway": "CfWcDJq8QBz6cVAPCYSaLbaJEhVTmHEmyYgQ6C5GdDW9" - } - ] - }, - { - "id": "electrum", - "description": "Electrum Wallet", - "items": [ - { - "id": "nym-electrum", - "description": "Nym Electrum Service Provider", - "address": "DpB3cHAchJiNBQi5FrZx2csXb1mrHkpYh9Wzf8Rjsuko.ANNWrvHqMYuertHGHUrZdBntQhpzfbWekB39qez9U2Vx@2BuMSfMW3zpeAjKXyKLhmY4QW1DXurrtSPEJ6CjX3SEh", - "gateway": "2BuMSfMW3zpeAjKXyKLhmY4QW1DXurrtSPEJ6CjX3SEh" - }, - { - "id": "shipyard-electrum-1", - "description": "Nym Electrum Service Provider", - "address": "8Tb73cFQpXCLpgxEA2VSDru2hHrcZ3KQcyMsGbxcTjBp.4x5tu66k8YkHk4tYac1qwEFbNq5WsKiX5kR51q5KKH88@4WgKhJdmUffz4e1o1ftVAGS3HnG56LiNAxA9dmaekrVd", - "gateway": "4WgKhJdmUffz4e1o1ftVAGS3HnG56LiNAxA9dmaekrVd" - }, - { - "id": "shipyard-electrum-2", - "description": "Nym Electrum Service Provider", - "address": "GR6z31MwCsvxHrnvvVN1Cpasd8aQ1giwQqPTZM9dN7VH.5rEiqakSPDrBtKmvpU8Shnhz6gRM85JLoB7AX4h7PJYr@5Ao1J38frnU9Rx5YVeF5BWExcnDTcW8etNe9W2sRASXD", - "gateway": "5Ao1J38frnU9Rx5YVeF5BWExcnDTcW8etNe9W2sRASXD" - } - ] - }, - { - "id": "telegram", - "description": "Telegram", - "items": [ - { - "id": "shipyard-telegram-2", - "description": "Nym Telegram Service Provider", - "address": "C4w6ewbQtoaZEeoaaNw1xVASChqo4WVjNfuYEUFjZxpc.8F1D7rQXf2jGoj1Ken7PiGDM8HS2Ug79wSoc9nZ1iqh1@62F81C9GrHDRja9WCqozemRFSzFPMecY85MbGwn6efve", - "gateway": "62F81C9GrHDRja9WCqozemRFSzFPMecY85MbGwn6efve" - }, - { - "id": "shipyard-telegram-3", - "description": "Nym Telegram Service Provider", - "address": "DStL3BEUZuQZfbij1KAY3BvJh8rC5jpr9mc6AQ6aTLUu.Ax9foYaKfFgX6g8y393GoNpKkKrnDGFGRZwxDv9R7X6M@FQon7UwF5knbUr2jf6jHhmNLbJnMreck1eUcVH59kxYE", - "gateway": "FQon7UwF5knbUr2jf6jHhmNLbJnMreck1eUcVH59kxYE" - }, - { - "id": "shipyard-telegram-4", - "description": "Nym Telegram Service Provider", - "address": "8gRdGTzsDxYzpasRQhsRg59MCgNNhnfag2oFfwwZPXnB.DtDrGz7ScVm4o7sN4K3CYUJveYgz7fcXELBVLNDfMS9Y@3ojQD6V7skM1bSXJX7fVQvscjmcgptzdixQEaAha2ixh", - "gateway": "3ojQD6V7skM1bSXJX7fVQvscjmcgptzdixQEaAha2ixh" - }, - { - "id": "shipyard-telegram-5", - "description": "Nym Telegram Service Provider", - "address": "AR3oEM6Uvmfs6fyddwSehoBUKCFxz7MdFi4z7aahuHuY.3ZKapg9A3Py1PXhyLbCJr8ZbJsEV6NZdN1WJaGGut5tj@EEyq16v63aotPBCepxUpCgAojrNasZ6Hk1PjpRyBAdEp", - "gateway": "EEyq16v63aotPBCepxUpCgAojrNasZ6Hk1PjpRyBAdEp" - }, - { - "id": "shipyard-telegram-6", - "description": "Nym Telegram Service Provider", - "address": "7n1BYhsXSwcr8Qim8AqZTAodqFia4QG6T7CRc1ihQHpv.7o4trpGqu2LHMUiXc3dddgfGET1CFFcAK9gKYoHoSn5e@BTZNB3bkkEePsT14GN8ofVtM1SJae4YLWjpBerrKYfr", - "gateway": "BTZNB3bkkEePsT14GN8ofVtM1SJae4YLWjpBerrKYfr" - }, - { - "id": "shipyard-telegram-7", - "description": "Nym Telegram Service Provider", - "address": "Gv4TWhUKrvJfqh1jBRPGEQrikNZvZse2kS3ZgN9Z2nAZ.7KGPaaqUEum2C59jLvw7f8Ug8a48YuZdjjZu3t4JES4U@C7J8SwZQqjWqhBryyjJxLt7FacVuPTwAmR2otGy53ayi", - "gateway": "C7J8SwZQqjWqhBryyjJxLt7FacVuPTwAmR2otGy53ayi" - }, - { - "id": "shipyard-telegram-8", - "description": "Nym Telegram Service Provider", - "address": "8Mqgp12cpF6FSXMeqzxgFgQXvTSapyNqGAi5wy7ub4ge.7z7PDsiJGiGxGz4p77v5L5fZhXBJ5qNZ8CgJwYNr6H6J", - "gateway": "3zd3wrCK8Dz5TXrcvk5dG5s9EEdf4Ck1v9VgBPMMFVkR" - }, - { - "id": "shipyard-telegram-9", - "description": "Nym Telegram Service Provider", - "address": "F3N5eiPDZcGFC985Go4Mpv8p9uxFD1L3jRUdrLCbrZLm.EyTxWwwTwYpPrJBmc97GLd1LpUAphjptS5y1ed182bGk@GAjhJcrd6f1edaqUkfWCff6zdHoqo756qYrc2TfPuCXJ", - "gateway": "GAjhJcrd6f1edaqUkfWCff6zdHoqo756qYrc2TfPuCXJ" - }, - { - "id": "shipyard-telegram-10", - "description": "Nym Telegram Service Provider", - "address": "G7y7e1nVBr8fmQSzdeAxXnCmmmJb5k8N3E8LBV31KE5g.GRRUCj6t6cCUUjakmTWzidMLiYA7EdCedKnup8osaBC6@AJad2R9virYEYXEsTcicN5y5tyPoixrhhAGsxoESZVnc", - "gateway": "AJad2R9virYEYXEsTcicN5y5tyPoixrhhAGsxoESZVnc" - }, - { - "id": "shipyard-telegram-11", - "description": "Nym Telegram Service Provider", - "address": "2kq9Z7RyDZtb8kxXjyP3ZT8VMWHg6JXFDChGuuNBk7Hw.F5XYbBaGSoF8qAo8faPcaNRPHEq3Y25BDcwESeabUS9S@HaLyPQrhBTq75dnGeBUdYWeFVA2BBn39MgkhEt3VTMMM", - "gateway": "HaLyPQrhBTq75dnGeBUdYWeFVA2BBn39MgkhEt3VTMMM" - }, - { - "id": "shipyard-telegram-12", - "description": "Nym Telegram Service Provider", - "address": "GegdtpNzYj4QCgpih9Kxv7ZVZxmVdxYHsDkiPsbT71XG.E8xtE8mrapjzFtyuziZSrsScAKhwZMH5wNpKWtKfzJ5Y@9Byd9VAtyYMnbVAcqdoQxJnq76XEg2dbxbiF5Aa5Jj9J", - "gateway": "9Byd9VAtyYMnbVAcqdoQxJnq76XEg2dbxbiF5Aa5Jj9J" - }, - { - "id": "shipyard-telegram-13", - "description": "Nym Telegram Service Provider", - "address": "4SsrDQeEtG3mpeD9nN5CDdGaCsxFvNeYMhoviDzNNB9f.GyqG6iK5rBvhe3HXLR11m6ULpf13ATgYvkkidLmteDLs@5EpkkrMFYAM3XcaztXnZwBWquURHSKsyc9JxUCengDFS", - "gateway": "5EpkkrMFYAM3XcaztXnZwBWquURHSKsyc9JxUCengDFS" - }, - { - "id": "shipyard-telegram-14", - "description": "Nym Telegram Service Provider", - "address": "9JoHRu2RrSD1fjbj9NSTASgjv9Szep7Nhd9L2PywxbBi.AZhAUDNX6iH8BqXyR5c7TJuzpwMEvDXrabNLGuRukvVf@9xJM74FwwHhEKKJHihD21QSZnHM2QBRMoFx9Wst6qNBS", - "gateway": "9xJM74FwwHhEKKJHihD21QSZnHM2QBRMoFx9Wst6qNBS" - }, - { - "id": "shipyard-telegram-15", - "description": "Nym Telegram Service Provider", - "address": "3K174ijjXqCkhMDT9xLcqjS4MXk2QsqZt4PdgNcuUrnn.BNnHnQmWoj6Uo6kkS1QkPqsdHaXrcwyR9F6MnnzDkZJL@C7J8SwZQqjWqhBryyjJxLt7FacVuPTwAmR2otGy53ayi", - "gateway": "C7J8SwZQqjWqhBryyjJxLt7FacVuPTwAmR2otGy53ayi" - }, - { - "id": "shipyard-telegram-16", - "description": "Nym Telegram Service Provider", - "address": "BqX5Q3MEcbTnM39hUswQchLW68SrqbhL8K5ucrLmtP39.AWrVsFoVC9s6KjdpcasATmZPA3GtMsUxcfHpAkuNdtFG@Emswx6KXyjRfq1c2k4d4uD2e6nBSbH1biorCZUei8UNS", - "gateway": "Emswx6KXyjRfq1c2k4d4uD2e6nBSbH1biorCZUei8UNS" - }, - { - "id": "shipyard-telegram-17", - "description": "Nym Telegram Service Provider", - "address": "2tQxccgcqdkuUvLqgiEkEN4rNRZ5QknygnKAFcS4gfoe.EVrY5q5sqDqBUbS3wHsRRZhk2MAw1S17hNoH1Bicyv7n@DAGQxdxwAkwjaLjTw1B9vndia4YyFD15qRgcTQxrmkom", - "gateway": "DAGQxdxwAkwjaLjTw1B9vndia4YyFD15qRgcTQxrmkom" - }, - { - "id": "shipyard-telegram-18", - "description": "Nym Telegram Service Provider", - "address": "8YG1rcEauJA814Nd7hSxjNe2UrRwrGsrXTm1Cmd3gRrU.FxYaYqpNN8PciNsySs3zYPrTB1J8AYUu9DBsM2vVDDfF@7EfEESLo71GUvx3UEW79LgTegHUBPUocUzGyJVv6LHog", - "gateway": "7EfEESLo71GUvx3UEW79LgTegHUBPUocUzGyJVv6LHog" - }, - { - "id": "shipyard-telegram-19", - "description": "Nym Telegram Service Provider", - "address": "HPiXADVFLwLQPNpPtyYefzvYntC6tp9UJ5fJZGfkqvDt.2EUUxmeT3AiaUzAcE5SyXRAk8a2JXBkRz4B8McSdkrST@9ACTkYraCqE9jMb6zb6ne8EDQGGhZw5ykNiq9YRUdHTD", - "gateway": "9ACTkYraCqE9jMb6zb6ne8EDQGGhZw5ykNiq9YRUdHTD" - }, - { - "id": "shipyard-telegram-20", - "description": "Nym Telegram Service Provider", - "address": "2QLnEEnTmf2NRWtcQPWBeRcg7Hej5WSPWRWwtTpEEZWF.BheS78ozc8ngvhsXNNnshdJzpoYsmEvhfn3WKUYF5dRU@C2uyokSPoxhku9GexRxEo1e8KPZ7q6e8FXmK3gtY8kkF", - "gateway": "C2uyokSPoxhku9GexRxEo1e8KPZ7q6e8FXmK3gtY8kkF" - }, - { - "id": "shipyard-telegram-21", - "description": "Nym Telegram Service Provider", - "address": "FuBbnwiANfaXZnn683jBapK5XVm5ttgZSykU3vqPSHoD.94MFGv1VcBLTkRwzBDQUkWjvqtZYVBrJg2Q8JGbizcib@CTqYPY8htdAQMXCzRW9SjZzZuqYwSt2iUh6HPaNgmTvK", - "gateway": "CTqYPY8htdAQMXCzRW9SjZzZuqYwSt2iUh6HPaNgmTvK" - }, - { - "id": "shipyard-telegram-22", - "description": "Nym Telegram Service Provider", - "address": "9EbQx5jQznSVbftFom7sqUSHAACrsfvMhrzhaFt4A3SZ.D1FQCirL4YKwfcmtMGvB5Rugt5sAzGEhdSjJ3bHVQRZ@7Zh1Sz5dXpA6s53CbtcdqhQhLqwf4cLynL7KqHKcjrG4", - "gateway": "7Zh1Sz5dXpA6s53CbtcdqhQhLqwf4cLynL7KqHKcjrG4" - }, - { - "id": "shipyard-telegram-24", - "description": "Nym Telegram Service Provider", - "address": "6Umawwvf551VyB3Ko46NgKLqJdTFJeToCM67mrTmM3G.3A4sesBac4KGuMTFjvYBwLpksMJvbMbteGJQgmm4PV4Y@AnnYnEtBjB2a5sHmeRCnBq43qxyHDf95Bqd7cwQyKNLR", - "gateway": "AnnYnEtBjB2a5sHmeRCnBq43qxyHDf95Bqd7cwQyKNLR" - }, - { - "id": "shipyard-telegram-25", - "description": "Nym Telegram Service Provider", - "address": "CDtxTeoyqq83JpV9G8cR5HRHRdMMaVspQsCwH3Qnajt3.F5EHK9HFcdGrE2hqA7bK9AUmkbihujYDhtNNqHKxW765@BDkeNx7JQm5NsQakst9s8htogZXhpTQedFAgZpvsGCqH", - "gateway": "BDkeNx7JQm5NsQakst9s8htogZXhpTQedFAgZpvsGCqH" - }, - { - "id": "shipyard-telegram-26", - "description": "Nym Telegram Service Provider", - "address": "HukZkLG2DoarQEqaoDLuqW1GFf2NSHDUMGBZiyJGRYJD.9GyU8wPsyzcvRjcyk8hiNpTJbXCmq5F3VoVhFBZYuHR3@GsGEZiDBz8SWfHGaK5SDmhfbTEM55v37WCYYcT9wTSxN", - "gateway": "GsGEZiDBz8SWfHGaK5SDmhfbTEM55v37WCYYcT9wTSxN" - }, - { - "id": "shipyard-telegram-27", - "description": "Nym Telegram Service Provider", - "address": "773y8iMVJiRk4dRbjQzkJVbrei4TwkePNE5WTEttt77d.3Mw47C9XZj3oAzk1iSqC5Y36tbBsjtaTtdgaHM3Zsdma@7fiZtNL1RACQTwGrKLBT9nbY77bfwZnX9rqcWqc53qgv", - "gateway": "7fiZtNL1RACQTwGrKLBT9nbY77bfwZnX9rqcWqc53qgv" - }, - { - "id": "shipyard-telegram-28", - "description": "Nym Telegram Service Provider", - "address": "6jQJEorCu7YiP9HdDaMeHxcNhxeNmZ1kpd836GnqLZX.HsJqEmNTszGecsKqFB37i84nBXxqf4ETgrKmKmBvMGHC@FYnDMQzT49ZGM23gVqpTxfih14V6wuedNXirekmt37zE", - "gateway": "FYnDMQzT49ZGM23gVqpTxfih14V6wuedNXirekmt37zE" - }, - { - "id": "shipyard-telegram-29", - "description": "Nym Telegram Service Provider", - "address": "BiCSyovpFMuSnTvF2TdiuZwrytXDrd9AH47ZMcCxscVC.G9YpdicA9BBNoVHDgjWjgt17wv5WYKWcbE3vPJJVpSJD@GAjhJcrd6f1edaqUkfWCff6zdHoqo756qYrc2TfPuCXJ", - "gateway":"GAjhJcrd6f1edaqUkfWCff6zdHoqo756qYrc2TfPuCXJ" - }, - { - "id": "shipyard-telegram-30", - "description": "Nym Telegram Service Provider", - "address": "AQRRAs9oc8QWXAFBs44YhCKUny7AyLsfLy91pwmGgxuf.CWUKoKA1afSKyw5BnFJJg19UDgnaVATupsFhQpyTEBHJ@EBT8jTD8o4tKng2NXrrcrzVhJiBnKpT1bJy5CMeArt2w", - "gateway": "EBT8jTD8o4tKng2NXrrcrzVhJiBnKpT1bJy5CMeArt2w" - }, - { - "id": "shipyard-telegram-31", - "description": "Nym Telegram Service Provider", - "address": "6YqjAZK3Pr1ngiBLcDkotboB5WiN6k6NPpbXvShH4pR5.9Ss6VW3Xbyi8LuxduNNwnXEv9njHCQ2PLSP1UK6tsoa5@42XCK9dMS9m5XJLzQd2dBuwimk6ndZnczhZaV5VPFkQD", - "gateway": "42XCK9dMS9m5XJLzQd2dBuwimk6ndZnczhZaV5VPFkQD" - }, - { - "id": "shipyard-telegram-32", - "description": "Nym Telegram Service Provider", - "address": "EmYWLeybmj86Vzr62vxuZ3T15jwMNHggzK7sQwid96yp.GyaF9WprSr56cxUdGf5TpcUvAjb2VbAr8CVBrmBUYAaw@GL5wESoz4oSbpBaTki9qB9213FGUQXCiRjbzDkhWwoBC", - "gateway": "GL5wESoz4oSbpBaTki9qB9213FGUQXCiRjbzDkhWwoBC" - }, - { - "id": "shipyard-telegram-33", - "description": "Nym Telegram Service Provider", - "address": "4PDb96cck5btTj6G7rsomqwHJsp4qu8uPvFCbwHfjFUx.C5dKbaoakH7egsZvAueRbwLFbmxnQaVMeSr6QTMpuBAA@58ceEFaLJh6zAo3cirzT1BDQm7D3L5acnQrxGH1D6TAY", - "gateway": "58ceEFaLJh6zAo3cirzT1BDQm7D3L5acnQrxGH1D6TAY" - }, - { - "id": "shipyard-telegram-34", - "description": "Nym Telegram Service Provider", - "address": "BeZbeMf9vcpUf368qDd85dtLwXLj4Ee5bsHMB2fUD8uX.HELVbppkwU1jmzUAUrCEbHeJfVciSeo8VGAkbJSpwxsb@ADdHkiTfkpsSt31zVToWW9j3KikH24aLAAwDKtCYE5jY", - "gateway":"ADdHkiTfkpsSt31zVToWW9j3KikH24aLAAwDKtCYE5jY" - }, - { - "id": "shipyard-telegram-35", - "description": "Nym Telegram Service Provider", - "address": "Bp4JRFyf7GB9L9J95AqMPnz9zbGmPnViA5fDXKeNraLJ.D6CTdcjJVxDmH2UQvzXuPWg9Se9xXYe76uDMypXvhzd7@6UjGEeQZK14C5K2kenycTkqt7qRjEHGLgaQx3FWySo3N", - "gateway": "6UjGEeQZK14C5K2kenycTkqt7qRjEHGLgaQx3FWySo3N" - }, - { - "id": "shipyard-telegram-36", - "description": "Nym Telegram Service Provider", - "address": "91h7io6BGhVjbtC7dbbRcScyTJcTfnMsTQZ6aWMVsrWR.Epb4hANXCp8cGEY3wSgawux991ti9Z5Y1FHTMzAKFa6E@DF4TE7V8kJkttMvnoSVGnRFFRt6WYGxxiC2w1XyPQnHe", - "gateway": "DF4TE7V8kJkttMvnoSVGnRFFRt6WYGxxiC2w1XyPQnHe" - }, - { - "id": "shipyard-telegram-37", - "description": "Nym Telegram Service Provider", - "address": "Cy2wuwKpWZ3iWzKU3ZWL1qqcVfJ5Cq85dU7UHVWwv2gc.9AhC9b2zVcLnXLGriMdxjpsWJpq6iAdCavDi63udbL89@678qVUJ21uwxZBhp3r56z7GRf6gMh3NYDHruTegPtgMf", - "gateway": "678qVUJ21uwxZBhp3r56z7GRf6gMh3NYDHruTegPtgMf" - }, - { - "id": "shipyard-telegram-38", - "description": "Nym Telegram Service Provider", - "address": "GgUeUWW1NRSuquZYeZf3WkppE92EQUHJrFjNZtYU1jow.CSEjwrRi4f4uyw7N6L2LPKw2tB8spcMbFu99wHZzFZSj@77TSuVU8d1oXKbPzjec2xh4i3Wj5WwUyy9Lr36sm8gZm", - "gateway": "77TSuVU8d1oXKbPzjec2xh4i3Wj5WwUyy9Lr36sm8gZm" - }, - { - "id": "shipyard-telegram-39", - "description": "Nym Telegram Service Provider", - "address": "kz4zWwSkYiQxqxXFPNcGUByTPQWXascD9RfYsmSxY7n.ajp3SjbBVBjrU9nXpSQXAXzbb6EHJJyhbY6cc1ajayx@BTZNB3bkkEePsT14GN8ofVtM1SJae4YLWjpBerrKYf", - "gateway": "HyS2UZtZX3kQXdazbdE99DhCjBXjbG61LC9QsmXwbxrU" - } - ] - }, - { - "id": "blockstream", - "description": "Blockstream Green", - "items": [ - { - "id": "nym-blockstream", - "description": "Nym Blockstream Green Service Provider", - "address": "GiRjFWrMxt58pEMuusm4yT3RxoMD1MMPrR9M2N4VWRJP.3CNZBPq4vg7v7qozjGjdPMXcvDmkbWPCgbGCjQVw9n6Z@2xU4CBE6QiiYt6EyBXSALwxkNvM7gqJfjHXaMkjiFmYW", - "gateway": "2xU4CBE6QiiYt6EyBXSALwxkNvM7gqJfjHXaMkjiFmYW" - } - ] - } -]`); -export const Loaded = () => ( - - - -); - -export const ServiceAlreadySelected = () => ( - - console.log('New service provider selected: ', serviceProvider)} - /> - -); diff --git a/nym-connect/mobile/src/components/ServiceProviderSelector.tsx b/nym-connect/mobile/src/components/ServiceProviderSelector.tsx deleted file mode 100644 index 741465dac4..0000000000 --- a/nym-connect/mobile/src/components/ServiceProviderSelector.tsx +++ /dev/null @@ -1,147 +0,0 @@ -import React, { useEffect, useMemo } from 'react'; -import { Box, CircularProgress, MenuItem, Stack, TextField, Tooltip, Typography } from '@mui/material'; -import { Service, ServiceProvider, Services } from '../types/directory'; -import { useTauriEvents } from '../utils'; - -type ServiceWithRandomSp = { - id: string; - description: string; - sp: ServiceProvider; -}; - -const defaultServiceValue = { id: '', description: '', items: [] }; - -export const ServiceProviderSelector: FCWithChildren<{ - onChange?: (serviceProvider?: ServiceProvider) => void; - services?: Services; - currentSp?: ServiceProvider; -}> = ({ services, currentSp, onChange }) => { - const [service, setService] = React.useState(defaultServiceValue); - const [serviceProvider, setServiceProvider] = React.useState(currentSp); - const [resetTrigger, setResetTrigger] = React.useState(new Date().toISOString()); - - const handleSelectSp = (newServiceProvider?: ServiceProvider) => { - if (newServiceProvider && newServiceProvider !== currentSp) { - setServiceProvider(newServiceProvider); - onChange?.(newServiceProvider); - } - }; - - // when the user clears local storage, reset the selector - useTauriEvents('help://clear-storage', () => { - setService(defaultServiceValue); - setServiceProvider(undefined); - onChange?.(undefined); - setResetTrigger(new Date().toISOString()); - }); - - useEffect(() => { - if (!serviceProvider && currentSp) { - setServiceProvider(currentSp); - } - }, [currentSp]); - - useEffect(() => { - if (services && serviceProvider) { - // retrieve the service corresponding to this service provider - - const match = services.find((s) => - s.items.some( - ({ id, address, gateway }) => - id === serviceProvider.id && address === serviceProvider.address && gateway === serviceProvider.gateway, - ), - ); - - if (match) { - setService(match); - } - } - }, [serviceProvider, services]); - - if (!services) { - return ( - - theme.palette.common.white}> - - Loading services... - - - ); - } - - const servicesWithRandomSp: ServiceWithRandomSp[] = useMemo( - () => - services.map(({ id, items, description }) => ({ - id, - description, - sp: items[Math.floor(Math.random() * items.length)], - })), - [services, resetTrigger], - ); - - if (!service) return null; - - return ( - - - {servicesWithRandomSp.map(({ id, description, sp }) => ( - handleSelectSp(sp)}> - - - {sp.id} - - - {sp.description} - - - Gateway {sp.gateway.slice(0, 10)}... - - - Provider {sp.address.slice(0, 10)}... - - - } - arrow - placement="top" - > - {description} - - - ))} - - - ); -}; diff --git a/nym-connect/mobile/src/components/ServiceSelector.stories.tsx b/nym-connect/mobile/src/components/ServiceSelector.stories.tsx deleted file mode 100644 index 56be7c19ee..0000000000 --- a/nym-connect/mobile/src/components/ServiceSelector.stories.tsx +++ /dev/null @@ -1,342 +0,0 @@ -import * as React from 'react'; -import { ComponentMeta } from '@storybook/react'; -import { Box } from '@mui/material'; -import { ServiceSelector } from './ServiceSelector'; -import { Services } from '../types/directory'; - -export default { - title: 'Components/Service Selector', - component: ServiceSelector, -} as ComponentMeta; - -const width = 240; - -export const Loading = () => ( - - - -); - -const services: Services = JSON.parse(`[ - { - "id": "keybase", - "description": "Keybase", - "items": [ - { - "id": "nym-keybase", - "description": "Nym Keybase Service Provider", - "address": "Entztfv6Uaz2hpYHQJ6JKoaCTpDL5dja18SuQWVJAmmx.Cvhn9rBJw5Ay9wgHcbgCnVg89MPSV5s2muPV2YF1BXYu@Fo4f4SQLdoyoGkFae5TpVhRVoXCF8UiypLVGtGjujVPf", - "gateway": "Fo4f4SQLdoyoGkFae5TpVhRVoXCF8UiypLVGtGjujVPf" - }, - { - "id": "shipyard-keybase-1", - "description": "Nym Keybase Service Provider", - "address": "D55ksecHzY6vAeqk8MCTzCfj2pqwJeKCKZCUUGnwGnn3.FS42vXS5a6GNTb1qk3aVk5mjSiJCAuawbBVyQZZVfhvt@DfNMqQRy6pPkU8Z5rBsxRwzDUzAMXHPFwMhjF16ScZqn", - "gateway": "DfNMqQRy6pPkU8Z5rBsxRwzDUzAMXHPFwMhjF16ScZqn" - }, - { - "id": "shipyard-keybase-2", - "description": "Nym Keybase Service Provider", - "address": "DFdDtW7LNBATxQ4ef3jNbqs3cRE8b9wDZTCctHCQRULa.4AbKiTNVUwYFWHhy98o5pT9dELiUrkXoJQ9wHqPgf6GV@GJqd3ZxpXWSNxTfx7B1pPtswpetH4LnJdFeLeuY5KUuN", - "gateway": "GJqd3ZxpXWSNxTfx7B1pPtswpetH4LnJdFeLeuY5KUuN" - }, - { - "id": "shipyard-keybase-3", - "description": "Nym Keybase Service Provider", - "address": "6Y1HE1jJ92P9yoHer11TR4A2NdZePrLGaBNFf65MnYGe.FwXoh217odQDWNmViqzNX28fauYrjB3PYLrVvpqnQrX4@5vC8spDvw5VDQ8Zvd9fVvBhbUDv9jABR4cXzd4Kh5vz", - "gateway": "5vC8spDvw5VDQ8Zvd9fVvBhbUDv9jABR4cXzd4Kh5vz" - }, - { - "id": "shipyard-keybase-4", - "description": "Nym Keybase Service Provider", - "address": "3zzhLtWvaJgn755MkRckG5aRnoTZich8ASn395iSsTgj.J1R5VuxXbh2eNHiaRbrwbKGXrrEQcHKLdzf8eg9HTB6q@3B7PsbXFuqq6rerYFLw5HPbQb4UmBqAhfWURRovMmWoj", - "gateway": "3B7PsbXFuqq6rerYFLw5HPbQb4UmBqAhfWURRovMmWoj" - }, - { - "id": "shipyard-keybase-5", - "description": "Nym Keybase Service Provider", - "address": "CHuXdZJYQ8xH7ekgN9gAuVtQ7ZikjjHEZY5BSN7yc5mN.29dFvqicKQQQvoX1vup44mspmc249RH5xgLibWMwTYGT@CfWcDJq8QBz6cVAPCYSaLbaJEhVTmHEmyYgQ6C5GdDW9", - "gateway": "CfWcDJq8QBz6cVAPCYSaLbaJEhVTmHEmyYgQ6C5GdDW9" - } - ] - }, - { - "id": "electrum", - "description": "Electrum Wallet", - "items": [ - { - "id": "nym-electrum", - "description": "Nym Electrum Service Provider", - "address": "DpB3cHAchJiNBQi5FrZx2csXb1mrHkpYh9Wzf8Rjsuko.ANNWrvHqMYuertHGHUrZdBntQhpzfbWekB39qez9U2Vx@2BuMSfMW3zpeAjKXyKLhmY4QW1DXurrtSPEJ6CjX3SEh", - "gateway": "2BuMSfMW3zpeAjKXyKLhmY4QW1DXurrtSPEJ6CjX3SEh" - }, - { - "id": "shipyard-electrum-1", - "description": "Nym Electrum Service Provider", - "address": "8Tb73cFQpXCLpgxEA2VSDru2hHrcZ3KQcyMsGbxcTjBp.4x5tu66k8YkHk4tYac1qwEFbNq5WsKiX5kR51q5KKH88@4WgKhJdmUffz4e1o1ftVAGS3HnG56LiNAxA9dmaekrVd", - "gateway": "4WgKhJdmUffz4e1o1ftVAGS3HnG56LiNAxA9dmaekrVd" - }, - { - "id": "shipyard-electrum-2", - "description": "Nym Electrum Service Provider", - "address": "GR6z31MwCsvxHrnvvVN1Cpasd8aQ1giwQqPTZM9dN7VH.5rEiqakSPDrBtKmvpU8Shnhz6gRM85JLoB7AX4h7PJYr@5Ao1J38frnU9Rx5YVeF5BWExcnDTcW8etNe9W2sRASXD", - "gateway": "5Ao1J38frnU9Rx5YVeF5BWExcnDTcW8etNe9W2sRASXD" - } - ] - }, - { - "id": "telegram", - "description": "Telegram", - "items": [ - { - "id": "shipyard-telegram-2", - "description": "Nym Telegram Service Provider", - "address": "C4w6ewbQtoaZEeoaaNw1xVASChqo4WVjNfuYEUFjZxpc.8F1D7rQXf2jGoj1Ken7PiGDM8HS2Ug79wSoc9nZ1iqh1@62F81C9GrHDRja9WCqozemRFSzFPMecY85MbGwn6efve", - "gateway": "62F81C9GrHDRja9WCqozemRFSzFPMecY85MbGwn6efve" - }, - { - "id": "shipyard-telegram-3", - "description": "Nym Telegram Service Provider", - "address": "DStL3BEUZuQZfbij1KAY3BvJh8rC5jpr9mc6AQ6aTLUu.Ax9foYaKfFgX6g8y393GoNpKkKrnDGFGRZwxDv9R7X6M@FQon7UwF5knbUr2jf6jHhmNLbJnMreck1eUcVH59kxYE", - "gateway": "FQon7UwF5knbUr2jf6jHhmNLbJnMreck1eUcVH59kxYE" - }, - { - "id": "shipyard-telegram-4", - "description": "Nym Telegram Service Provider", - "address": "8gRdGTzsDxYzpasRQhsRg59MCgNNhnfag2oFfwwZPXnB.DtDrGz7ScVm4o7sN4K3CYUJveYgz7fcXELBVLNDfMS9Y@3ojQD6V7skM1bSXJX7fVQvscjmcgptzdixQEaAha2ixh", - "gateway": "3ojQD6V7skM1bSXJX7fVQvscjmcgptzdixQEaAha2ixh" - }, - { - "id": "shipyard-telegram-5", - "description": "Nym Telegram Service Provider", - "address": "AR3oEM6Uvmfs6fyddwSehoBUKCFxz7MdFi4z7aahuHuY.3ZKapg9A3Py1PXhyLbCJr8ZbJsEV6NZdN1WJaGGut5tj@EEyq16v63aotPBCepxUpCgAojrNasZ6Hk1PjpRyBAdEp", - "gateway": "EEyq16v63aotPBCepxUpCgAojrNasZ6Hk1PjpRyBAdEp" - }, - { - "id": "shipyard-telegram-6", - "description": "Nym Telegram Service Provider", - "address": "7n1BYhsXSwcr8Qim8AqZTAodqFia4QG6T7CRc1ihQHpv.7o4trpGqu2LHMUiXc3dddgfGET1CFFcAK9gKYoHoSn5e@BTZNB3bkkEePsT14GN8ofVtM1SJae4YLWjpBerrKYfr", - "gateway": "BTZNB3bkkEePsT14GN8ofVtM1SJae4YLWjpBerrKYfr" - }, - { - "id": "shipyard-telegram-7", - "description": "Nym Telegram Service Provider", - "address": "Gv4TWhUKrvJfqh1jBRPGEQrikNZvZse2kS3ZgN9Z2nAZ.7KGPaaqUEum2C59jLvw7f8Ug8a48YuZdjjZu3t4JES4U@C7J8SwZQqjWqhBryyjJxLt7FacVuPTwAmR2otGy53ayi", - "gateway": "C7J8SwZQqjWqhBryyjJxLt7FacVuPTwAmR2otGy53ayi" - }, - { - "id": "shipyard-telegram-8", - "description": "Nym Telegram Service Provider", - "address": "8Mqgp12cpF6FSXMeqzxgFgQXvTSapyNqGAi5wy7ub4ge.7z7PDsiJGiGxGz4p77v5L5fZhXBJ5qNZ8CgJwYNr6H6J", - "gateway": "3zd3wrCK8Dz5TXrcvk5dG5s9EEdf4Ck1v9VgBPMMFVkR" - }, - { - "id": "shipyard-telegram-9", - "description": "Nym Telegram Service Provider", - "address": "F3N5eiPDZcGFC985Go4Mpv8p9uxFD1L3jRUdrLCbrZLm.EyTxWwwTwYpPrJBmc97GLd1LpUAphjptS5y1ed182bGk@GAjhJcrd6f1edaqUkfWCff6zdHoqo756qYrc2TfPuCXJ", - "gateway": "GAjhJcrd6f1edaqUkfWCff6zdHoqo756qYrc2TfPuCXJ" - }, - { - "id": "shipyard-telegram-10", - "description": "Nym Telegram Service Provider", - "address": "G7y7e1nVBr8fmQSzdeAxXnCmmmJb5k8N3E8LBV31KE5g.GRRUCj6t6cCUUjakmTWzidMLiYA7EdCedKnup8osaBC6@AJad2R9virYEYXEsTcicN5y5tyPoixrhhAGsxoESZVnc", - "gateway": "AJad2R9virYEYXEsTcicN5y5tyPoixrhhAGsxoESZVnc" - }, - { - "id": "shipyard-telegram-11", - "description": "Nym Telegram Service Provider", - "address": "2kq9Z7RyDZtb8kxXjyP3ZT8VMWHg6JXFDChGuuNBk7Hw.F5XYbBaGSoF8qAo8faPcaNRPHEq3Y25BDcwESeabUS9S@HaLyPQrhBTq75dnGeBUdYWeFVA2BBn39MgkhEt3VTMMM", - "gateway": "HaLyPQrhBTq75dnGeBUdYWeFVA2BBn39MgkhEt3VTMMM" - }, - { - "id": "shipyard-telegram-12", - "description": "Nym Telegram Service Provider", - "address": "GegdtpNzYj4QCgpih9Kxv7ZVZxmVdxYHsDkiPsbT71XG.E8xtE8mrapjzFtyuziZSrsScAKhwZMH5wNpKWtKfzJ5Y@9Byd9VAtyYMnbVAcqdoQxJnq76XEg2dbxbiF5Aa5Jj9J", - "gateway": "9Byd9VAtyYMnbVAcqdoQxJnq76XEg2dbxbiF5Aa5Jj9J" - }, - { - "id": "shipyard-telegram-13", - "description": "Nym Telegram Service Provider", - "address": "4SsrDQeEtG3mpeD9nN5CDdGaCsxFvNeYMhoviDzNNB9f.GyqG6iK5rBvhe3HXLR11m6ULpf13ATgYvkkidLmteDLs@5EpkkrMFYAM3XcaztXnZwBWquURHSKsyc9JxUCengDFS", - "gateway": "5EpkkrMFYAM3XcaztXnZwBWquURHSKsyc9JxUCengDFS" - }, - { - "id": "shipyard-telegram-14", - "description": "Nym Telegram Service Provider", - "address": "9JoHRu2RrSD1fjbj9NSTASgjv9Szep7Nhd9L2PywxbBi.AZhAUDNX6iH8BqXyR5c7TJuzpwMEvDXrabNLGuRukvVf@9xJM74FwwHhEKKJHihD21QSZnHM2QBRMoFx9Wst6qNBS", - "gateway": "9xJM74FwwHhEKKJHihD21QSZnHM2QBRMoFx9Wst6qNBS" - }, - { - "id": "shipyard-telegram-15", - "description": "Nym Telegram Service Provider", - "address": "3K174ijjXqCkhMDT9xLcqjS4MXk2QsqZt4PdgNcuUrnn.BNnHnQmWoj6Uo6kkS1QkPqsdHaXrcwyR9F6MnnzDkZJL@C7J8SwZQqjWqhBryyjJxLt7FacVuPTwAmR2otGy53ayi", - "gateway": "C7J8SwZQqjWqhBryyjJxLt7FacVuPTwAmR2otGy53ayi" - }, - { - "id": "shipyard-telegram-16", - "description": "Nym Telegram Service Provider", - "address": "BqX5Q3MEcbTnM39hUswQchLW68SrqbhL8K5ucrLmtP39.AWrVsFoVC9s6KjdpcasATmZPA3GtMsUxcfHpAkuNdtFG@Emswx6KXyjRfq1c2k4d4uD2e6nBSbH1biorCZUei8UNS", - "gateway": "Emswx6KXyjRfq1c2k4d4uD2e6nBSbH1biorCZUei8UNS" - }, - { - "id": "shipyard-telegram-17", - "description": "Nym Telegram Service Provider", - "address": "2tQxccgcqdkuUvLqgiEkEN4rNRZ5QknygnKAFcS4gfoe.EVrY5q5sqDqBUbS3wHsRRZhk2MAw1S17hNoH1Bicyv7n@DAGQxdxwAkwjaLjTw1B9vndia4YyFD15qRgcTQxrmkom", - "gateway": "DAGQxdxwAkwjaLjTw1B9vndia4YyFD15qRgcTQxrmkom" - }, - { - "id": "shipyard-telegram-18", - "description": "Nym Telegram Service Provider", - "address": "8YG1rcEauJA814Nd7hSxjNe2UrRwrGsrXTm1Cmd3gRrU.FxYaYqpNN8PciNsySs3zYPrTB1J8AYUu9DBsM2vVDDfF@7EfEESLo71GUvx3UEW79LgTegHUBPUocUzGyJVv6LHog", - "gateway": "7EfEESLo71GUvx3UEW79LgTegHUBPUocUzGyJVv6LHog" - }, - { - "id": "shipyard-telegram-19", - "description": "Nym Telegram Service Provider", - "address": "HPiXADVFLwLQPNpPtyYefzvYntC6tp9UJ5fJZGfkqvDt.2EUUxmeT3AiaUzAcE5SyXRAk8a2JXBkRz4B8McSdkrST@9ACTkYraCqE9jMb6zb6ne8EDQGGhZw5ykNiq9YRUdHTD", - "gateway": "9ACTkYraCqE9jMb6zb6ne8EDQGGhZw5ykNiq9YRUdHTD" - }, - { - "id": "shipyard-telegram-20", - "description": "Nym Telegram Service Provider", - "address": "2QLnEEnTmf2NRWtcQPWBeRcg7Hej5WSPWRWwtTpEEZWF.BheS78ozc8ngvhsXNNnshdJzpoYsmEvhfn3WKUYF5dRU@C2uyokSPoxhku9GexRxEo1e8KPZ7q6e8FXmK3gtY8kkF", - "gateway": "C2uyokSPoxhku9GexRxEo1e8KPZ7q6e8FXmK3gtY8kkF" - }, - { - "id": "shipyard-telegram-21", - "description": "Nym Telegram Service Provider", - "address": "FuBbnwiANfaXZnn683jBapK5XVm5ttgZSykU3vqPSHoD.94MFGv1VcBLTkRwzBDQUkWjvqtZYVBrJg2Q8JGbizcib@CTqYPY8htdAQMXCzRW9SjZzZuqYwSt2iUh6HPaNgmTvK", - "gateway": "CTqYPY8htdAQMXCzRW9SjZzZuqYwSt2iUh6HPaNgmTvK" - }, - { - "id": "shipyard-telegram-22", - "description": "Nym Telegram Service Provider", - "address": "9EbQx5jQznSVbftFom7sqUSHAACrsfvMhrzhaFt4A3SZ.D1FQCirL4YKwfcmtMGvB5Rugt5sAzGEhdSjJ3bHVQRZ@7Zh1Sz5dXpA6s53CbtcdqhQhLqwf4cLynL7KqHKcjrG4", - "gateway": "7Zh1Sz5dXpA6s53CbtcdqhQhLqwf4cLynL7KqHKcjrG4" - }, - { - "id": "shipyard-telegram-24", - "description": "Nym Telegram Service Provider", - "address": "6Umawwvf551VyB3Ko46NgKLqJdTFJeToCM67mrTmM3G.3A4sesBac4KGuMTFjvYBwLpksMJvbMbteGJQgmm4PV4Y@AnnYnEtBjB2a5sHmeRCnBq43qxyHDf95Bqd7cwQyKNLR", - "gateway": "AnnYnEtBjB2a5sHmeRCnBq43qxyHDf95Bqd7cwQyKNLR" - }, - { - "id": "shipyard-telegram-25", - "description": "Nym Telegram Service Provider", - "address": "CDtxTeoyqq83JpV9G8cR5HRHRdMMaVspQsCwH3Qnajt3.F5EHK9HFcdGrE2hqA7bK9AUmkbihujYDhtNNqHKxW765@BDkeNx7JQm5NsQakst9s8htogZXhpTQedFAgZpvsGCqH", - "gateway": "BDkeNx7JQm5NsQakst9s8htogZXhpTQedFAgZpvsGCqH" - }, - { - "id": "shipyard-telegram-26", - "description": "Nym Telegram Service Provider", - "address": "HukZkLG2DoarQEqaoDLuqW1GFf2NSHDUMGBZiyJGRYJD.9GyU8wPsyzcvRjcyk8hiNpTJbXCmq5F3VoVhFBZYuHR3@GsGEZiDBz8SWfHGaK5SDmhfbTEM55v37WCYYcT9wTSxN", - "gateway": "GsGEZiDBz8SWfHGaK5SDmhfbTEM55v37WCYYcT9wTSxN" - }, - { - "id": "shipyard-telegram-27", - "description": "Nym Telegram Service Provider", - "address": "773y8iMVJiRk4dRbjQzkJVbrei4TwkePNE5WTEttt77d.3Mw47C9XZj3oAzk1iSqC5Y36tbBsjtaTtdgaHM3Zsdma@7fiZtNL1RACQTwGrKLBT9nbY77bfwZnX9rqcWqc53qgv", - "gateway": "7fiZtNL1RACQTwGrKLBT9nbY77bfwZnX9rqcWqc53qgv" - }, - { - "id": "shipyard-telegram-28", - "description": "Nym Telegram Service Provider", - "address": "6jQJEorCu7YiP9HdDaMeHxcNhxeNmZ1kpd836GnqLZX.HsJqEmNTszGecsKqFB37i84nBXxqf4ETgrKmKmBvMGHC@FYnDMQzT49ZGM23gVqpTxfih14V6wuedNXirekmt37zE", - "gateway": "FYnDMQzT49ZGM23gVqpTxfih14V6wuedNXirekmt37zE" - }, - { - "id": "shipyard-telegram-29", - "description": "Nym Telegram Service Provider", - "address": "BiCSyovpFMuSnTvF2TdiuZwrytXDrd9AH47ZMcCxscVC.G9YpdicA9BBNoVHDgjWjgt17wv5WYKWcbE3vPJJVpSJD@GAjhJcrd6f1edaqUkfWCff6zdHoqo756qYrc2TfPuCXJ", - "gateway":"GAjhJcrd6f1edaqUkfWCff6zdHoqo756qYrc2TfPuCXJ" - }, - { - "id": "shipyard-telegram-30", - "description": "Nym Telegram Service Provider", - "address": "AQRRAs9oc8QWXAFBs44YhCKUny7AyLsfLy91pwmGgxuf.CWUKoKA1afSKyw5BnFJJg19UDgnaVATupsFhQpyTEBHJ@EBT8jTD8o4tKng2NXrrcrzVhJiBnKpT1bJy5CMeArt2w", - "gateway": "EBT8jTD8o4tKng2NXrrcrzVhJiBnKpT1bJy5CMeArt2w" - }, - { - "id": "shipyard-telegram-31", - "description": "Nym Telegram Service Provider", - "address": "6YqjAZK3Pr1ngiBLcDkotboB5WiN6k6NPpbXvShH4pR5.9Ss6VW3Xbyi8LuxduNNwnXEv9njHCQ2PLSP1UK6tsoa5@42XCK9dMS9m5XJLzQd2dBuwimk6ndZnczhZaV5VPFkQD", - "gateway": "42XCK9dMS9m5XJLzQd2dBuwimk6ndZnczhZaV5VPFkQD" - }, - { - "id": "shipyard-telegram-32", - "description": "Nym Telegram Service Provider", - "address": "EmYWLeybmj86Vzr62vxuZ3T15jwMNHggzK7sQwid96yp.GyaF9WprSr56cxUdGf5TpcUvAjb2VbAr8CVBrmBUYAaw@GL5wESoz4oSbpBaTki9qB9213FGUQXCiRjbzDkhWwoBC", - "gateway": "GL5wESoz4oSbpBaTki9qB9213FGUQXCiRjbzDkhWwoBC" - }, - { - "id": "shipyard-telegram-33", - "description": "Nym Telegram Service Provider", - "address": "4PDb96cck5btTj6G7rsomqwHJsp4qu8uPvFCbwHfjFUx.C5dKbaoakH7egsZvAueRbwLFbmxnQaVMeSr6QTMpuBAA@58ceEFaLJh6zAo3cirzT1BDQm7D3L5acnQrxGH1D6TAY", - "gateway": "58ceEFaLJh6zAo3cirzT1BDQm7D3L5acnQrxGH1D6TAY" - }, - { - "id": "shipyard-telegram-34", - "description": "Nym Telegram Service Provider", - "address": "BeZbeMf9vcpUf368qDd85dtLwXLj4Ee5bsHMB2fUD8uX.HELVbppkwU1jmzUAUrCEbHeJfVciSeo8VGAkbJSpwxsb@ADdHkiTfkpsSt31zVToWW9j3KikH24aLAAwDKtCYE5jY", - "gateway":"ADdHkiTfkpsSt31zVToWW9j3KikH24aLAAwDKtCYE5jY" - }, - { - "id": "shipyard-telegram-35", - "description": "Nym Telegram Service Provider", - "address": "Bp4JRFyf7GB9L9J95AqMPnz9zbGmPnViA5fDXKeNraLJ.D6CTdcjJVxDmH2UQvzXuPWg9Se9xXYe76uDMypXvhzd7@6UjGEeQZK14C5K2kenycTkqt7qRjEHGLgaQx3FWySo3N", - "gateway": "6UjGEeQZK14C5K2kenycTkqt7qRjEHGLgaQx3FWySo3N" - }, - { - "id": "shipyard-telegram-36", - "description": "Nym Telegram Service Provider", - "address": "91h7io6BGhVjbtC7dbbRcScyTJcTfnMsTQZ6aWMVsrWR.Epb4hANXCp8cGEY3wSgawux991ti9Z5Y1FHTMzAKFa6E@DF4TE7V8kJkttMvnoSVGnRFFRt6WYGxxiC2w1XyPQnHe", - "gateway": "DF4TE7V8kJkttMvnoSVGnRFFRt6WYGxxiC2w1XyPQnHe" - }, - { - "id": "shipyard-telegram-37", - "description": "Nym Telegram Service Provider", - "address": "Cy2wuwKpWZ3iWzKU3ZWL1qqcVfJ5Cq85dU7UHVWwv2gc.9AhC9b2zVcLnXLGriMdxjpsWJpq6iAdCavDi63udbL89@678qVUJ21uwxZBhp3r56z7GRf6gMh3NYDHruTegPtgMf", - "gateway": "678qVUJ21uwxZBhp3r56z7GRf6gMh3NYDHruTegPtgMf" - }, - { - "id": "shipyard-telegram-38", - "description": "Nym Telegram Service Provider", - "address": "GgUeUWW1NRSuquZYeZf3WkppE92EQUHJrFjNZtYU1jow.CSEjwrRi4f4uyw7N6L2LPKw2tB8spcMbFu99wHZzFZSj@77TSuVU8d1oXKbPzjec2xh4i3Wj5WwUyy9Lr36sm8gZm", - "gateway": "77TSuVU8d1oXKbPzjec2xh4i3Wj5WwUyy9Lr36sm8gZm" - }, - { - "id": "shipyard-telegram-39", - "description": "Nym Telegram Service Provider", - "address": "kz4zWwSkYiQxqxXFPNcGUByTPQWXascD9RfYsmSxY7n.ajp3SjbBVBjrU9nXpSQXAXzbb6EHJJyhbY6cc1ajayx@BTZNB3bkkEePsT14GN8ofVtM1SJae4YLWjpBerrKYf", - "gateway": "HyS2UZtZX3kQXdazbdE99DhCjBXjbG61LC9QsmXwbxrU" - } - ] - }, - { - "id": "blockstream", - "description": "Blockstream Green", - "items": [ - { - "id": "nym-blockstream", - "description": "Nym Blockstream Green Service Provider", - "address": "GiRjFWrMxt58pEMuusm4yT3RxoMD1MMPrR9M2N4VWRJP.3CNZBPq4vg7v7qozjGjdPMXcvDmkbWPCgbGCjQVw9n6Z@2xU4CBE6QiiYt6EyBXSALwxkNvM7gqJfjHXaMkjiFmYW", - "gateway": "2xU4CBE6QiiYt6EyBXSALwxkNvM7gqJfjHXaMkjiFmYW" - } - ] - } -]`); -export const Loaded = () => ( - - - -); - -export const ServiceAlreadySelected = () => ( - - console.log('New service provider selected: ', serviceProvider)} - /> - -); diff --git a/nym-connect/mobile/src/components/ServiceSelector.tsx b/nym-connect/mobile/src/components/ServiceSelector.tsx deleted file mode 100644 index 5eb8361d3c..0000000000 --- a/nym-connect/mobile/src/components/ServiceSelector.tsx +++ /dev/null @@ -1,179 +0,0 @@ -import React, { useEffect } from 'react'; -import { - Box, - CircularProgress, - Divider, - FormControl, - InputLabel, - MenuItem, - Select, - Stack, - Typography, -} from '@mui/material'; -import { Service, ServiceProvider, Services } from '../types/directory'; -import { useTauriEvents } from '../utils'; -import { ServiceProviderPopup } from './ServiceProviderPopup'; - -export const ServiceSelector: FCWithChildren<{ - onChange?: (serviceProvider?: ServiceProvider) => void; - services?: Services; - currentSp?: ServiceProvider; -}> = ({ services, currentSp, onChange }) => { - const [service, setService] = React.useState(); - const [serviceProvider, setServiceProvider] = React.useState(currentSp); - const [isPopupVisible, setPopupVisible] = React.useState(false); - - const getService = () => { - if (!services || !currentSp) { - return undefined; - } - return services.find((s) => - s.items.some( - ({ id, address, gateway }) => - id === currentSp.id && address === currentSp.address && gateway === currentSp.gateway, - ), - ); - }; - - useEffect(() => { - if (!service && currentSp) { - setServiceProvider(currentSp); - setService(getService()); - } - }, [currentSp, services]); - - /** - * Gets a random service provider from the currently selected service. - * - * If there is no service selected, or it does not have items, `undefined` is returned. - */ - const getRandomServiceProviderForService = (serviceToUse?: Service): ServiceProvider | undefined => { - if (!serviceToUse?.items.length) { - return undefined; - } - return serviceToUse.items[Math.floor(Math.random() * serviceToUse.items.length)]; - }; - - const handleServiceSelected = React.useCallback( - (newService?: Service) => { - console.log(newService?.id, service?.id); - // if the user has chosen a new service, then pick a random service provider - if (newService?.id !== service?.id) { - const newServiceProvider = getRandomServiceProviderForService(newService); - setServiceProvider(newServiceProvider); - onChange?.(newServiceProvider); - setService(newService); - } - }, - [service], - ); - - // clears the display and fire on change (to trigger upstream storage clearance) - const clearServiceProviderAndFireOnChange = () => { - setService(undefined); - setServiceProvider(undefined); - onChange?.(undefined); - }; - - // when the user clears local storage, reset the selector - useTauriEvents('help://clear-storage', () => { - clearServiceProviderAndFireOnChange(); - }); - - const handleAdvancedSpChange = (newServiceProvider?: ServiceProvider, newService?: Service) => { - setPopupVisible(false); - setService(newService); - setServiceProvider(newServiceProvider); - onChange?.(newServiceProvider); - }; - - const handleNewService = (newServiceId?: string) => { - const newService = (services || []).find((s) => s.id === newServiceId); - setService(newService); - }; - - if (!services) { - return ( - - theme.palette.common.white}> - - Loading services... - - - ); - } - - return ( - - - - Select a service - - - - setPopupVisible(false)} - onServiceProviderChanged={handleAdvancedSpChange} - /> - - ); -}; diff --git a/nym-connect/mobile/src/components/TooltipInfo.tsx b/nym-connect/mobile/src/components/TooltipInfo.tsx deleted file mode 100644 index 64b718d174..0000000000 --- a/nym-connect/mobile/src/components/TooltipInfo.tsx +++ /dev/null @@ -1,29 +0,0 @@ -import React from 'react'; -import { Divider, Stack, Typography } from '@mui/material'; -import { ServiceProvider } from 'src/types/directory'; - -export const ServiceProviderInfo = ({ serviceProvider }: { serviceProvider: ServiceProvider }) => ( - - - Connection info - - - - Gateway {serviceProvider.gateway} - - - - Service provider {serviceProvider.address.slice(0, 35)}... - - -); - -export const GatwayWarningInfo = () => ( - - - Connection issue - - - Try disconnecting and connecting again - -); diff --git a/nym-connect/mobile/src/config.ts b/nym-connect/mobile/src/config.ts deleted file mode 100644 index f405ceac15..0000000000 --- a/nym-connect/mobile/src/config.ts +++ /dev/null @@ -1,3 +0,0 @@ -export const config = { - DOCS_BASE_URL: 'https://nymtech.net/docs', -}; diff --git a/nym-connect/mobile/src/context/main.tsx b/nym-connect/mobile/src/context/main.tsx deleted file mode 100644 index 10f6525685..0000000000 --- a/nym-connect/mobile/src/context/main.tsx +++ /dev/null @@ -1,217 +0,0 @@ -import React, { createContext, useCallback, useContext, useEffect, useMemo, useState } from 'react'; -import { DateTime } from 'luxon'; -import { invoke } from '@tauri-apps/api'; -import { Error } from 'src/types/error'; -import { getVersion } from '@tauri-apps/api/app'; -import { useEvents } from 'src/hooks/events'; -import { UserDefinedGateway, UserDefinedSPAddress } from 'src/types/service-provider'; -import { getItemFromStorage, setItemInStorage } from 'src/utils'; -import { ConnectionStatusKind, GatewayPerformance } from '../types'; -import { ConnectionStatsItem } from '../components/ConnectionStats'; -import { ServiceProvider } from '../types/directory'; - -const FORAGE_GATEWAY_KEY = 'nym-connect-user-gateway'; -const FORAGE_SP_KEY = 'nym-connect-user-sp'; - -type ModeType = 'light' | 'dark'; - -export type TClientContext = { - mode: ModeType; - appVersion?: string; - connectionStatus: ConnectionStatusKind; - connectionStats?: ConnectionStatsItem[]; - connectedSince?: DateTime; - error?: Error; - gatewayPerformance: GatewayPerformance; - selectedProvider?: ServiceProvider; - showInfoModal: boolean; - userDefinedGateway?: UserDefinedGateway; - userDefinedSPAddress: UserDefinedSPAddress; - serviceProviders?: ServiceProvider[]; - setMode: (mode: ModeType) => void; - clearError: () => void; - setConnectionStatus: (connectionStatus: ConnectionStatusKind) => void; - setConnectionStats: (connectionStats: ConnectionStatsItem[] | undefined) => void; - setConnectedSince: (connectedSince: DateTime | undefined) => void; - setShowInfoModal: (show: boolean) => void; - setSerivceProvider: () => void; - startConnecting: () => Promise; - startDisconnecting: () => Promise; - setUserDefinedGateway: React.Dispatch>; - setUserDefinedSPAddress: React.Dispatch>; -}; - -export const ClientContext = createContext({} as TClientContext); - -export const ClientContextProvider: FCWithChildren = ({ children }) => { - const [mode, setMode] = useState('dark'); - const [connectionStatus, setConnectionStatus] = useState(ConnectionStatusKind.connected); - const [connectionStats, setConnectionStats] = useState(); - const [connectedSince, setConnectedSince] = useState(); - const [selectedProvider, setSelectedProvider] = React.useState(); - const [serviceProviders, setServiceProviders] = React.useState(); - const [error, setError] = useState(); - const [appVersion, setAppVersion] = useState(); - const [gatewayPerformance, setGatewayPerformance] = useState('Good'); - const [showInfoModal, setShowInfoModal] = useState(false); - const [userDefinedGateway, setUserDefinedGateway] = useState({ - isActive: false, - gateway: undefined, - }); - const [userDefinedSPAddress, setUserDefinedSPAddress] = useState({ - isActive: false, - address: undefined, - }); - - const getAppVersion = async () => { - const version = await getVersion(); - return version; - }; - - useEffect(() => { - setItemInStorage({ key: FORAGE_GATEWAY_KEY, value: userDefinedGateway }); - }, [userDefinedGateway]); - - useEffect(() => { - setItemInStorage({ key: FORAGE_SP_KEY, value: userDefinedSPAddress }); - }, [userDefinedSPAddress]); - - const initialiseApp = async () => { - const services = await invoke('get_services'); - const AppVersion = await getAppVersion(); - const storedUserDefinedGateway = await getItemFromStorage({ key: FORAGE_GATEWAY_KEY }); - const storedUserDefinedSP = await getItemFromStorage({ key: FORAGE_SP_KEY }); - - setAppVersion(AppVersion); - setServiceProviders(services as ServiceProvider[]); - - if (storedUserDefinedGateway) setUserDefinedGateway(storedUserDefinedGateway); - if (storedUserDefinedSP) setUserDefinedSPAddress(storedUserDefinedSP); - }; - - useEvents({ - onError: (e) => setError(e), - onGatewayPerformanceChange: (performance) => setGatewayPerformance(performance), - onStatusChange: (status) => setConnectionStatus(status), - }); - - useEffect(() => { - initialiseApp(); - }, []); - - useEffect(() => { - // when mounting, load the connection state (needed for the Growth window, that checks the connection state) - (async () => { - const currentStatus: ConnectionStatusKind = await invoke('get_connection_status'); - setConnectionStatus(currentStatus); - })(); - }, []); - - const startConnecting = useCallback(async () => { - try { - await invoke('start_connecting'); - } catch (e) { - setError({ title: 'Could not connect', message: e as string }); - console.log(e); - } - }, []); - - const afterDisconnection = useCallback(async () => { - setGatewayPerformance('Good'); - setConnectedSince(undefined); - }, []); - - const startDisconnecting = useCallback(async () => { - try { - await invoke('start_disconnecting'); - afterDisconnection(); - } catch (e) { - console.log(e); - } - }, []); - - const shouldUseUserGateway = !!userDefinedGateway.gateway && userDefinedGateway.isActive; - const shouldUseUserSP = !!userDefinedSPAddress.address && userDefinedSPAddress.isActive; - - const setServiceProvider = async (newServiceProvider: ServiceProvider) => { - await invoke('set_gateway', { - gateway: shouldUseUserGateway ? userDefinedGateway.gateway : newServiceProvider.gateway, - }); - await invoke('set_service_provider', { - serviceProvider: shouldUseUserSP ? userDefinedSPAddress.address : newServiceProvider.address, - }); - }; - - const getRandomSPFromList = (services: ServiceProvider[]) => { - const randomSelection = services[Math.floor(Math.random() * services.length)]; - return randomSelection; - }; - - const buildServiceProvider = async (serviceProvider: ServiceProvider) => { - const sp = { ...serviceProvider }; - - if (shouldUseUserGateway) sp.gateway = userDefinedGateway.gateway as string; - if (shouldUseUserSP) sp.address = userDefinedSPAddress.address as string; - - return sp; - }; - - const setSerivceProvider = async () => { - if (serviceProviders) { - const randomServiceProvider = getRandomSPFromList(serviceProviders); - const withUserDefinitions = await buildServiceProvider(randomServiceProvider); - await setServiceProvider(withUserDefinitions); - setSelectedProvider(withUserDefinitions); - } - return undefined; - }; - - const clearError = () => setError(undefined); - - const contextValue = useMemo( - () => ({ - mode, - appVersion, - setMode, - error, - clearError, - connectionStatus, - setConnectionStatus, - connectionStats, - showInfoModal, - setConnectionStats, - selectedProvider, - serviceProviders, - connectedSince, - setConnectedSince, - setSerivceProvider, - startConnecting, - startDisconnecting, - gatewayPerformance, - setShowInfoModal, - userDefinedSPAddress, - userDefinedGateway, - setUserDefinedGateway, - setUserDefinedSPAddress, - }), - [ - mode, - appVersion, - error, - showInfoModal, - serviceProviders, - connectedSince, - connectionStatus, - connectionStats, - connectedSince, - gatewayPerformance, - selectedProvider, - userDefinedGateway, - userDefinedSPAddress, - ], - ); - - return {children}; -}; - -export const useClientContext = () => useContext(ClientContext); diff --git a/nym-connect/mobile/src/context/mocks/main.tsx b/nym-connect/mobile/src/context/mocks/main.tsx deleted file mode 100644 index 78028b03f8..0000000000 --- a/nym-connect/mobile/src/context/mocks/main.tsx +++ /dev/null @@ -1,33 +0,0 @@ -import React, { useMemo } from 'react'; -import { ConnectionStatusKind } from 'src/types'; -import { ClientContext, TClientContext } from '../main'; - -const mockValues: TClientContext = { - appVersion: 'v1.x.x', - mode: 'dark', - connectionStatus: ConnectionStatusKind.disconnected, - selectedProvider: { id: '1', description: 'Keybase service provider', gateway: 'abc123', address: '123abc' }, - gatewayPerformance: 'Good', - showInfoModal: false, - userDefinedGateway: { isActive: false, gateway: '' }, - userDefinedSPAddress: { isActive: false, address: '' }, - setShowInfoModal: () => {}, - setMode: () => {}, - clearError: () => {}, - setConnectedSince: () => {}, - setConnectionStats: () => {}, - setConnectionStatus: () => {}, - startConnecting: async () => {}, - startDisconnecting: async () => {}, - setSerivceProvider: () => {}, - setUserDefinedGateway: () => {}, - setUserDefinedSPAddress: () => {}, -}; - -export const MockProvider: FCWithChildren<{ - children?: React.ReactNode; - connectionStatus?: ConnectionStatusKind; -}> = ({ connectionStatus = ConnectionStatusKind.disconnected, children }) => { - const value = useMemo(() => ({ ...mockValues, connectionStatus }), [connectionStatus]); - return {children}; -}; diff --git a/nym-connect/mobile/src/fonts/OpenSans-Italic-VariableFont_wdth,wght.ttf b/nym-connect/mobile/src/fonts/OpenSans-Italic-VariableFont_wdth,wght.ttf deleted file mode 100644 index 0fea34ba24ec13e9636f3ed04d575da62586be26..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 580468 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

    KDPq=O;8gv5j; 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

    l-5 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

    ly}MeV>=*ZTQ0urb=*FZRXrf1u%f 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 literal 0 HcmV?d00001 diff --git a/documentation/operators/src/introduction.md b/documentation/operators/src/introduction.md new file mode 100644 index 0000000000..f0344db71a --- /dev/null +++ b/documentation/operators/src/introduction.md @@ -0,0 +1,34 @@ +# Introduction + +This is Nym's operators guide, containing information and setup tutorials about the various pieces of Nym mixnet infrastructure nodes, such as mix node, gateway and network requester. + +If you are new to Nym and want to learn about the mixnet, how to integrate with the network, developer tutorials and quickstart guides, check out the [Developer Portal](https://nymtech.net/developers/). + +If you want to go deeper into Nym architecture and various software such as different, application clients, existing applications like the desktop wallet, mixnet explorer or SDKs, visit [Nym's technical documentation](https://nymtech.net/docs/). + +If you are looking for information on grants and beta-release Nym apps, check out the [Shipyard](https://shipyard.nymtech.net) site. + + +## Popular pages + + + +**Network Architecture:** +* [Network Overview](https://nymtech.net/docs/architecture/network-overview.html) +* [Mixnet Traffic Flow](https://nymtech.net/docs/architecture/traffic-flow.html) + +**Node setup and usage guides:** +* [Mix nodes](./nodes/mix-node-setup.md) +* [Gateways](./nodes/gateway-setup.md) +* [Network requesters](./nodes/network-requester-setup.md) + + +**Client setup and usage guides:** +* [Websocket client](https://nymtech.net/docs/clients/websocket-client.html) +* [Socks5 client](https://nymtech.net/docs/clients/socks5-client.html) +* [Webassembly client](https://nymtech.net/docs/clients/webassembly-client.html) + +**SDK guides:** +* [Typescript SDK](https://nymtech.net/docs/sdk/typescript.html) +* [Rust SDK](https://nymtech.net/docs/sdk/rust.html) + diff --git a/documentation/operators/src/nodes/mix-node-setup.md b/documentation/operators/src/nodes/mix-node-setup.md new file mode 100644 index 0000000000..a24ec587ad --- /dev/null +++ b/documentation/operators/src/nodes/mix-node-setup.md @@ -0,0 +1,700 @@ + + +# Mix Nodes + +> The Nym mix node binary was built in the [building nym](https://nymtech.net/docs/binaries/building-nym.html) section. If you haven't yet built Nym and want to run the code, go there first. + +> Any syntax in `<>` brackets is a user's unique variable. Exchange with a corresponding name without the `<>` brackets. + +## Current version +``` + +``` + +The `nym-mixnode` binary is currently one point version ahead of the rest of the platform binaries due to a patch applied between releases. + +## Preliminary steps + +There are a couple of steps that need completing before starting to set up your mix node: + +- preparing your [wallet](https://nymtech.net/docs/wallet/desktop-wallet.html) +- requisitioning a VPS (Virtual Private Server) + +### Wallet preparation + +#### Mainnet + +Before you initialise and run your mixnode, head to our [website](https://nymtech.net/download/) and download the Nym wallet for your operating system. If pre-compiled binaries for your operating system aren't available, you can build the wallet yourself with instructions [here](https://nymtech.net/docs/wallet/desktop-wallet.html). + +If you don't already have one, please create a Nym address using the wallet, and fund it with tokens. The minimum amount required to bond a mixnode is 100 `NYM`, but make sure you have a bit more to account for gas costs. + +`NYM` can be purchased via Bity from the wallet itself with BTC or fiat, and is currently present on several [exchanges](https://www.coingecko.com/en/coins/nym#markets). + +> Remember that you can **only** use Cosmos `NYM` tokens to bond your mixnode. You **cannot** use ERC20 representations of `NYM` to run a node. + +#### Sandbox testnet + +Make sure to download a wallet and create an account as outlined above. Then head to our [token faucet](https://faucet.nymtech.net/) and get some tokens to use to bond it. + +### VPS Hardware Specs + +You will need to rent a VPS to run your mix node on. One key reason for this is that your node **must be able to send TCP data using both IPv4 and IPv6** (as other nodes you talk to may use either protocol). + +For the moment, we haven't put a great amount of effort into optimizing concurrency to increase throughput, so don't bother provisioning a beastly server with multiple cores. This will change when we get a chance to start doing performance optimizations in a more serious way. Sphinx packet decryption is CPU-bound, so once we optimize, more fast cores will be better. + +For now, see the below rough specs: + +- Processors: 2 cores are fine. Get the fastest CPUs you can afford. +- RAM: Memory requirements are very low - typically a mix node may use only a few hundred MB of RAM. +- Disks: The mixnodes require no disk space beyond a few bytes for the configuration files. + +## Mix node setup + +Now that you have built the codebase, set up your wallet, and have a VPS with the `nym-mixnode` binary, you can set up your mix node with the instructions below. + +To begin, move to `/taget/release` directory from which you run the node commands: + +``` +cd target/release +``` + +### Viewing command help + +You can check that your binaries are properly compiled with: + +``` +./nym-mixnode --help +``` + +Which should return a list of all available commands. + +~~~admonish example collapsible=true title="Console output" +``` + +``` +~~~ + +You can also check the various arguments required for individual commands with: + +``` +./nym-mixnode --help +``` + +### Initialising your mix node + +To check available configuration options for initializing your node use: + +``` +./nym-mixnode init --help +``` + +~~~admonish example collapsible=true title="Console output" +``` + +``` +~~~ + +Initalise your mixnode with the following command, replacing the value of `--id` with the moniker you wish to give your mixnode, and the `--wallet-address` with the Nym address you created earlier. Your `--host` must be publicly routable on the internet in order to mix packets, and can be either an Ipv4 or IPv6 address. The `$(curl ifconfig.me)` command returns your IP automatically using an external service. If you enter your IP address manually, enter it **without** any port information. + +``` +./nym-mixnode init --id --host $(curl ifconfig.me) --wallet-address +``` + + +~~~admonish example collapsible=true title="Console output" +``` +.nym-mixnode init --id --host $(curl ifconfig.me) --wallet-address + + +Initialising mixnode ... +Saved mixnet identity and sphinx keypairs + 2023-06-04T08:20:32.862Z INFO nym_config > Configuration file will be saved to "/home//.nym/mixnodes//config/config.toml" +Saved configuration file to "/home//.nym/mixnodes//config/config.toml" +Mixnode configuration completed. + + _ __ _ _ _ __ ___ + | '_ \| | | | '_ \ _ \ + | | | | |_| | | | | | | + |_| |_|\__, |_| |_| |_| + |___/ + + (nym-mixnode - version {{mix_node_release_version}}) + + +Identity Key: DhmUYedPZvhP9MMwXdNpPaqCxxTQgjAg78s2nqtTTiNF","version":"{{mix_node_release_version}}"},"cost_params +Sphinx Key: CfZSy1jRfrfiVi9JYexjFWPqWkKoY72t7NdpWaq37K8Z +Host: 62.240.134.189 (bind address: 62.240.134.189) +Version: {{mix_node_release_version}} +Mix Port: 1789, Verloc port: 1790, Http Port: 8000 +``` +~~~ + +> The `init` command will refuse to destroy existing mix node keys. + +During the `init` process you will have the option to change the `http_api`, `verloc` and `mixnode` ports from their default settings. If you wish to change these in the future you can edit their values in the `config.toml` file created by the initialization process, which is located at `~/.nym/mixnodes//`. + +### Bonding your mix node + +```admonish caution +From `v1.1.3`, if you unbond your mixnode that means you are leaving the mixnet and you will lose all your delegations (permanently). You can join again with the same identity key, however, you will start with **no delegations**. +``` + +#### Bond via the Desktop wallet (recommended) + +You can bond your mix node via the Desktop wallet. + +* Open your wallet, and head to the `Bond` page, then select the node type `Mixnode` and input your node details. Press `Next`. + +* Enter the `Amount`, `Operating cost` and `Profit margin` and press `Next`. + +* You will be asked to run a the `sign` command with your `gateway` - copy and paste the long signature as the value of `--contract-msg` and run it. + +``` +./nym-mixnode sign --id --contract-msg +``` + +It will look something like this: + +~~~admonish example collapsible=true title="Console output" +``` +./nym-mixnode sign --id upgrade_test --contract-msg 22Z9wt4PyiBCbMiErxj5bBa4VCCFsjNawZ1KnLyMeV9pMUQGyksRVANbXHjWndMUaXNRnAuEVJW6UCxpRJwZe788hDt4sicsrv7iAXRajEq19cWPVybbUqgeo76wbXbCbRdg1FvVKgYZGZZp8D72p5zWhKSBRD44qgCrqzfV1SkiFEhsvcLUvZATdLRocAUL75KmWivyRiQjCE1XYEWyRH9yvRYn4TymWwrKVDtEB63zhHjATN4QEi2E5qSrSbBcmmqatXsKakbgSbQoLsYygcHx7tkwbQ2HDYzeiKP1t16Rhcjn6Ftc2FuXUNnTcibk2LQ1hiqu3FAq31bHUbzn2wiaPfm4RgqTwGM4eqnjBofwR3251wQSxbYwKUYwGsrkweRcoPuEaovApR9R19oJ7GVG5BrKmFwZWX3XFVuECe8vt1x9MY7DbQ3xhAapsHhThUmzN6JPPU4qbQ3PdMt3YVWy6oRhap97ma2dPMBaidebfgLJizpRU3Yu7mtb6E8vgi5Xnehrgtd35gitoJqJUY5sB1p6TDPd6vk3MVU1zqusrke7Lvrud4xKfCLqp672Bj9eGb2wPwow643CpHuMkhigfSWsv9jDq13d75EGTEiprC2UmWTzCJWHrDH7ka68DZJ5XXAW67DBewu7KUm1jrJkNs55vS83SWwm5RjzQLVhscdtCH1Bamec6uZoFBNVzjs21o7ax2WHDghJpGMxFi6dmdMCZpqn618t4 + + _ __ _ _ _ __ ___ + | '_ \| | | | '_ \ _ \ + | | | | |_| | | | | | | + |_| |_|\__, |_| |_| |_| + |___/ + + (nym-mixnode - version {{mix_node_release_version}}) + + +>>> attempting to sign 22Z9wt4PyiBCbMiErxj5bBa4VCCFsjNawZ1KnLyMeV9pMUQGyksRVANbXHjWndMUaXNRnAuEVJW6UCxpRJwZe788hDt4sicsrv7iAXRajEq19cWPVybbUqgeo76wbXbCbRdg1FvVKgYZGZZp8D72p5zWhKSBRD44qgCrqzfV1SkiFEhsvcLUvZATdLRocAUL75KmWivyRiQjCE1XYEWyRH9yvRYn4TymWwrKVDtEB63zhHjATN4QEi2E5qSrSbBcmmqatXsKakbgSbQoLsYygcHx7tkwbQ2HDYzeiKP1t16Rhcjn6Ftc2FuXUNnTcibk2LQ1hiqu3FAq31bHUbzn2wiaPfm4RgqTwGM4eqnjBofwR3251wQSxbYwKUYwGsrkweRcoPuEaovApR9R19oJ7GVG5BrKmFwZWX3XFVuECe8vt1x9MY7DbQ3xhAapsHhThUmzN6JPPU4qbQ3PdMt3YVWy6oRhap97ma2dPMBaidebfgLJizpRU3Yu7mtb6E8vgi5Xnehrgtd35gitoJqJUY5sB1p6TDPd6vk3MVU1zqusrke7Lvrud4xKfCLqp672Bj9eGb2wPwow643CpHuMkhigfSWsv9jDq13d75EGTEiprC2UmWTzCJWHrDH7ka68DZJ5XXAW67DBewu7KUm1jrJkNs55vS83SWwm5RjzQLVhscdtCH1Bamec6uZoFBNVzjs21o7ax2WHDghJpGMxFi6dmdMCZpqn618t4 +>>> decoding the message... +>>> message to sign: {"nonce":0,"algorithm":"ed25519","message_type":"mixnode-bonding","content":{"sender":"n1eufxdlgt0puwrwptgjfqne8pj4nhy2u5ft62uq","proxy":null,"funds":[{"denom":"unym","amount":"100000000"}],"data":{"mix_node":{"host":"62.240.134.189","mix_port":1789,"verloc_port":1790,"http_api_port":8000,"sphinx_key":"CfZSy1jRfrfiVi9JYexjFWPqWkKoY72t7NdpWaq37K8Z","identity_key":"DhmUYedPZvhP9MMwXdNpPaqCxxTQgjAg78s2nqtTTiNF","version":"1.1.14"},"cost_params":{"profit_margin_percent":"0.1","interval_operating_cost":{"denom":"unym","amount":"40000000"}}}}} +``` +~~~ + +* Copy the resulting signature: + +``` +>>> The base58-encoded signature is: +2GbKcZVKFdpi3sR9xoJWzwPuGdj3bvd7yDtDYVoKfbTWdpjqAeU8KS5bSftD5giVLJC3gZiCg2kmEjNG5jkdjKUt +``` + +* And paste it into the wallet nodal, press `Next` and confirm the transaction. + +![Paste Signature](../images/wallet-screenshots/wallet-sign.png) + +* Your node will now be bonded and ready to mix at the beginning of the next epoch (at most 1 hour). + +> You are asked to `sign` a transaction on bonding so that the mixnet smart contract is able to map your nym address to your node. This allows us to create a nonce for each account and defend against replay attacks. + +#### Bond via the CLI (power users) +If you want to bond your mix node via the CLI, then check out the [relevant section in the Nym CLI](https://nymtech.net/docs/tools/nym-cli.html#bond-a-mix-node) docs. + +### Running your mix node + +Now you've bonded your mix node, run it with: + +``` +./nym-mixnode run --id +``` + +If everything worked, you'll see your node running on the either the [Sandbox testnet network explorer](https://sandbox-explorer.nymtech.net) or the [mainnet network explorer](https://explorer.nymtech.net), depending on which network you're running. + +Note that your node's public identity key is displayed during startup, you can use it to identify your node in the list. + + + +Have a look at the saved configuration files in `$HOME/.nym/mixnodes/` to see more configuration options. + +### Describe your mix node (optional) + +In order to easily identify your node via human-readable information later on in the development of the testnet when delegated staking is implemented, you can `describe` your mixnode with the following command: + +``` +./nym-mixnode describe --id +``` + +> Remember to restart your mix node process in order for the new description to be propagated. + +### Upgrading your mix node + +Upgrading your node is a two-step process: +* Updating the binary and `~/.nym/mixnodes//config.toml` on your VPS +* Updating the node information in the [mixnet smart contract](../nyx/mixnet-contract.md). **This is the information that is present on the [mixnet explorer](https://explorer.nymtech.net)**. + +#### Step 1: Upgrading your binary +Follow these steps to upgrade your mix node binary and update its config file: +* pause your mix node process. +* replace the existing binary with the newest binary (which you can either [compile yourself](https://nymtech.net/docs/binaries/building-nym.html) or grab from our [releases page](https://github.com/nymtech/nym/releases)). +* re-run `init` with the same values as you used initially. **This will just update the config file, it will not overwrite existing keys**. +* restart your mix node process with the new binary. + +#### Step 2: Updating your node information in the smart contract +Follow these steps to update the information about your mix node which is publically avaliable from the [Nym API](https://validator.nymtech.net/api/swagger/index.html) and information displayed on the [mixnet explorer](https://explorer.nymtech.net). + +You can either do this graphically via the Desktop Wallet, or the CLI. + +#### Updating node information via the Desktop Wallet +* Navigate to the `Bonding` page and click the `Node Settings` link in the top right corner: +\ +![Bonding page](../images/wallet-screenshots/bonding.png) + +* Update the fields in the `Node Settings` page and click `Submit changes to the blockchain`. +\ +![Node Settings Page](../images/wallet-screenshots/node_settings.png) + +#### Updating node information via the CLI +If you want to bond your mix node via the CLI, then check out the [relevant section in the Nym CLI](../../documentation/docs/src/tools/nym-cli.md#upgrade-a-mix-node) docs. + +### Displaying mix node information + +You can always check the details of your mix node with the `node-details` command: + +``` +./nym-mixnode node-details --id +``` + +## VPS Setup and Automation +### Configure your firewall +The following commands will allow you to set up a firewall using `ufw`. + +``` +# check if you have ufw installed +ufw version + +# if it is not installed, install with +sudo apt install ufw -y + +# enable ufw +sudo ufw enable + +# check the status of the firewall +sudo ufw status +``` + +Finally open your mix node's p2p port, as well as ports for ssh and ports `8000` and `1790` for verloc and measurement pings: + +``` +sudo ufw allow 1789,1790,8000,22/tcp + +# check the status of the firewall +sudo ufw status +``` + +For more information about your mix node's port configuration, check the [mix node port reference table](./mix-node-setup.md#mixnode-port-reference) below. + +### Automating your mix node with tmux and systemd + +It's useful to have the mix node automatically start at system boot time. + +#### tmux + +One way is to use `tmux` shell on top of your current VPS terminal. Tmux is a terminal multiplexer, it allows you to create several terminal windows and panes from a single terminal. Processes started in `tmux` keep running after closing the terminal as long as the given `tmux` window was not terminated. + +Use the following command to get `tmux`. + +Platform|Install Command +---|--- +Arch Linux|`pacman -S tmux` +Debian or Ubuntu|`apt install tmux` +Fedora|`dnf install tmux` +RHEL or CentOS|`yum install tmux` +macOS (using Homebrew|`brew install tmux` +macOS (using MacPorts)|`port install tmux` +openSUSE|`zypper install tmux` + +In case it didn't work for your distribution, see how to build `tmux` from [version control](https://github.com/tmux/tmux#from-version-control). + +**Running tmux** + +No when you installed tmux on your VPS, let's run a mixnode on tmux, which allows you to detach your terminal and let the mixnode run on its own on the VPS. + +* Pause your mixnode +* Start tmux with the command +``` +tmux +``` +* The terminal should stay in the same directory, just the layout changed into tmux default layout. +* Start the mixnode again with a command: +``` +./nym-mixnode run --id +``` +* Now, without closing the tmux window, you can close the whole terminal and the mixnode (and any other process running in tmux) will stay active. +* Next time just start your teminal, ssh into the VPS and run the following command to attach back to your previous session: +``` +tmux attach session +``` +* To see keybinding options of tmux press `ctrl`+`b` and after 1 second `?` + +#### systemd + +Here's a systemd service file to do that: + +```ini +[Unit] +Description=Nym Mixnode ({{mix_node_release_version}}) +StartLimitInterval=350 +StartLimitBurst=10 + +[Service] +User= +LimitNOFILE=65536 +ExecStart=/home///nym-mixnode run --id +KillSignal=SIGINT +Restart=on-failure +RestartSec=30 + +[Install] +WantedBy=multi-user.target +``` + +Put the above file onto your system at `/etc/systemd/system/nym-mixnode.service`. + +Change the `` in `ExecStart` to point at your mix node binary (`nym-mixnode`), and the `` so it is the user you are running as. + +If you have built nym in the `$HOME` directory on your server, and your username is `jetpanther`, then the start command might look like this: + +`ExecStart=/home/jetpanther/nym/target/release/nym-mixnode run --id your-id`. Basically, you want the full `/path/to/nym-mixnode run --id whatever-your-node-id-is` + +Then run: + +``` +systemctl enable nym-mixnode.service +``` + +Start your node: + +``` +service nym-mixnode start +``` + +This will cause your node to start at system boot time. If you restart your machine, the node will come back up automatically. + +You can also do `service nym-mixnode stop` or `service nym-mixnode restart`. + +Note: if you make any changes to your systemd script after you've enabled it, you will need to run: + +``` +systemctl daemon-reload +``` + +This lets your operating system know it's ok to reload the service configuration. + +#### Setting the ulimit + +Linux machines limit how many open files a user is allowed to have. This is called a `ulimit`. + +`ulimit` is 1024 by default on most systems. It needs to be set higher, because mix nodes make and receive a lot of connections to other nodes. + +If you see errors such as: + +``` +Failed to accept incoming connection - Os { code: 24, kind: Other, message: "Too many open files" } +``` + +This means that the operating system is preventing network connections from being made. + +##### Set the ulimit via `systemd` service file + +Query the `ulimit` of your mix node with: + +``` +grep -i "open files" /proc/$(ps -A -o pid,cmd|grep nym-mixnode | grep -v grep |head -n 1 | awk '{print $1}')/limits +``` + +You'll get back the hard and soft limits, which looks something like this: + +``` +Max open files 65536 65536 files +``` + +If your output is **the same as above**, your node will not encounter any `ulimit` related issues. + +However if either value is `1024`, you must raise the limit via the systemd service file. Add the line: + +``` +LimitNOFILE=65536 +``` + +Reload the daemon: + +``` +systemctl daemon-reload +``` + +or execute this as root for system-wide setting of `ulimit`: + +``` +echo "DefaultLimitNOFILE=65535" >> /etc/systemd/system.conf +``` + +Reboot your machine and restart your node. When it comes back, use `cat /proc/$(pidof nym-mixnode)/limits | grep "Max open files"` to make sure the limit has changed to 65535. + +##### Set the ulimit on `non-systemd` based distributions + +In case you chose tmux option for mixnode automatization, see your `ulimit` list by running: + +``` +ulimit -a + +# watch for the output line -n +-n: file descriptors 1024 +``` + +You can change it either by running a command: + +``` +ulimit -u -n 4096 +``` + +or editing `etc/security/conf` and add the following lines: + +``` +# Example hard limit for max opened files +username hard nofile 4096 + +# Example soft limit for max opened files +username soft nofile 4096 +``` + +Then reboot your server and restart your mixnode. + +## Node Description +Node description is a short text that describes your node. It is displayed in the `./nym-mixnode list` command and in the `./nym-mixnode node-details` command. It also shows up in the node explorer to let people know what your node is about and link to your website. + +To set your node description, create a file called `description.toml` and put it in the same directory as your `config.toml` file (`~/.nym/mixnodes//description.toml`). The file should look like this example: + +```toml +name = "Winston Smith" +description = "I am the Sphinx" +link = "https://nymtech.net" +location = "Giza, Egypt" +``` + +You will need to restart your node for the changes to take effect. + +## Node Families + +Node family involves setting up a group of mix nodes that work together to provide greater privacy and security for network communications. This is achieved by having the nodes in the family share information and routes, creating a decentralized network that makes it difficult for third parties to monitor or track communication traffic. + +### Create a Node Family + +To create a Node family, you will need to install and configure multiple mix nodes, and then use the CLI to link them together into a family. Once your Node family is up and running, you can use it to route your network traffic through a series of nodes, obscuring the original source and destination of the communication. + +You can use either `nym-cli` which can be downloaded from the [release page](https://github.com/nymtech/nym/releases) or compiling `nyxd`. + + +Change directory by `cd ///` and run the following on the family head to obtain the signature for the member: + +``` +./nym-mixnode sign --id --text +``` + +~~~admonish example collapsible=true title="Console output" +``` + +``` +~~~ + +Using `nym-cli`: + +> `--mnemonic` is the mnemonic of the member wanting to be the head of family. + +``` +/nym-cli cosmwasm execute '{"create_family": {"signature": "","family_head": "","owner_signature":"","label": ""}}' --mnemonic +``` + +Using `nyxd`: + +> `--from` is mnemonic of the member wanting to join the family. + +``` +./nyxd tx wasm execute ${MIXNET-CONTRACT} '{"join_family": {"signature": "","family_head": ""}}' --node ${VALIDATOR-ENDPOINT} --from mix1 --chain-id nyx --gas-prices 0.025unym --gas auto --gas-adjustment 1.3 -y -b block +``` + +To get the node owner signature, use: + +`./nym-mixnode node-details --id ` + +### Joining a Node Family + +Change directory by `cd ///` and run the following on the family head to obtain the signature for the member: + +``` +./nym-mixnode sign --id --text +``` + +~~~admonish example collapsible=true title="Console output" +``` + +``` +~~~ + +Using `nym-cli`: + +``` +./nym-cli cosmwasm execute '{"join_family": {"signature": "","family_head": "","owner_signautre": "", "label":""}}' --mnemonic +``` + +Using `nyxd`: + +``` +./nyxd tx wasm execute ${MIXNET-CONTRACT} '{"join_family": {"signature": "","family_head": ""}}' --node ${VALIDATOR-ENDPOINT} --from mix1 --chain-id nyx --gas-prices 0.025unym --gas auto --gas-adjustment 1.3 -y -b block +``` + + +To get the node owner signature, use: + +`./nym-mixnode node-details --id ` + + +### Leaving a family +If wanting to leave, run the same initial command as above, followed by: + +Using `nym-cli`: + +``` +./nym-cli cosmwasm execute '{"leave_family": {"signature": "","family_head": "","owner_signautre": ""}}' --mnemonic +``` + +Using `nyxd`: + +``` +./nyxd tx wasm execute ${MIXNET-CONTRACT} '{"join_family": {"signature": "","family_head": ""}}' --node ${VALIDATOR-ENDPOINT} --from mix1 --chain-id nyx --gas-prices 0.025unym --gas auto --gas-adjustment 1.3 -y -b block +``` + +## Checking that your node is mixing correctly +### Network explorers +Once you've started your mix node and it connects to the validator, your node will automatically show up in the 'Mix nodes' section of either the Nym Network Explorers: + +- [Mainnet](https://explorer.nymtech.net/overview) +- [Sandbox testnet](https://sandbox-explorer.nymtech.net/) + +Enter your **identity key** to find your node. There are numerous statistics about your node on that page that are useful for checking your uptime history, packets mixed, and any delegations your node may have. + +There are also 2 community explorers which have been created by [Nodes Guru](https://nodes.guru): + +- [Mainnet](https://mixnet.explorers.guru/) +- [Sandbox testnet](https://sandbox.mixnet.explorers.guru/) + +For more details see [Troubleshooting FAQ](../nodes/troubleshooting.md) + + + +### Virtual IPs and hosting via Google & AWS +For tru internet decentralization we encourage operators to use diverse VPS providers instead of the largest companies offering such services. If for some reasons you have already running AWS or Google and want to setup a mixnode there, please read the following. + +On some services (AWS, Google, etc) the machine's available bind address is not the same as the public IP address. In this case, bind `--host` to the local machine address returned by `$(curl ifconfig.me)`, but also specify `--announce-host` with the public IP. Please make sure that you pass the correct, routable `--announce-host`. + +For example, on a Google machine, you may see the following output from the `ifconfig` command: + +``` +ens4: flags=4163 mtu 1460 + inet 10.126.5.7 netmask 255.255.255.255 broadcast 0.0.0.0 + ... +``` + +The `ens4` interface has the IP `10.126.5.7`. But this isn't the public IP of the machine, it's the IP of the machine on Google's internal network. Google uses virtual routing, so the public IP of this machine is something else, maybe `36.68.243.18`. + +`./nym-mixnode init --host 10.126.5.7`, initalises the mix node, but no packets will be routed because `10.126.5.7` is not on the public internet. + +Trying `nym-mixnode init --host 36.68.243.18`, you'll get back a startup error saying `AddrNotAvailable`. This is because the mix node doesn't know how to bind to a host that's not in the output of `ifconfig`. + +The right thing to do in this situation is to init with a command: +``` +./nym-mixnode init --host 10.126.5.7 --announce-host 36.68.243.18 +``` + +This will bind the mix node to the available host `10.126.5.7`, but announce the mix node's public IP to the directory server as `36.68.243.18`. It's up to you as a node operator to ensure that your public and private IPs match up properly. + +To find the right IP configuration, contact your VPS provider for support. + +## Metrics / API endpoints +The mix node binary exposes several API endpoints that can be pinged in order to gather information about the node, and the Nym API (previously 'Validator API') exposes numerous mix node related endpoints which provide network-wide information about mix nodes, the network topology (the list of avaliable mix nodes for packet routing), and information regarding uptime monitoring and rewarding history. + +### Mix node API endpoints +Since the mix node binary exposes several API endpoints itself, you can ping these easily via curl: + +| Endpoint | Description | Command | +| -------------------- | ------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------- | +| `/description` | Returns the description of the node set with the `describe` command | `curl :8000/description` | +| `/hardware` | Returns the hardware information of the node | `curl :8000/hardware` | +| `/verloc` | Returns the verloc information of the node, updated every 12 hours | `curl :8000/verloc` | + +The code for exposed API endpoints can be found [here](https://github.com/nymtech/nym/tree/release/{{platform_release_version}}/mixnode/src/node/http). + +> You can get more detailed info by appending `?debug` to the URL, like so: +> ``` +> curl http://:8000/stats?debug +> ``` + +### Mix node related Nym API (previously 'Validator API') endpoints +Numerous endpoints are documented on the Nym API (previously 'Validator API')'s [Swagger Documentation](https://validator.nymtech.net/api/swagger/index.html). There you can also try out various requests from your browser, and download the response from the API. Swagger will also show you what commands it is running, so that you can run these from an app or from your CLI if you prefer. + +#### Mix node Reward Estimation API endpoint + +The Reward Estimation API endpoint allows mixnode operators to estimate the rewards they could earn for running a Nym mixnode with a specific `MIX_ID`. + +> The `` can be found in the "Mix ID" column of the [Network Explorer](https://explorer.nymtech.net/network-components/mixnodes/active). + +The endpoint is a particularly common for mixnode operators as it can provide an estimate of potential earnings based on factors such as the amount of traffic routed through the mixnode, the quality of the mixnode's performance, and the overall demand for mixnodes in the network. This information can be useful for mixnode operators in deciding whether or not to run a mix node and in optimizing its operations for maximum profitability. + +Using this API endpoint returns information about the Reward Estimation: + +``` +/status/mixnode//reward-estimation +``` + +Query Response: + +``` + "estimation": { + "total_node_reward": "942035.916721770541325331", + "operator": "161666.263307386408152071", + "delegates": "780369.65341438413317326", + "operating_cost": "54444.444444444444444443" + }, +``` + +> The unit of value is measured in `uNYM`. + +- `estimated_total_node_reward` - An estimate of the total amount of rewards that a particular mix node can expect to receive during the current epoch. This value is calculated by the Nym Validator based on a number of factors, including the current state of the network, the number of mix nodes currently active in the network, and the amount of network traffic being processed by the mix node. + +- `estimated_operator_reward` - An estimate of the amount of rewards that a particular mix node operator can expect to receive. This value is calculated by the Nym Validator based on a number of factors, including the amount of traffic being processed by the mix node, the quality of service provided by the mix node, and the operator's stake in the network. + +- `estimated_delegators_reward` - An estimate of the amount of rewards that mix node delegators can expect to receive individually. This value is calculated by the Nym Validator based on a number of factors, including the amount of traffic being processed by the mix node, the quality of service provided by the mix node, and the delegator's stake in the network. + +- `estimated_node_profit` - An estimate of the profit that a particular mix node operator can expect to earn. This value is calculated by subtracting the mix node operator's `operating_costs` from their `estimated_operator_reward` for the current epoch. + +- `estimated_operator_cost` - An estimate of the total cost that a particular mix node operator can expect to incur for their participation. This value is calculated by the Nym Validator based on a number of factors, including the cost of running a mix node, such as server hosting fees, and other expenses associated with operating the mix node. + +## Ports +All mix node-specific port configuration can be found in `$HOME/.nym/mixnodes//config/config.toml`. If you do edit any port configs, remember to restart your mix node. + +### Mix node port reference +| Default port | Use | +| ------------ | ------------------------- | +| `1789` | Listen for mixnet traffic | +| `1790` | Listen for VerLoc traffic | +| `8000` | Metrics http API endpoint | +/ + diff --git a/documentation/operators/src/nodes/setup-guides.md b/documentation/operators/src/nodes/setup-guides.md new file mode 100644 index 0000000000..b19cf07deb --- /dev/null +++ b/documentation/operators/src/nodes/setup-guides.md @@ -0,0 +1,18 @@ + +# Node Setup Guides + +To setup any type of Nym's node, start with building [Nym's platform](https://nymtech.net/docs/binaries/building-nym.html) on the machine (VPS) where you want to run the node. Nodes will need to be bond to Nym's wallet, setup one [here](https://nymtech.net/docs/wallet/desktop-wallet.html). + +This section contains setup guides for the following node types: +* [Mix node](./mix-node-setup.md) +* [Gateway](./gateway-setup.md) +* [Network Requester](./network-requester-setup.md) +* [Validator](./validator-setup.md) + + From 232032baf4b47c8959b81a04222d6232c3705b81 Mon Sep 17 00:00:00 2001 From: serinko Date: Thu, 13 Jul 2023 16:59:25 +0200 Subject: [PATCH 203/361] feature/operators-guide: coppied all configs from ../docs/ --- documentation/operators/.gitignore | 27 ++ documentation/operators/mdbook-admonish.css | 352 ++++++++++++++++++ .../operators/src/nodes/gateway-setup.md | 1 + .../src/nodes/network-requester-setup.md | 1 + .../operators/src/nodes/troubleshooting.md | 1 + 5 files changed, 382 insertions(+) create mode 100644 documentation/operators/.gitignore create mode 100644 documentation/operators/mdbook-admonish.css create mode 100644 documentation/operators/src/nodes/gateway-setup.md create mode 100644 documentation/operators/src/nodes/network-requester-setup.md create mode 100644 documentation/operators/src/nodes/troubleshooting.md diff --git a/documentation/operators/.gitignore b/documentation/operators/.gitignore new file mode 100644 index 0000000000..e84b38bdfb --- /dev/null +++ b/documentation/operators/.gitignore @@ -0,0 +1,27 @@ +# mdbook files +book +book/* +theme/index.hbs +theme/pagetoc.css +theme/pagetoc.js +theme/* +theme + +# --------------- +# Compiled assets +# --------------- +.sass-cache +_site + +# ---------- +# Developing +# ---------- +todo.md +.vscode + +# OSX +.DS_Store + +theme/ +theme +theme/* diff --git a/documentation/operators/mdbook-admonish.css b/documentation/operators/mdbook-admonish.css new file mode 100644 index 0000000000..5e360387df --- /dev/null +++ b/documentation/operators/mdbook-admonish.css @@ -0,0 +1,352 @@ +@charset "UTF-8"; +:root { + --md-admonition-icon--note: + url("data:image/svg+xml;charset=utf-8,"); + --md-admonition-icon--abstract: + url("data:image/svg+xml;charset=utf-8,"); + --md-admonition-icon--info: + url("data:image/svg+xml;charset=utf-8,"); + --md-admonition-icon--tip: + url("data:image/svg+xml;charset=utf-8,"); + --md-admonition-icon--success: + url("data:image/svg+xml;charset=utf-8,"); + --md-admonition-icon--question: + url("data:image/svg+xml;charset=utf-8,"); + --md-admonition-icon--warning: + url("data:image/svg+xml;charset=utf-8,"); + --md-admonition-icon--failure: + url("data:image/svg+xml;charset=utf-8,"); + --md-admonition-icon--danger: + url("data:image/svg+xml;charset=utf-8,"); + --md-admonition-icon--bug: + url("data:image/svg+xml;charset=utf-8,"); + --md-admonition-icon--example: + url("data:image/svg+xml;charset=utf-8,"); + --md-admonition-icon--quote: + url("data:image/svg+xml;charset=utf-8,"); + --md-details-icon: + url("data:image/svg+xml;charset=utf-8,"); +} + +:is(.admonition) { + display: flow-root; + margin: 1.5625em 0; + padding: 0 1.2rem; + color: var(--fg); + page-break-inside: avoid; + background-color: var(--bg); + border: 0 solid black; + border-inline-start-width: 0.4rem; + border-radius: 0.2rem; + box-shadow: 0 0.2rem 1rem rgba(0, 0, 0, 0.05), 0 0 0.1rem rgba(0, 0, 0, 0.1); +} +@media print { + :is(.admonition) { + box-shadow: none; + } +} +:is(.admonition) > * { + box-sizing: border-box; +} +:is(.admonition) :is(.admonition) { + margin-top: 1em; + margin-bottom: 1em; +} +:is(.admonition) > .tabbed-set:only-child { + margin-top: 0; +} +html :is(.admonition) > :last-child { + margin-bottom: 1.2rem; +} + +a.admonition-anchor-link { + display: none; + position: absolute; + left: -1.2rem; + padding-right: 1rem; +} +a.admonition-anchor-link:link, a.admonition-anchor-link:visited { + color: var(--fg); +} +a.admonition-anchor-link:link:hover, a.admonition-anchor-link:visited:hover { + text-decoration: none; +} +a.admonition-anchor-link::before { + content: "§"; +} + +:is(.admonition-title, summary) { + position: relative; + margin-block: 0; + margin-inline: -1.6rem -1.2rem; + padding-block: 0.8rem; + padding-inline: 4.4rem 1.2rem; + font-weight: 700; + background-color: rgba(68, 138, 255, 0.1); + display: flex; +} +:is(.admonition-title, summary) p { + margin: 0; +} +html :is(.admonition-title, summary):last-child { + margin-bottom: 0; +} +:is(.admonition-title, summary)::before { + position: absolute; + top: 0.625em; + inset-inline-start: 1.6rem; + width: 2rem; + height: 2rem; + background-color: #448aff; + mask-image: url('data:image/svg+xml;charset=utf-8,'); + -webkit-mask-image: url('data:image/svg+xml;charset=utf-8,'); + mask-repeat: no-repeat; + -webkit-mask-repeat: no-repeat; + mask-size: contain; + -webkit-mask-size: contain; + content: ""; +} +:is(.admonition-title, summary):hover a.admonition-anchor-link { + display: initial; +} + +details.admonition > summary.admonition-title::after { + position: absolute; + top: 0.625em; + inset-inline-end: 1.6rem; + height: 2rem; + width: 2rem; + background-color: currentcolor; + mask-image: var(--md-details-icon); + -webkit-mask-image: var(--md-details-icon); + mask-repeat: no-repeat; + -webkit-mask-repeat: no-repeat; + mask-size: contain; + -webkit-mask-size: contain; + content: ""; + transform: rotate(0deg); + transition: transform 0.25s; +} +details[open].admonition > summary.admonition-title::after { + transform: rotate(90deg); +} + +:is(.admonition):is(.note) { + border-color: #448aff; +} + +:is(.note) > :is(.admonition-title, summary) { + background-color: rgba(68, 138, 255, 0.1); +} +:is(.note) > :is(.admonition-title, summary)::before { + background-color: #448aff; + mask-image: var(--md-admonition-icon--note); + -webkit-mask-image: var(--md-admonition-icon--note); + mask-repeat: no-repeat; + -webkit-mask-repeat: no-repeat; + mask-size: contain; + -webkit-mask-repeat: no-repeat; +} + +:is(.admonition):is(.abstract, .summary, .tldr) { + border-color: #00b0ff; +} + +:is(.abstract, .summary, .tldr) > :is(.admonition-title, summary) { + background-color: rgba(0, 176, 255, 0.1); +} +:is(.abstract, .summary, .tldr) > :is(.admonition-title, summary)::before { + background-color: #00b0ff; + mask-image: var(--md-admonition-icon--abstract); + -webkit-mask-image: var(--md-admonition-icon--abstract); + mask-repeat: no-repeat; + -webkit-mask-repeat: no-repeat; + mask-size: contain; + -webkit-mask-repeat: no-repeat; +} + +:is(.admonition):is(.info, .todo) { + border-color: #00b8d4; +} + +:is(.info, .todo) > :is(.admonition-title, summary) { + background-color: rgba(0, 184, 212, 0.1); +} +:is(.info, .todo) > :is(.admonition-title, summary)::before { + background-color: #00b8d4; + mask-image: var(--md-admonition-icon--info); + -webkit-mask-image: var(--md-admonition-icon--info); + mask-repeat: no-repeat; + -webkit-mask-repeat: no-repeat; + mask-size: contain; + -webkit-mask-repeat: no-repeat; +} + +:is(.admonition):is(.tip, .hint, .important) { + border-color: #00bfa5; +} + +:is(.tip, .hint, .important) > :is(.admonition-title, summary) { + background-color: rgba(0, 191, 165, 0.1); +} +:is(.tip, .hint, .important) > :is(.admonition-title, summary)::before { + background-color: #00bfa5; + mask-image: var(--md-admonition-icon--tip); + -webkit-mask-image: var(--md-admonition-icon--tip); + mask-repeat: no-repeat; + -webkit-mask-repeat: no-repeat; + mask-size: contain; + -webkit-mask-repeat: no-repeat; +} + +:is(.admonition):is(.success, .check, .done) { + border-color: #00c853; +} + +:is(.success, .check, .done) > :is(.admonition-title, summary) { + background-color: rgba(0, 200, 83, 0.1); +} +:is(.success, .check, .done) > :is(.admonition-title, summary)::before { + background-color: #00c853; + mask-image: var(--md-admonition-icon--success); + -webkit-mask-image: var(--md-admonition-icon--success); + mask-repeat: no-repeat; + -webkit-mask-repeat: no-repeat; + mask-size: contain; + -webkit-mask-repeat: no-repeat; +} + +:is(.admonition):is(.question, .help, .faq) { + border-color: #64dd17; +} + +:is(.question, .help, .faq) > :is(.admonition-title, summary) { + background-color: rgba(100, 221, 23, 0.1); +} +:is(.question, .help, .faq) > :is(.admonition-title, summary)::before { + background-color: #64dd17; + mask-image: var(--md-admonition-icon--question); + -webkit-mask-image: var(--md-admonition-icon--question); + mask-repeat: no-repeat; + -webkit-mask-repeat: no-repeat; + mask-size: contain; + -webkit-mask-repeat: no-repeat; +} + +:is(.admonition):is(.warning, .caution, .attention) { + border-color: #ff9100; +} + +:is(.warning, .caution, .attention) > :is(.admonition-title, summary) { + background-color: rgba(255, 145, 0, 0.1); +} +:is(.warning, .caution, .attention) > :is(.admonition-title, summary)::before { + background-color: #ff9100; + mask-image: var(--md-admonition-icon--warning); + -webkit-mask-image: var(--md-admonition-icon--warning); + mask-repeat: no-repeat; + -webkit-mask-repeat: no-repeat; + mask-size: contain; + -webkit-mask-repeat: no-repeat; +} + +:is(.admonition):is(.failure, .fail, .missing) { + border-color: #ff5252; +} + +:is(.failure, .fail, .missing) > :is(.admonition-title, summary) { + background-color: rgba(255, 82, 82, 0.1); +} +:is(.failure, .fail, .missing) > :is(.admonition-title, summary)::before { + background-color: #ff5252; + mask-image: var(--md-admonition-icon--failure); + -webkit-mask-image: var(--md-admonition-icon--failure); + mask-repeat: no-repeat; + -webkit-mask-repeat: no-repeat; + mask-size: contain; + -webkit-mask-repeat: no-repeat; +} + +:is(.admonition):is(.danger, .error) { + border-color: #ff1744; +} + +:is(.danger, .error) > :is(.admonition-title, summary) { + background-color: rgba(255, 23, 68, 0.1); +} +:is(.danger, .error) > :is(.admonition-title, summary)::before { + background-color: #ff1744; + mask-image: var(--md-admonition-icon--danger); + -webkit-mask-image: var(--md-admonition-icon--danger); + mask-repeat: no-repeat; + -webkit-mask-repeat: no-repeat; + mask-size: contain; + -webkit-mask-repeat: no-repeat; +} + +:is(.admonition):is(.bug) { + border-color: #f50057; +} + +:is(.bug) > :is(.admonition-title, summary) { + background-color: rgba(245, 0, 87, 0.1); +} +:is(.bug) > :is(.admonition-title, summary)::before { + background-color: #f50057; + mask-image: var(--md-admonition-icon--bug); + -webkit-mask-image: var(--md-admonition-icon--bug); + mask-repeat: no-repeat; + -webkit-mask-repeat: no-repeat; + mask-size: contain; + -webkit-mask-repeat: no-repeat; +} + +:is(.admonition):is(.example) { + border-color: #7c4dff; +} + +:is(.example) > :is(.admonition-title, summary) { + background-color: rgba(124, 77, 255, 0.1); +} +:is(.example) > :is(.admonition-title, summary)::before { + background-color: #7c4dff; + mask-image: var(--md-admonition-icon--example); + -webkit-mask-image: var(--md-admonition-icon--example); + mask-repeat: no-repeat; + -webkit-mask-repeat: no-repeat; + mask-size: contain; + -webkit-mask-repeat: no-repeat; +} + +:is(.admonition):is(.quote, .cite) { + border-color: #9e9e9e; +} + +:is(.quote, .cite) > :is(.admonition-title, summary) { + background-color: rgba(158, 158, 158, 0.1); +} +:is(.quote, .cite) > :is(.admonition-title, summary)::before { + background-color: #9e9e9e; + mask-image: var(--md-admonition-icon--quote); + -webkit-mask-image: var(--md-admonition-icon--quote); + mask-repeat: no-repeat; + -webkit-mask-repeat: no-repeat; + mask-size: contain; + -webkit-mask-repeat: no-repeat; +} + +.navy :is(.admonition) { + background-color: var(--sidebar-bg); +} + +.ayu :is(.admonition), .coal :is(.admonition) { + background-color: var(--theme-hover); +} + +.rust :is(.admonition) { + background-color: var(--sidebar-bg); + color: var(--sidebar-fg); +} +.rust .admonition-anchor-link:link, .rust .admonition-anchor-link:visited { + color: var(--sidebar-fg); +} diff --git a/documentation/operators/src/nodes/gateway-setup.md b/documentation/operators/src/nodes/gateway-setup.md new file mode 100644 index 0000000000..f4ee68e242 --- /dev/null +++ b/documentation/operators/src/nodes/gateway-setup.md @@ -0,0 +1 @@ +# Gateway diff --git a/documentation/operators/src/nodes/network-requester-setup.md b/documentation/operators/src/nodes/network-requester-setup.md new file mode 100644 index 0000000000..797dfd7795 --- /dev/null +++ b/documentation/operators/src/nodes/network-requester-setup.md @@ -0,0 +1 @@ +# Network Requester diff --git a/documentation/operators/src/nodes/troubleshooting.md b/documentation/operators/src/nodes/troubleshooting.md new file mode 100644 index 0000000000..4f341277d3 --- /dev/null +++ b/documentation/operators/src/nodes/troubleshooting.md @@ -0,0 +1 @@ +# Troubleshooting From cbee6153bd4a11dad0bc7d8ea6d1e75c9b20e90b Mon Sep 17 00:00:00 2001 From: serinko Date: Fri, 14 Jul 2023 11:20:51 +0200 Subject: [PATCH 204/361] mixnode-seup: minor edits --- documentation/operators/src/introduction.md | 10 ++++++++-- documentation/operators/src/nodes/mix-node-setup.md | 2 +- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/documentation/operators/src/introduction.md b/documentation/operators/src/introduction.md index f0344db71a..d2c148bf64 100644 --- a/documentation/operators/src/introduction.md +++ b/documentation/operators/src/introduction.md @@ -21,7 +21,13 @@ If you are looking for information on grants and beta-release Nym apps, check ou * [Mix nodes](./nodes/mix-node-setup.md) * [Gateways](./nodes/gateway-setup.md) * [Network requesters](./nodes/network-requester-setup.md) - +* [Validators](./nodes/validator-setup.md) + +**Troubleshooting and FAQ** +* [Troubleshooting](./nodes/troubleshooting.md) +* [FAQ](./faq.md) + + diff --git a/documentation/operators/src/nodes/mix-node-setup.md b/documentation/operators/src/nodes/mix-node-setup.md index a24ec587ad..723984ff39 100644 --- a/documentation/operators/src/nodes/mix-node-setup.md +++ b/documentation/operators/src/nodes/mix-node-setup.md @@ -63,7 +63,7 @@ For now, see the below rough specs: ## Mix node setup -Now that you have built the codebase, set up your wallet, and have a VPS with the `nym-mixnode` binary, you can set up your mix node with the instructions below. +Now that you have built the [codebase](https://nymtech.net/docs/binaries/building-nym.html), set up your [wallet](https://nymtech.net/docs/wallet/desktop-wallet.html), and have a VPS with the `nym-mixnode` binary, you can set up your mix node with the instructions below. To begin, move to `/taget/release` directory from which you run the node commands: From 55cd1a8b9134f6503fb3863778d47bcbc080aa41 Mon Sep 17 00:00:00 2001 From: serinko Date: Fri, 14 Jul 2023 13:25:12 +0200 Subject: [PATCH 205/361] operators-guide: created generic docs maintenance.md and preliminary-steps.md to avoid redundancy --- documentation/operators/src/SUMMARY.md | 3 + documentation/operators/src/faq.md | 7 + .../wallet-gateway-sign.png | Bin 0 -> 118052 bytes documentation/operators/src/introduction.md | 3 +- .../operators/src/nodes/gateway-setup.md | 254 ++++++++++++- .../operators/src/nodes/maintenance.md | 357 ++++++++++++++++++ .../operators/src/nodes/mix-node-setup.md | 35 +- .../operators/src/preliminary-steps.md | 43 +++ 8 files changed, 667 insertions(+), 35 deletions(-) create mode 100644 documentation/operators/src/images/wallet-screenshots/wallet-gateway-sign.png create mode 100644 documentation/operators/src/nodes/maintenance.md create mode 100644 documentation/operators/src/preliminary-steps.md diff --git a/documentation/operators/src/SUMMARY.md b/documentation/operators/src/SUMMARY.md index 7bb81c8800..796c7d41ed 100644 --- a/documentation/operators/src/SUMMARY.md +++ b/documentation/operators/src/SUMMARY.md @@ -3,8 +3,11 @@ # Operators Guides - [Node Setup Guides](./nodes/setup-guides.md) +- [Preliminary Steps](./preliminary-steps.md) - [Mix Node](./nodes/mix-node-setup.md) - [Gateway](./nodes/gateway-setup.md) - [Network Requester](./nodes/network-requester-setup.md) + - [Validator](./nodes/validator-setup.md) +- [Maintenance](./nodes/maintenance.md) - [Troubleshooting](./nodes/troubleshooting.md) - [FAQ](./faq.md) diff --git a/documentation/operators/src/faq.md b/documentation/operators/src/faq.md index d6b32f3e77..5e1f5b72ad 100644 --- a/documentation/operators/src/faq.md +++ b/documentation/operators/src/faq.md @@ -4,4 +4,11 @@ TODO - [ ] List friendly VPS providers - [ ] Explain difficulty running a node on a self-hosted machine - [ ] Explain uNYM vs NYM. Why, ratio, etc + +Some community questions: +Hi, do you know if it's on the roadmap for network nodes (requesters, socks5 client, etc.) to reconnect to another gateway if the current gateway's connection drops or has issues? I have to reset my requester and socks5 clients to connect them to a new gateway almost every day...😕 having to delete the entire folders associated with them + +you should be able to switch gateway (and overwrite keys) using —force-register + + ---> diff --git a/documentation/operators/src/images/wallet-screenshots/wallet-gateway-sign.png b/documentation/operators/src/images/wallet-screenshots/wallet-gateway-sign.png new file mode 100644 index 0000000000000000000000000000000000000000..63a76aaf4daa49a970ceed5e65c6bcf03891d507 GIT binary patch literal 118052 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

    gh{@Ld5VYc~WzO%W;Gv3S$jd$h&x{|!h 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

    @E}Q7HmryqarIxWXBsD9W9l?>YP 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

    TaZoR+yex1nQT6Ur! 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 Fe*r}Fq&@%u literal 0 HcmV?d00001 diff --git a/documentation/operators/src/introduction.md b/documentation/operators/src/introduction.md index d2c148bf64..a97860f292 100644 --- a/documentation/operators/src/introduction.md +++ b/documentation/operators/src/introduction.md @@ -23,7 +23,8 @@ If you are looking for information on grants and beta-release Nym apps, check ou * [Network requesters](./nodes/network-requester-setup.md) * [Validators](./nodes/validator-setup.md) -**Troubleshooting and FAQ** +**Maintenance, troubleshooting and FAQ** +* [Maintenance](./nodes/maintenance.md) * [Troubleshooting](./nodes/troubleshooting.md) * [FAQ](./faq.md) diff --git a/documentation/operators/src/nodes/gateway-setup.md b/documentation/operators/src/nodes/gateway-setup.md index f4ee68e242..591cd3d1a9 100644 --- a/documentation/operators/src/nodes/gateway-setup.md +++ b/documentation/operators/src/nodes/gateway-setup.md @@ -1 +1,253 @@ -# Gateway +# Gateways + +> The Nym gateway was built in the [building nym](../binaries/building-nym.md) section. If you haven't yet built Nym and want to run the code, go there first. + +> Any syntax in `<>` brackets is a user's unique variable. Exchange with a corresponding name without the `<>` brackets. + +## Current version +``` + +``` + +## Preliminary steps + +Make sure you do the preparation listed in the [preliminary steps page](../preliminary-steps.md) before setting up your gateway. + + +## Gateway setup +Now that you have built the codebase, set up your wallet, and have a VPS with the `nym-gateway` binary, you can set up your gateway with the instructions below. + +To begin, move to `/taget/release` directory from which you run the node commands: + +``` +cd target/release +``` + +### Viewing command help +You can check that your binaries are properly compiled with: + +``` +./nym-gateway --help +``` + +~~~admonish example collapsible=true title="Console output" +``` + +``` +~~~ + +You can also check the various arguments required for individual commands with: + +``` +./nym-gateway --help +``` + +### Initialising your gateway +To check available configuration options use: + +``` + ./nym-gateway init --help +``` + +~~~admonish example collapsible=true title="Console output" +``` + +``` +~~~ + +The following command returns a gateway on your current IP with the `id` of `supergateway`: + +``` +./nym-gateway init --id supergateway --host $(curl ifconfig.me) --wallet-address n1eufxdlgt0puwrwptgjfqne8pj4nhy2u5ft62uq +``` + +~~~admonish example collapsible=true title="Console output" +``` + +``` +~~~ + +The `$(curl ifconfig.me)` command above returns your IP automatically using an external service. Alternatively, you can enter your IP manually if you wish. If you do this, remember to enter your IP **without** any port information. + +### Bonding your gateway +#### Via the Desktop wallet +You can bond your gateway via the Desktop wallet. + +1. Open your wallet, and head to the `Bonding` page, then select the node type `Gateway` and input your node details (Location format is , ). Press `Next` + +2. Enter the `Amount`, `Operating cost` and press `Next`. + +3. You will be asked to run a the `sign` command with your `gateway` - copy and paste the long signature as the value of `--contract-msg` and run it. + +``` +./nym-mixnode sign --id --contract-msg +``` + +It will look something like this: + +~~~admonish example collapsible=true title="Console output" +``` +./nym-gateway sign --id upgrade_test --contract-msg 2Mf8xYytgEeyJke9LA7TjhHoGQWNBEfgHZtTyy2krFJfGHSiqy7FLgTnauSkQepCZTqKN5Yfi34JQCuog9k6FGA2EjsdpNGAWHZiuUGDipyJ6UksNKRxnFKhYW7ri4MRduyZwbR98y5fQMLAwHne1Tjm9cXYCn8McfigNt77WAYwBk5bRRKmC34BJMmWcAxphcLES2v9RdSR68tkHSpy2C8STfdmAQs3tZg8bJS5Qa8pQdqx14TnfQAPLk3QYCynfUJvgcQTrg29aqCasceGRpKdQ3Tbn81MLXAGAs7JLBbiMEAhCezAr2kEN8kET1q54zXtKz6znTPgeTZoSbP8rzf4k2JKHZYWrHYF9JriXepuZTnyxAKAxvGFPBk8Z6KAQi33NRQkwd7MPyttatHna6kG9x7knffV6ebGzgRBf7NV27LurH8x4L1uUXwm1v1UYCA1WSBQ9Pp2JW69k5v5v7G9gBy8RUcZnMbeL26Qqb8WkuGcmuHhaFfoqSfV7PRHPpPT4M8uRqUyR4bjUtSJJM1yh6QSeZk9BEazzoJqPeYeGoiFDZ3LMj2jesbJweQR4caaYuRczK92UGSSqu9zBKmE45a + + + _ __ _ _ _ __ ___ + | '_ \| | | | '_ \ _ \ + | | | | |_| | | | | | | + |_| |_|\__, |_| |_| |_| + |___/ + + (nym-gateway - version {{platform_release_version}}) + + +>>> attempting to sign 2Mf8xYytgEeyJke9LA7TjhHoGQWNBEfgHZtTyy2krFJfGHSiqy7FLgTnauSkQepCZTqKN5Yfi34JQCuog9k6FGA2EjsdpNGAWHZiuUGDipyJ6UksNKRxnFKhYW7ri4MRduyZwbR98y5fQMLAwHne1Tjm9cXYCn8McfigNt77WAYwBk5bRRKmC34BJMmWcAxphcLES2v9RdSR68tkHSpy2C8STfdmAQs3tZg8bJS5Qa8pQdqx14TnfQAPLk3QYCynfUJvgcQTrg29aqCasceGRpKdQ3Tbn81MLXAGAs7JLBbiMEAhCezAr2kEN8kET1q54zXtKz6znTPgeTZoSbP8rzf4k2JKHZYWrHYF9JriXepuZTnyxAKAxvGFPBk8Z6KAQi33NRQkwd7MPyttatHna6kG9x7knffV6ebGzgRBf7NV27LurH8x4L1uUXwm1v1UYCA1WSBQ9Pp2JW69k5v5v7G9gBy8RUcZnMbeL26Qqb8WkuGcmuHhaFfoqSfV7PRHPpPT4M8uRqUyR4bjUtSJJM1yh6QSeZk9BEazzoJqPeYeGoiFDZ3LMj2jesbJweQR4caaYuRczK92UGSSqu9zBKmE45a +>>> decoding the message... +>>> message to sign: {"nonce":0,"algorithm":"ed25519","message_type":"gateway-bonding","content":{"sender":"n1ewmme88q22l8syvgshqma02jv0vqrug9zq9dy8","proxy":null,"funds":[{"denom":"unym","amount":"100000000"}],"data":{"gateway":{"host":"62.240.134.189","mix_port":1789,"clients_port":9000,"location":"62.240.134.189","sphinx_key":"FKbuN7mPdoCG9jA3CkAfXxC5X4rHhqeMVtmfRtJ3cFZd","identity_key":"3RoAhR8gEdfBETMjm2vbMFzKddxXDdE9ygBAnJHWqSzD","version":"1.1.13"}}}} +``` +~~~ + +* Copy the resulting signature: + +``` +>>> The base58-encoded signature is: +2SPDjLjX4b6XEtkgG7yD8Znsb1xycL1edFvRK4JcVnPsM9k6HXEUUeVS6rswRiYxoj1bMgiRKyPDwiksiuyxu8Xi +``` + +* And paste it into the wallet nodal, press `Next` and confirm the transaction. + +![Paste Signature](../images/wallet-screenshots/wallet-gateway-sign.png) + +* Your gateway is now bonded. + +> You are asked to `sign` a transaction on bonding so that the mixnet smart contract is able to map your nym address to your node. This allows us to create a nonce for each account and defend against replay attacks. + +#### Via the CLI (power users) +If you want to bond your mix node via the CLI, then check out the [relevant section in the Nym CLI](https://nymtech.net/docs/tools/nym-cli.html#bond-a-mix-node) docs. + +### Running your gateway +The `run` command starts the gateway: + +``` +./nym-gateway run --id +``` + +### Upgrading your gateway +Upgrading your node is a two-step process: +* Updating the binary and `config.toml` on your VPS +* Updating the node information in the [mixnet smart contract](../nyx/mixnet-contract.md). **This is the information that is present on the [mixnet explorer](https://explorer.nymtech.net)**. + +>These instructions are specifically regarding upgrading your gateway binary from one version to another. If you want to change node information such as the listening port, you can do this by clicking the `node settings` tab in the `Bonding` page of the wallet. + +#### Step 1: Upgrading your binary +Follow these steps to upgrade your binary and update its config file: +* pause your gateway process. +* replace the existing binary with the newest binary (which you can either compile yourself or grab from our [releases page](https://github.com/nymtech/nym/releases)). +* re-run `init` with the same values as you used initially. **This will just update the config file, it will not overwrite existing keys**. +* restart your gateway process with the new binary. + +> Do **not** use the `upgrade` command: there is a known error with the command that will be fixed in a subsequent release. + +#### Step 2: Updating your node information in the smart contract +Follow these steps to update the information about your node which is publically avaliable from the [Nym API](https://validator.nymtech.net/api/swagger/index.html) and information displayed on the [mixnet explorer](https://explorer.nymtech.net). + +You can either do this graphically via the Desktop Wallet, or the CLI. + +#### Updating node information via the Desktop Wallet +* Navigate to the `Bonding` page and click the `Node Settings` link in the top right corner: +![Bonding page](../images/wallet-screenshots/bonding.png) + +* Update the fields in the `Node Settings` page and click `Submit changes to the blockchain`. +![Node Settings Page](../images/wallet-screenshots/node_settings.png) + +#### Updating node information via the CLI +If you want to bond your mix node via the CLI, then check out the [relevant section in the Nym CLI](../tools/nym-cli.md#upgrade-a-mix-node) docs. + +## VPS Setup and Automation +### Configure your firewall +Although your gateway is now ready to receive traffic, your server may not be - the following commands will allow you to set up a properly configured firewall using `ufw`: + +``` +# check if you have ufw installed +ufw version +# if it is not installed, install with +sudo apt install ufw -y +# enable ufw +sudo ufw enable +# check the status of the firewall +sudo ufw status +``` + +Finally open your gateway's p2p port, as well as ports for ssh and incoming traffic connections: + +``` +sudo ufw allow 1789,22,9000/tcp +# check the status of the firewall +sudo ufw status +``` + +For more information about your gateway's port configuration, check the [gateway port reference table](#gateway-port-reference) below. + +### Automating your gateway with systemd +Although it's not totally necessary, it's useful to have the gateway automatically start at system boot time. Here's a systemd service file to do that: + +```ini +[Unit] +Description=Nym Gateway ({{platform_release_version}}) +StartLimitInterval=350 +StartLimitBurst=10 + +[Service] +User=nym +LimitNOFILE=65536 +ExecStart=/home/nym/nym-gateway run --id supergateway +KillSignal=SIGINT +Restart=on-failure +RestartSec=30 + +[Install] +WantedBy=multi-user.target +``` + +Put the above file onto your system at `/etc/systemd/system/nym-gateway.service`. + +Change the path in `ExecStart` to point at your gateway binary (`nym-gateway`), and the `User` so it is the user you are running as. + +If you have built nym on your server, and your username is `jetpanther`, then the start command might look like this: + +`ExecStart=/home/jetpanther/nym/target/release/nym-gateway run --id your-id`. Basically, you want the full `/path/to/nym-gateway run --id whatever-your-node-id-is` + + +Then run: + +``` +systemctl enable nym-gateway.service +``` + +Start your node: + +``` +service nym-gateway start +``` + +This will cause your node to start at system boot time. If you restart your machine, the node will come back up automatically. + +You can also do `service nym-gateway stop` or `service nym-gateway restart`. + +Note: if you make any changes to your systemd script after you've enabled it, you will need to run: + +``` +systemctl daemon-reload +``` + +This lets your operating system know it's ok to reload the service configuration. + +## Gateway related Validator API endpoints +Numerous gateway related API endpoints are documented on the Validator API's [Swagger Documentation](https://validator.nymtech.net/api/swagger/index.html). There you can also try out various requests from your broswer, and download the response from the API. Swagger will also show you what commands it is running, so that you can run these from an app or from your CLI if you prefer. + +## Ports +All gateway specific port configuration can be found in `$HOME/.nym/gateways//config/config.toml`. If you do edit any port configs, remember to restart your gateway. + +### Gateway port reference +| Default port | Use | +|--------------|---------------------------| +| 1789 | Listen for Mixnet traffic | +| 9000 | Listen for Client traffic | diff --git a/documentation/operators/src/nodes/maintenance.md b/documentation/operators/src/nodes/maintenance.md new file mode 100644 index 0000000000..b2fe722d0a --- /dev/null +++ b/documentation/operators/src/nodes/maintenance.md @@ -0,0 +1,357 @@ +# Maintenance + + + + + +### Upgrading your mix node + +Upgrading your node is a two-step process: +* Updating the binary and `~/.nym/mixnodes//config.toml` on your VPS +* Updating the node information in the [mixnet smart contract](../nyx/mixnet-contract.md). **This is the information that is present on the [mixnet explorer](https://explorer.nymtech.net)**. + +#### Step 1: Upgrading your binary +Follow these steps to upgrade your mix node binary and update its config file: +* pause your mix node process. +* replace the existing binary with the newest binary (which you can either [compile yourself](https://nymtech.net/docs/binaries/building-nym.html) or grab from our [releases page](https://github.com/nymtech/nym/releases)). +* re-run `init` with the same values as you used initially. **This will just update the config file, it will not overwrite existing keys**. +* restart your mix node process with the new binary. + +#### Step 2: Updating your node information in the smart contract +Follow these steps to update the information about your mix node which is publically avaliable from the [Nym API](https://validator.nymtech.net/api/swagger/index.html) and information displayed on the [mixnet explorer](https://explorer.nymtech.net). + +You can either do this graphically via the Desktop Wallet, or the CLI. + +#### Updating node information via the Desktop Wallet +* Navigate to the `Bonding` page and click the `Node Settings` link in the top right corner: +\ +![Bonding page](../images/wallet-screenshots/bonding.png) + +* Update the fields in the `Node Settings` page and click `Submit changes to the blockchain`. +\ +![Node Settings Page](../images/wallet-screenshots/node_settings.png) + +#### Updating node information via the CLI +If you want to bond your mix node via the CLI, then check out the [relevant section in the Nym CLI](../../documentation/docs/src/tools/nym-cli.md#upgrade-a-mix-node) docs. + +--- + +## VPS Setup and Automation +### Configure your firewall +The following commands will allow you to set up a firewall using `ufw`. + +``` +# check if you have ufw installed +ufw version + +# if it is not installed, install with +sudo apt install ufw -y + +# enable ufw +sudo ufw enable + +# check the status of the firewall +sudo ufw status +``` + +Finally open your mix node's p2p port, as well as ports for ssh and ports `8000` and `1790` for verloc and measurement pings: + +``` +sudo ufw allow 1789,1790,8000,22/tcp + +# check the status of the firewall +sudo ufw status +``` + +For more information about your mix node's port configuration, check the [mix node port reference table](./mix-node-setup.md#mixnode-port-reference) below. + +### Automating your mix node with tmux and systemd + +It's useful to have the mix node automatically start at system boot time. + +#### tmux + +One way is to use `tmux` shell on top of your current VPS terminal. Tmux is a terminal multiplexer, it allows you to create several terminal windows and panes from a single terminal. Processes started in `tmux` keep running after closing the terminal as long as the given `tmux` window was not terminated. + +Use the following command to get `tmux`. + +Platform|Install Command +---|--- +Arch Linux|`pacman -S tmux` +Debian or Ubuntu|`apt install tmux` +Fedora|`dnf install tmux` +RHEL or CentOS|`yum install tmux` +macOS (using Homebrew|`brew install tmux` +macOS (using MacPorts)|`port install tmux` +openSUSE|`zypper install tmux` + +In case it didn't work for your distribution, see how to build `tmux` from [version control](https://github.com/tmux/tmux#from-version-control). + +**Running tmux** + +No when you installed tmux on your VPS, let's run a mixnode on tmux, which allows you to detach your terminal and let the mixnode run on its own on the VPS. + +* Pause your mixnode +* Start tmux with the command +``` +tmux +``` +* The terminal should stay in the same directory, just the layout changed into tmux default layout. +* Start the mixnode again with a command: +``` +./nym-mixnode run --id +``` +* Now, without closing the tmux window, you can close the whole terminal and the mixnode (and any other process running in tmux) will stay active. +* Next time just start your teminal, ssh into the VPS and run the following command to attach back to your previous session: +``` +tmux attach session +``` +* To see keybinding options of tmux press `ctrl`+`b` and after 1 second `?` + +#### systemd + +Here's a systemd service file to do that: + +```ini +[Unit] +Description=Nym Mixnode ({{mix_node_release_version}}) +StartLimitInterval=350 +StartLimitBurst=10 + +[Service] +User= +LimitNOFILE=65536 +ExecStart=/home///nym-mixnode run --id +KillSignal=SIGINT +Restart=on-failure +RestartSec=30 + +[Install] +WantedBy=multi-user.target +``` + +Put the above file onto your system at `/etc/systemd/system/nym-mixnode.service`. + +Change the `` in `ExecStart` to point at your mix node binary (`nym-mixnode`), and the `` so it is the user you are running as. + +If you have built nym in the `$HOME` directory on your server, and your username is `jetpanther`, then the start command might look like this: + +`ExecStart=/home/jetpanther/nym/target/release/nym-mixnode run --id your-id`. Basically, you want the full `/path/to/nym-mixnode run --id whatever-your-node-id-is` + +Then run: + +``` +systemctl enable nym-mixnode.service +``` + +Start your node: + +``` +service nym-mixnode start +``` + +This will cause your node to start at system boot time. If you restart your machine, the node will come back up automatically. + +You can also do `service nym-mixnode stop` or `service nym-mixnode restart`. + +Note: if you make any changes to your systemd script after you've enabled it, you will need to run: + +``` +systemctl daemon-reload +``` + +This lets your operating system know it's ok to reload the service configuration. + +#### Setting the ulimit + +Linux machines limit how many open files a user is allowed to have. This is called a `ulimit`. + +`ulimit` is 1024 by default on most systems. It needs to be set higher, because mix nodes make and receive a lot of connections to other nodes. + +If you see errors such as: + +``` +Failed to accept incoming connection - Os { code: 24, kind: Other, message: "Too many open files" } +``` + +This means that the operating system is preventing network connections from being made. + +##### Set the ulimit via `systemd` service file + +Query the `ulimit` of your mix node with: + +``` +grep -i "open files" /proc/$(ps -A -o pid,cmd|grep nym-mixnode | grep -v grep |head -n 1 | awk '{print $1}')/limits +``` + +You'll get back the hard and soft limits, which looks something like this: + +``` +Max open files 65536 65536 files +``` + +If your output is **the same as above**, your node will not encounter any `ulimit` related issues. + +However if either value is `1024`, you must raise the limit via the systemd service file. Add the line: + +``` +LimitNOFILE=65536 +``` + +Reload the daemon: + +``` +systemctl daemon-reload +``` + +or execute this as root for system-wide setting of `ulimit`: + +``` +echo "DefaultLimitNOFILE=65535" >> /etc/systemd/system.conf +``` + +Reboot your machine and restart your node. When it comes back, use `cat /proc/$(pidof nym-mixnode)/limits | grep "Max open files"` to make sure the limit has changed to 65535. + +##### Set the ulimit on `non-systemd` based distributions + +In case you chose tmux option for mixnode automatization, see your `ulimit` list by running: + +``` +ulimit -a + +# watch for the output line -n +-n: file descriptors 1024 +``` + +You can change it either by running a command: + +``` +ulimit -u -n 4096 +``` + +or editing `etc/security/conf` and add the following lines: + +``` +# Example hard limit for max opened files +username hard nofile 4096 + +# Example soft limit for max opened files +username soft nofile 4096 +``` + +Then reboot your server and restart your mixnode. + + + + + +#### Step 1: Upgrading your binary +Follow these steps to upgrade your binary and update its config file: +* pause your gateway process. +* replace the existing binary with the newest binary (which you can either compile yourself or grab from our [releases page](https://github.com/nymtech/nym/releases)). +* re-run `init` with the same values as you used initially. **This will just update the config file, it will not overwrite existing keys**. +* restart your gateway process with the new binary. + +> Do **not** use the `upgrade` command: there is a known error with the command that will be fixed in a subsequent release. + +#### Step 2: Updating your node information in the smart contract +Follow these steps to update the information about your node which is publically avaliable from the [Nym API](https://validator.nymtech.net/api/swagger/index.html) and information displayed on the [mixnet explorer](https://explorer.nymtech.net). + +You can either do this graphically via the Desktop Wallet, or the CLI. + +#### Updating node information via the Desktop Wallet +* Navigate to the `Bonding` page and click the `Node Settings` link in the top right corner: +![Bonding page](../images/wallet-screenshots/bonding.png) + +* Update the fields in the `Node Settings` page and click `Submit changes to the blockchain`. +![Node Settings Page](../images/wallet-screenshots/node_settings.png) + +#### Updating node information via the CLI +If you want to bond your mix node via the CLI, then check out the [relevant section in the Nym CLI](../tools/nym-cli.md#upgrade-a-mix-node) docs. + +--- + +## VPS Setup and Automation +### Configure your firewall +Although your gateway is now ready to receive traffic, your server may not be - the following commands will allow you to set up a properly configured firewall using `ufw`: + +``` +# check if you have ufw installed +ufw version +# if it is not installed, install with +sudo apt install ufw -y +# enable ufw +sudo ufw enable +# check the status of the firewall +sudo ufw status +``` + +Finally open your gateway's p2p port, as well as ports for ssh and incoming traffic connections: + +``` +sudo ufw allow 1789,22,9000/tcp +# check the status of the firewall +sudo ufw status +``` + +For more information about your gateway's port configuration, check the [gateway port reference table](#gateway-port-reference) below. + +### Automating your gateway with systemd +Although it's not totally necessary, it's useful to have the gateway automatically start at system boot time. Here's a systemd service file to do that: + +```ini +[Unit] +Description=Nym Gateway ({{platform_release_version}}) +StartLimitInterval=350 +StartLimitBurst=10 + +[Service] +User=nym +LimitNOFILE=65536 +ExecStart=/home/nym/nym-gateway run --id supergateway +KillSignal=SIGINT +Restart=on-failure +RestartSec=30 + +[Install] +WantedBy=multi-user.target +``` + +Put the above file onto your system at `/etc/systemd/system/nym-gateway.service`. + +Change the path in `ExecStart` to point at your gateway binary (`nym-gateway`), and the `User` so it is the user you are running as. + +If you have built nym on your server, and your username is `jetpanther`, then the start command might look like this: + +`ExecStart=/home/jetpanther/nym/target/release/nym-gateway run --id your-id`. Basically, you want the full `/path/to/nym-gateway run --id whatever-your-node-id-is` + + +Then run: + +``` +systemctl enable nym-gateway.service +``` + +Start your node: + +``` +service nym-gateway start +``` + +This will cause your node to start at system boot time. If you restart your machine, the node will come back up automatically. + +You can also do `service nym-gateway stop` or `service nym-gateway restart`. + +Note: if you make any changes to your systemd script after you've enabled it, you will need to run: + +``` +systemctl daemon-reload +``` + +This lets your operating system know it's ok to reload the service configuration. + +## Gateway related Validator API endpoints +Numerous gateway related API endpoints are documented on the Validator API's [Swagger Documentation](https://validator.nymtech.net/api/swagger/index.html). There you can also try out various requests from your broswer, and download the response from the API. Swagger will also show you what commands it is running, so that you can run these from an app or from your CLI if you prefer. diff --git a/documentation/operators/src/nodes/mix-node-setup.md b/documentation/operators/src/nodes/mix-node-setup.md index 723984ff39..68f6bf9090 100644 --- a/documentation/operators/src/nodes/mix-node-setup.md +++ b/documentation/operators/src/nodes/mix-node-setup.md @@ -28,42 +28,11 @@ The `nym-mixnode` binary is currently one point version ahead of the rest of the ## Preliminary steps -There are a couple of steps that need completing before starting to set up your mix node: - -- preparing your [wallet](https://nymtech.net/docs/wallet/desktop-wallet.html) -- requisitioning a VPS (Virtual Private Server) - -### Wallet preparation - -#### Mainnet - -Before you initialise and run your mixnode, head to our [website](https://nymtech.net/download/) and download the Nym wallet for your operating system. If pre-compiled binaries for your operating system aren't available, you can build the wallet yourself with instructions [here](https://nymtech.net/docs/wallet/desktop-wallet.html). - -If you don't already have one, please create a Nym address using the wallet, and fund it with tokens. The minimum amount required to bond a mixnode is 100 `NYM`, but make sure you have a bit more to account for gas costs. - -`NYM` can be purchased via Bity from the wallet itself with BTC or fiat, and is currently present on several [exchanges](https://www.coingecko.com/en/coins/nym#markets). - -> Remember that you can **only** use Cosmos `NYM` tokens to bond your mixnode. You **cannot** use ERC20 representations of `NYM` to run a node. - -#### Sandbox testnet - -Make sure to download a wallet and create an account as outlined above. Then head to our [token faucet](https://faucet.nymtech.net/) and get some tokens to use to bond it. - -### VPS Hardware Specs - -You will need to rent a VPS to run your mix node on. One key reason for this is that your node **must be able to send TCP data using both IPv4 and IPv6** (as other nodes you talk to may use either protocol). - -For the moment, we haven't put a great amount of effort into optimizing concurrency to increase throughput, so don't bother provisioning a beastly server with multiple cores. This will change when we get a chance to start doing performance optimizations in a more serious way. Sphinx packet decryption is CPU-bound, so once we optimize, more fast cores will be better. - -For now, see the below rough specs: - -- Processors: 2 cores are fine. Get the fastest CPUs you can afford. -- RAM: Memory requirements are very low - typically a mix node may use only a few hundred MB of RAM. -- Disks: The mixnodes require no disk space beyond a few bytes for the configuration files. +Make sure you do the preparation listed in the [preliminary steps page](../preliminary-steps.md) before setting up your mixnode. ## Mix node setup -Now that you have built the [codebase](https://nymtech.net/docs/binaries/building-nym.html), set up your [wallet](https://nymtech.net/docs/wallet/desktop-wallet.html), and have a VPS with the `nym-mixnode` binary, you can set up your mix node with the instructions below. +Now that you have built the [codebase](https://nymtech.net/docs/binaries/building-nym.html), set up your [wallet](https://nymtech.net/docs/wallet/desktop-wallet.html), and have a VPS with the `nym-mixnode` binary, you can set up your mix node with the instructions below. To begin, move to `/taget/release` directory from which you run the node commands: diff --git a/documentation/operators/src/preliminary-steps.md b/documentation/operators/src/preliminary-steps.md new file mode 100644 index 0000000000..1c738b34bb --- /dev/null +++ b/documentation/operators/src/preliminary-steps.md @@ -0,0 +1,43 @@ +# Preliminary Steps + +> The Nym mixnode, gateway and network requester binaries were built in the [building nym](https://nymtech.net/docs/binaries/building-nym.html) section. If you haven't yet built Nym and want to run the code, go there first. + +## Preliminary steps + +There are a couple of steps that need completing before starting to set up your mixnode, gateway or a network requester: + +- preparing your [wallet](https://nymtech.net/docs/wallet/desktop-wallet.html) +- requisitioning a VPS (Virtual Private Server) + +### Wallet preparation +#### Mainnet +Before you initialise and run your mixnode, head to our [website](https://nymtech.net/download/) and download the Nym wallet for your operating system. If pre-compiled binaries for your operating system aren't available, you can build the wallet yourself with instructions [here](https://nymtech.net/docs/wallet/desktop-wallet.html). + +If you don't already have one, please create a Nym address using the wallet, and fund it with tokens. The minimum amount required to bond a mixnode is 100 `NYM`, but make sure you have a bit more to account for gas costs. + +`NYM` can be purchased via Bity from the wallet itself with BTC or fiat, and is currently present on several [exchanges](https://www.coingecko.com/en/coins/nym#markets). + +> Remember that you can **only** use Cosmos `NYM` tokens to bond your mixnode. You **cannot** use ERC20 representations of `NYM` to run a node. + + +#### Sandbox testnet +Make sure to download a wallet and create an account as outlined above. Then head to our [token faucet](https://faucet.nymtech.net/) and get some tokens to use to bond it. + +### VPS Hardware Specs +You will need to rent a VPS to run your node on. One key reason for this is that your node **must be able to send TCP data using both IPv4 and IPv6** (as other nodes you talk to may use either protocol). + +For the moment, we haven't put a great amount of effort into optimizing concurrency to increase throughput, so don't bother provisioning a beastly server with multiple cores. This will change when we get a chance to start doing performance optimizations in a more serious way. Sphinx packet decryption is CPU-bound, so once we optimize, more fast cores will be better. + +For now, see the below rough specs: + +- Processors: 2 cores are fine. Get the fastest CPUs you can afford. + +#### For Nym Mixnode + +- RAM: Memory requirements are very low - typically a mix node may use only a few hundred MB of RAM. +- Disks: The mixnodes require no disk space beyond a few bytes for the configuration files. + +#### For Nym Gateway + +- RAM: Memory requirements depend on the amount of users your Gateway will be serving at any one time. If you're just going to be using it yourself, then minimal RAM is fine. **If you're running your Gateway as part of a Service Grant, get something with at least 4GB RAM.** +- Disks: much like the amount of RAM your Gateway could use, the amount of disk space required will vary with the amount of users your Gateway is serving. **If you're running your Gateway as part of a Service Grant, get something with at least 40GB storage.** From 9a76db479ba1a7bfba63f3362f720c734652ed4a Mon Sep 17 00:00:00 2001 From: serinko Date: Fri, 14 Jul 2023 17:17:07 +0200 Subject: [PATCH 206/361] operator-guides: made todo notes --- documentation/operators/src/faq.md | 2 ++ documentation/operators/src/nodes/gateway-setup.md | 8 +++++++- documentation/operators/src/nodes/mix-node-setup.md | 4 ++++ 3 files changed, 13 insertions(+), 1 deletion(-) diff --git a/documentation/operators/src/faq.md b/documentation/operators/src/faq.md index 5e1f5b72ad..e09a78c21c 100644 --- a/documentation/operators/src/faq.md +++ b/documentation/operators/src/faq.md @@ -10,5 +10,7 @@ Hi, do you know if it's on the roadmap for network nodes (requesters, socks5 cli you should be able to switch gateway (and overwrite keys) using —force-register +make a note that the keys will be overwritten in bold when you're noting it in the operators guides + ---> diff --git a/documentation/operators/src/nodes/gateway-setup.md b/documentation/operators/src/nodes/gateway-setup.md index 591cd3d1a9..ecd04d1005 100644 --- a/documentation/operators/src/nodes/gateway-setup.md +++ b/documentation/operators/src/nodes/gateway-setup.md @@ -1,3 +1,7 @@ + # Gateways > The Nym gateway was built in the [building nym](../binaries/building-nym.md) section. If you haven't yet built Nym and want to run the code, go there first. @@ -153,9 +157,11 @@ You can either do this graphically via the Desktop Wallet, or the CLI. #### Updating node information via the Desktop Wallet * Navigate to the `Bonding` page and click the `Node Settings` link in the top right corner: +/ ![Bonding page](../images/wallet-screenshots/bonding.png) * Update the fields in the `Node Settings` page and click `Submit changes to the blockchain`. +/ ![Node Settings Page](../images/wallet-screenshots/node_settings.png) #### Updating node information via the CLI @@ -244,7 +250,7 @@ This lets your operating system know it's ok to reload the service configuration Numerous gateway related API endpoints are documented on the Validator API's [Swagger Documentation](https://validator.nymtech.net/api/swagger/index.html). There you can also try out various requests from your broswer, and download the response from the API. Swagger will also show you what commands it is running, so that you can run these from an app or from your CLI if you prefer. ## Ports -All gateway specific port configuration can be found in `$HOME/.nym/gateways//config/config.toml`. If you do edit any port configs, remember to restart your gateway. +All gateway specific port configuration can be found in `$HOME/.nym/gateways//config/config.toml`. If you do edit any port configs, remember to restart your gateway. ### Gateway port reference | Default port | Use | diff --git a/documentation/operators/src/nodes/mix-node-setup.md b/documentation/operators/src/nodes/mix-node-setup.md index 68f6bf9090..dc27005feb 100644 --- a/documentation/operators/src/nodes/mix-node-setup.md +++ b/documentation/operators/src/nodes/mix-node-setup.md @@ -10,6 +10,10 @@ TODO - [x] Add second console output to Initialising your mixnode manually as the automatized is not working - [ ] Point users to understand the requirements for becoming a part of the active set. - [ ] Include how to move a mix node from one VPS to another while keeping the delegation etc (ie init a new mix node and move `/home/nym/.nym/mixnodes/data/*` there ?) + - copy that dir over to the new vps + - re-run init - remember that init doesn't overwrite existing keys - to generate a config with the new listening address etc + - change the node info via the wallet + - Since otherwise their keys will point to the old IP address in the smart contract, and their node will (a) not be able to be connected to, and (b) fail uptime checks - [ ] Explain more about self hosted nodes in the 6.1 not only as a troubleshooting ---> From 389ccffb631b537eed19ffe0196b4a63877f3a33 Mon Sep 17 00:00:00 2001 From: serinko <97586125+serinko@users.noreply.github.com> Date: Fri, 14 Jul 2023 19:21:25 +0200 Subject: [PATCH 207/361] operators-guide: moved repeating to maintenance.md, shortened and cleaned the existing guides --- .../operators/src/nodes/gateway-setup.md | 126 +------ .../operators/src/nodes/maintenance.md | 249 ++++++------ .../operators/src/nodes/mix-node-setup.md | 356 +----------------- 3 files changed, 142 insertions(+), 589 deletions(-) diff --git a/documentation/operators/src/nodes/gateway-setup.md b/documentation/operators/src/nodes/gateway-setup.md index ecd04d1005..eaacfaf52a 100644 --- a/documentation/operators/src/nodes/gateway-setup.md +++ b/documentation/operators/src/nodes/gateway-setup.md @@ -1,6 +1,6 @@ # Gateways @@ -133,127 +133,7 @@ The `run` command starts the gateway: ``` ./nym-gateway run --id ``` +## Maintenance -### Upgrading your gateway -Upgrading your node is a two-step process: -* Updating the binary and `config.toml` on your VPS -* Updating the node information in the [mixnet smart contract](../nyx/mixnet-contract.md). **This is the information that is present on the [mixnet explorer](https://explorer.nymtech.net)**. +For gateway upgrade, firewall setup, port configuration, API endpoints, VPS suggestions, automation and more, see the [maintenance page](./maintenance.md) ->These instructions are specifically regarding upgrading your gateway binary from one version to another. If you want to change node information such as the listening port, you can do this by clicking the `node settings` tab in the `Bonding` page of the wallet. - -#### Step 1: Upgrading your binary -Follow these steps to upgrade your binary and update its config file: -* pause your gateway process. -* replace the existing binary with the newest binary (which you can either compile yourself or grab from our [releases page](https://github.com/nymtech/nym/releases)). -* re-run `init` with the same values as you used initially. **This will just update the config file, it will not overwrite existing keys**. -* restart your gateway process with the new binary. - -> Do **not** use the `upgrade` command: there is a known error with the command that will be fixed in a subsequent release. - -#### Step 2: Updating your node information in the smart contract -Follow these steps to update the information about your node which is publically avaliable from the [Nym API](https://validator.nymtech.net/api/swagger/index.html) and information displayed on the [mixnet explorer](https://explorer.nymtech.net). - -You can either do this graphically via the Desktop Wallet, or the CLI. - -#### Updating node information via the Desktop Wallet -* Navigate to the `Bonding` page and click the `Node Settings` link in the top right corner: -/ -![Bonding page](../images/wallet-screenshots/bonding.png) - -* Update the fields in the `Node Settings` page and click `Submit changes to the blockchain`. -/ -![Node Settings Page](../images/wallet-screenshots/node_settings.png) - -#### Updating node information via the CLI -If you want to bond your mix node via the CLI, then check out the [relevant section in the Nym CLI](../tools/nym-cli.md#upgrade-a-mix-node) docs. - -## VPS Setup and Automation -### Configure your firewall -Although your gateway is now ready to receive traffic, your server may not be - the following commands will allow you to set up a properly configured firewall using `ufw`: - -``` -# check if you have ufw installed -ufw version -# if it is not installed, install with -sudo apt install ufw -y -# enable ufw -sudo ufw enable -# check the status of the firewall -sudo ufw status -``` - -Finally open your gateway's p2p port, as well as ports for ssh and incoming traffic connections: - -``` -sudo ufw allow 1789,22,9000/tcp -# check the status of the firewall -sudo ufw status -``` - -For more information about your gateway's port configuration, check the [gateway port reference table](#gateway-port-reference) below. - -### Automating your gateway with systemd -Although it's not totally necessary, it's useful to have the gateway automatically start at system boot time. Here's a systemd service file to do that: - -```ini -[Unit] -Description=Nym Gateway ({{platform_release_version}}) -StartLimitInterval=350 -StartLimitBurst=10 - -[Service] -User=nym -LimitNOFILE=65536 -ExecStart=/home/nym/nym-gateway run --id supergateway -KillSignal=SIGINT -Restart=on-failure -RestartSec=30 - -[Install] -WantedBy=multi-user.target -``` - -Put the above file onto your system at `/etc/systemd/system/nym-gateway.service`. - -Change the path in `ExecStart` to point at your gateway binary (`nym-gateway`), and the `User` so it is the user you are running as. - -If you have built nym on your server, and your username is `jetpanther`, then the start command might look like this: - -`ExecStart=/home/jetpanther/nym/target/release/nym-gateway run --id your-id`. Basically, you want the full `/path/to/nym-gateway run --id whatever-your-node-id-is` - - -Then run: - -``` -systemctl enable nym-gateway.service -``` - -Start your node: - -``` -service nym-gateway start -``` - -This will cause your node to start at system boot time. If you restart your machine, the node will come back up automatically. - -You can also do `service nym-gateway stop` or `service nym-gateway restart`. - -Note: if you make any changes to your systemd script after you've enabled it, you will need to run: - -``` -systemctl daemon-reload -``` - -This lets your operating system know it's ok to reload the service configuration. - -## Gateway related Validator API endpoints -Numerous gateway related API endpoints are documented on the Validator API's [Swagger Documentation](https://validator.nymtech.net/api/swagger/index.html). There you can also try out various requests from your broswer, and download the response from the API. Swagger will also show you what commands it is running, so that you can run these from an app or from your CLI if you prefer. - -## Ports -All gateway specific port configuration can be found in `$HOME/.nym/gateways//config/config.toml`. If you do edit any port configs, remember to restart your gateway. - -### Gateway port reference -| Default port | Use | -|--------------|---------------------------| -| 1789 | Listen for Mixnet traffic | -| 9000 | Listen for Client traffic | diff --git a/documentation/operators/src/nodes/maintenance.md b/documentation/operators/src/nodes/maintenance.md index b2fe722d0a..583bb6aa47 100644 --- a/documentation/operators/src/nodes/maintenance.md +++ b/documentation/operators/src/nodes/maintenance.md @@ -1,47 +1,46 @@ # Maintenance - +## Upgrading your node - - -### Upgrading your mix node +> The process is the similar for mixnode, gateway and network requester. In the following steps we use a placeholder `` in the commands, please change it for the type of node you want to upgrade. Any particularities for the given type of node are included. Upgrading your node is a two-step process: -* Updating the binary and `~/.nym/mixnodes//config.toml` on your VPS -* Updating the node information in the [mixnet smart contract](../nyx/mixnet-contract.md). **This is the information that is present on the [mixnet explorer](https://explorer.nymtech.net)**. +* Updating the binary and `~/.nym///config.toml` on your VPS +* Updating the node information in the [mixnet smart contract](https://nymtech.net/docs/nyx/mixnet-contract.html). **This is the information that is present on the [mixnet explorer](https://explorer.nymtech.net)**. -#### Step 1: Upgrading your binary +### Step 1: Upgrading your binary Follow these steps to upgrade your mix node binary and update its config file: * pause your mix node process. * replace the existing binary with the newest binary (which you can either [compile yourself](https://nymtech.net/docs/binaries/building-nym.html) or grab from our [releases page](https://github.com/nymtech/nym/releases)). * re-run `init` with the same values as you used initially. **This will just update the config file, it will not overwrite existing keys**. * restart your mix node process with the new binary. -#### Step 2: Updating your node information in the smart contract -Follow these steps to update the information about your mix node which is publically avaliable from the [Nym API](https://validator.nymtech.net/api/swagger/index.html) and information displayed on the [mixnet explorer](https://explorer.nymtech.net). +### Step 2: Updating your node information in the smart contract +Follow these steps to update the information about your `` which is publicly available from the [Nym API](https://validator.nymtech.net/api/swagger/index.html) and information displayed on the [mixnet explorer](https://explorer.nymtech.net). You can either do this graphically via the Desktop Wallet, or the CLI. -#### Updating node information via the Desktop Wallet -* Navigate to the `Bonding` page and click the `Node Settings` link in the top right corner: -\ +### Updating node information via the Desktop Wallet +* Navigate to the `Bonding` page and click the `Node Settings` link in the top right corner: + ![Bonding page](../images/wallet-screenshots/bonding.png) -* Update the fields in the `Node Settings` page and click `Submit changes to the blockchain`. -\ +* Update the fields in the `Node Settings` page and click `Submit changes to the blockchain`. + ![Node Settings Page](../images/wallet-screenshots/node_settings.png) -#### Updating node information via the CLI -If you want to bond your mix node via the CLI, then check out the [relevant section in the Nym CLI](../../documentation/docs/src/tools/nym-cli.md#upgrade-a-mix-node) docs. +### Updating node information via the CLI +If you want to bond your `` via the CLI, then check out the [relevant section in the Nym CLI](../../documentation/docs/src/tools/nym-cli.md#upgrade-a-mix-node) docs. ---- ## VPS Setup and Automation ### Configure your firewall -The following commands will allow you to set up a firewall using `ufw`. +Although your `` is now ready to recieve traffic, your server may not be. The following commands will allow you to set up a firewall using `ufw`. ``` # check if you have ufw installed @@ -57,20 +56,24 @@ sudo ufw enable sudo ufw status ``` -Finally open your mix node's p2p port, as well as ports for ssh and ports `8000` and `1790` for verloc and measurement pings: +Finally open your `` p2p port, as well as ports for ssh and ports for verloc and measurement pings: ``` +# for mixnode sudo ufw allow 1789,1790,8000,22/tcp +# for gateway +sudo ufw allow 1789,22,9000/tcp + # check the status of the firewall sudo ufw status ``` -For more information about your mix node's port configuration, check the [mix node port reference table](./mix-node-setup.md#mixnode-port-reference) below. +For more information about your mix node's port configuration, check the [mix node port reference table](./mix-node-setup.md#mixnode-port-reference) or [gateway port reference table](https://nymtech.net/docs/nodes/gateway-setup.html#gateway-port-reference) below. -### Automating your mix node with tmux and systemd +### Automating your node with tmux and systemd -It's useful to have the mix node automatically start at system boot time. +Although it’s not totally necessary, it's useful to have the mix node automatically start at system boot time. #### tmux @@ -92,22 +95,22 @@ In case it didn't work for your distribution, see how to build `tmux` from [vers **Running tmux** -No when you installed tmux on your VPS, let's run a mixnode on tmux, which allows you to detach your terminal and let the mixnode run on its own on the VPS. +No when you installed tmux on your VPS, let's run a mixnode on tmux, which allows you to detach your terminal and let your `` run on its own on the VPS. -* Pause your mixnode +* Pause your `` * Start tmux with the command ``` tmux ``` -* The terminal should stay in the same directory, just the layout changed into tmux default layout. -* Start the mixnode again with a command: +* The tmux terminal should open in the same working directory, just the layout changed into tmux default layout. +* Start the `` again with a command: ``` -./nym-mixnode run --id +./ run --id ``` -* Now, without closing the tmux window, you can close the whole terminal and the mixnode (and any other process running in tmux) will stay active. +* Now, without closing the tmux window, you can close the whole terminal and the `` (and any other process running in tmux) will stay active. * Next time just start your teminal, ssh into the VPS and run the following command to attach back to your previous session: ``` -tmux attach session +tmux attach-session ``` * To see keybinding options of tmux press `ctrl`+`b` and after 1 second `?` @@ -115,6 +118,8 @@ tmux attach session Here's a systemd service file to do that: +For mixnode: + ```ini [Unit] Description=Nym Mixnode ({{mix_node_release_version}}) @@ -133,29 +138,60 @@ RestartSec=30 WantedBy=multi-user.target ``` -Put the above file onto your system at `/etc/systemd/system/nym-mixnode.service`. +* Put the above file onto your system at `/etc/systemd/system/nym-mixnode.service`. -Change the `` in `ExecStart` to point at your mix node binary (`nym-mixnode`), and the `` so it is the user you are running as. +For gateway: -If you have built nym in the `$HOME` directory on your server, and your username is `jetpanther`, then the start command might look like this: +```ini +[Unit] +Description=Nym Gateway ({{platform_release_version}}) +StartLimitInterval=350 +StartLimitBurst=10 -`ExecStart=/home/jetpanther/nym/target/release/nym-mixnode run --id your-id`. Basically, you want the full `/path/to/nym-mixnode run --id whatever-your-node-id-is` +[Service] +User=nym +LimitNOFILE=65536 +ExecStart=/home///nym-gateway run --id +KillSignal=SIGINT +Restart=on-failure +RestartSec=30 + +[Install] +WantedBy=multi-user.target +``` + +* Put the above file onto your system at `/etc/systemd/system/nym-gateway.service`. + +Change the `` in `ExecStart` to point at your `` binary (`nym-mixnode` or `nym-gateway), and the `` so it is the user you are running as. + +If you have built nym in the `$HOME` directory on your server, and your username is `jetpanther`, then the start command for nym mixnode might look like this: + +`ExecStart=/home/jetpanther/nym/target/release/nym-mixnode run --id `. Basically, you want the full `/path/to/nym-mixnode run --id whatever-your-node-id-is` Then run: ``` +# for mixnode systemctl enable nym-mixnode.service + +# for gateway +systemctl enable nym-gateway.service ``` Start your node: ``` +# for mixnode service nym-mixnode start + +# for gateway +service nym-gateway start + ``` This will cause your node to start at system boot time. If you restart your machine, the node will come back up automatically. -You can also do `service nym-mixnode stop` or `service nym-mixnode restart`. +You can also do `service stop` or `service restart`. Note: if you make any changes to your systemd script after you've enabled it, you will need to run: @@ -165,7 +201,7 @@ systemctl daemon-reload This lets your operating system know it's ok to reload the service configuration. -#### Setting the ulimit +### Setting the ulimit Linux machines limit how many open files a user is allowed to have. This is called a `ulimit`. @@ -179,7 +215,9 @@ Failed to accept incoming connection - Os { code: 24, kind: Other, message: "Too This means that the operating system is preventing network connections from being made. -##### Set the ulimit via `systemd` service file +#### Set the ulimit via `systemd` service file + +The ulimit setup is relevant for maintenance of nym mixnode only. Query the `ulimit` of your mix node with: @@ -215,7 +253,7 @@ echo "DefaultLimitNOFILE=65535" >> /etc/systemd/system.conf Reboot your machine and restart your node. When it comes back, use `cat /proc/$(pidof nym-mixnode)/limits | grep "Max open files"` to make sure the limit has changed to 65535. -##### Set the ulimit on `non-systemd` based distributions +#### Set the ulimit on `non-systemd` based distributions In case you chose tmux option for mixnode automatization, see your `ulimit` list by running: @@ -244,114 +282,89 @@ username soft nofile 4096 Then reboot your server and restart your mixnode. +## Virtual IPs and hosting via Google & AWS +For true internet decentralization we encourage operators to use diverse VPS providers instead of the largest companies offering such services. If for some reasons you have already running AWS or Google and want to setup a `` there, please read the following. +On some services (AWS, Google, etc) the machine's available bind address is not the same as the public IP address. In this case, bind `--host` to the local machine address returned by `$(curl ifconfig.me)`, but also specify `--announce-host` with the public IP. Please make sure that you pass the correct, routable `--announce-host`. - - -#### Step 1: Upgrading your binary -Follow these steps to upgrade your binary and update its config file: -* pause your gateway process. -* replace the existing binary with the newest binary (which you can either compile yourself or grab from our [releases page](https://github.com/nymtech/nym/releases)). -* re-run `init` with the same values as you used initially. **This will just update the config file, it will not overwrite existing keys**. -* restart your gateway process with the new binary. - -> Do **not** use the `upgrade` command: there is a known error with the command that will be fixed in a subsequent release. - -#### Step 2: Updating your node information in the smart contract -Follow these steps to update the information about your node which is publically avaliable from the [Nym API](https://validator.nymtech.net/api/swagger/index.html) and information displayed on the [mixnet explorer](https://explorer.nymtech.net). - -You can either do this graphically via the Desktop Wallet, or the CLI. - -#### Updating node information via the Desktop Wallet -* Navigate to the `Bonding` page and click the `Node Settings` link in the top right corner: -![Bonding page](../images/wallet-screenshots/bonding.png) - -* Update the fields in the `Node Settings` page and click `Submit changes to the blockchain`. -![Node Settings Page](../images/wallet-screenshots/node_settings.png) - -#### Updating node information via the CLI -If you want to bond your mix node via the CLI, then check out the [relevant section in the Nym CLI](../tools/nym-cli.md#upgrade-a-mix-node) docs. - ---- - -## VPS Setup and Automation -### Configure your firewall -Although your gateway is now ready to receive traffic, your server may not be - the following commands will allow you to set up a properly configured firewall using `ufw`: +For example, on a Google machine, you may see the following output from the `ifconfig` command: ``` -# check if you have ufw installed -ufw version -# if it is not installed, install with -sudo apt install ufw -y -# enable ufw -sudo ufw enable -# check the status of the firewall -sudo ufw status +ens4: flags=4163 mtu 1460 + inet 10.126.5.7 netmask 255.255.255.255 broadcast 0.0.0.0 + ... ``` -Finally open your gateway's p2p port, as well as ports for ssh and incoming traffic connections: +The `ens4` interface has the IP `10.126.5.7`. But this isn't the public IP of the machine, it's the IP of the machine on Google's internal network. Google uses virtual routing, so the public IP of this machine is something else, maybe `36.68.243.18`. +`./nym-mixnode init --host 10.126.5.7`, initalises the mix node, but no packets will be routed because `10.126.5.7` is not on the public internet. + +Trying `nym-mixnode init --host 36.68.243.18`, you'll get back a startup error saying `AddrNotAvailable`. This is because the mix node doesn't know how to bind to a host that's not in the output of `ifconfig`. + +The right thing to do in this situation is to init with a command: ``` -sudo ufw allow 1789,22,9000/tcp -# check the status of the firewall -sudo ufw status +./nym-mixnode init --host 10.126.5.7 --announce-host 36.68.243.18 ``` -For more information about your gateway's port configuration, check the [gateway port reference table](#gateway-port-reference) below. +This will bind the mix node to the available host `10.126.5.7`, but announce the mix node's public IP to the directory server as `36.68.243.18`. It's up to you as a node operator to ensure that your public and private IPs match up properly. -### Automating your gateway with systemd -Although it's not totally necessary, it's useful to have the gateway automatically start at system boot time. Here's a systemd service file to do that: +To find the right IP configuration, contact your VPS provider for support. -```ini -[Unit] -Description=Nym Gateway ({{platform_release_version}}) -StartLimitInterval=350 -StartLimitBurst=10 +## Nym API (previously 'Validator API') endpoints +Numerous API endpoints are documented on the Nym API (previously 'Validator API')'s [Swagger Documentation](https://validator.nymtech.net/api/swagger/index.html). There you can also try out various requests from your browser, and download the response from the API. Swagger will also show you what commands it is running, so that you can run these from an app or from your CLI if you prefer. -[Service] -User=nym -LimitNOFILE=65536 -ExecStart=/home/nym/nym-gateway run --id supergateway -KillSignal=SIGINT -Restart=on-failure -RestartSec=30 +### Mix node Reward Estimation API endpoint -[Install] -WantedBy=multi-user.target -``` +The Reward Estimation API endpoint allows mixnode operators to estimate the rewards they could earn for running a Nym mixnode with a specific `MIX_ID`. -Put the above file onto your system at `/etc/systemd/system/nym-gateway.service`. +> The `` can be found in the "Mix ID" column of the [Network Explorer](https://explorer.nymtech.net/network-components/mixnodes/active). -Change the path in `ExecStart` to point at your gateway binary (`nym-gateway`), and the `User` so it is the user you are running as. +The endpoint is a particularly common for mixnode operators as it can provide an estimate of potential earnings based on factors such as the amount of traffic routed through the mixnode, the quality of the mixnode's performance, and the overall demand for mixnodes in the network. This information can be useful for mixnode operators in deciding whether or not to run a mix node and in optimizing its operations for maximum profitability. -If you have built nym on your server, and your username is `jetpanther`, then the start command might look like this: - -`ExecStart=/home/jetpanther/nym/target/release/nym-gateway run --id your-id`. Basically, you want the full `/path/to/nym-gateway run --id whatever-your-node-id-is` - - -Then run: +Using this API endpoint returns information about the Reward Estimation: ``` -systemctl enable nym-gateway.service +/status/mixnode//reward-estimation ``` -Start your node: +Query Response: ``` -service nym-gateway start + "estimation": { + "total_node_reward": "942035.916721770541325331", + "operator": "161666.263307386408152071", + "delegates": "780369.65341438413317326", + "operating_cost": "54444.444444444444444443" + }, ``` -This will cause your node to start at system boot time. If you restart your machine, the node will come back up automatically. +> The unit of value is measured in `uNYM`. -You can also do `service nym-gateway stop` or `service nym-gateway restart`. +- `estimated_total_node_reward` - An estimate of the total amount of rewards that a particular mix node can expect to receive during the current epoch. This value is calculated by the Nym Validator based on a number of factors, including the current state of the network, the number of mix nodes currently active in the network, and the amount of network traffic being processed by the mix node. -Note: if you make any changes to your systemd script after you've enabled it, you will need to run: +- `estimated_operator_reward` - An estimate of the amount of rewards that a particular mix node operator can expect to receive. This value is calculated by the Nym Validator based on a number of factors, including the amount of traffic being processed by the mix node, the quality of service provided by the mix node, and the operator's stake in the network. -``` -systemctl daemon-reload -``` +- `estimated_delegators_reward` - An estimate of the amount of rewards that mix node delegators can expect to receive individually. This value is calculated by the Nym Validator based on a number of factors, including the amount of traffic being processed by the mix node, the quality of service provided by the mix node, and the delegator's stake in the network. -This lets your operating system know it's ok to reload the service configuration. +- `estimated_node_profit` - An estimate of the profit that a particular mix node operator can expect to earn. This value is calculated by subtracting the mix node operator's `operating_costs` from their `estimated_operator_reward` for the current epoch. -## Gateway related Validator API endpoints -Numerous gateway related API endpoints are documented on the Validator API's [Swagger Documentation](https://validator.nymtech.net/api/swagger/index.html). There you can also try out various requests from your broswer, and download the response from the API. Swagger will also show you what commands it is running, so that you can run these from an app or from your CLI if you prefer. +- `estimated_operator_cost` - An estimate of the total cost that a particular mix node operator can expect to incur for their participation. This value is calculated by the Nym Validator based on a number of factors, including the cost of running a mix node, such as server hosting fees, and other expenses associated with operating the mix node. + +## Ports +All ``-specific port configuration can be found in `$HOME/.nym///config/config.toml`. If you do edit any port configs, remember to restart your mix node. + +### Mix node port reference +| Default port | Use | +| ------------ | ------------------------- | +| `1789` | Listen for mixnet traffic | +| `1790` | Listen for VerLoc traffic | +| `8000` | Metrics http API endpoint | + + +### Gateway port reference +| Default port | Use | +|--------------|---------------------------| +| `1789` | Listen for Mixnet traffic | +| `9000` | Listen for Client traffic | + +/ diff --git a/documentation/operators/src/nodes/mix-node-setup.md b/documentation/operators/src/nodes/mix-node-setup.md index dc27005feb..8690b17806 100644 --- a/documentation/operators/src/nodes/mix-node-setup.md +++ b/documentation/operators/src/nodes/mix-node-setup.md @@ -1,6 +1,6 @@ # Mix Nodes @@ -198,263 +199,16 @@ Keep reading to find out more about configuration options or troubleshooting if Have a look at the saved configuration files in `$HOME/.nym/mixnodes/` to see more configuration options. -### Describe your mix node (optional) +## Node Description (optional) In order to easily identify your node via human-readable information later on in the development of the testnet when delegated staking is implemented, you can `describe` your mixnode with the following command: ``` ./nym-mixnode describe --id ``` +Node description is a short text that describes your node. It is displayed in the `./nym-mixnode list` command and in the `./nym-mixnode node-details --id ` command. It also shows up in the node explorer to let people know what your node is about and link to your website. -> Remember to restart your mix node process in order for the new description to be propagated. - -### Upgrading your mix node - -Upgrading your node is a two-step process: -* Updating the binary and `~/.nym/mixnodes//config.toml` on your VPS -* Updating the node information in the [mixnet smart contract](../nyx/mixnet-contract.md). **This is the information that is present on the [mixnet explorer](https://explorer.nymtech.net)**. - -#### Step 1: Upgrading your binary -Follow these steps to upgrade your mix node binary and update its config file: -* pause your mix node process. -* replace the existing binary with the newest binary (which you can either [compile yourself](https://nymtech.net/docs/binaries/building-nym.html) or grab from our [releases page](https://github.com/nymtech/nym/releases)). -* re-run `init` with the same values as you used initially. **This will just update the config file, it will not overwrite existing keys**. -* restart your mix node process with the new binary. - -#### Step 2: Updating your node information in the smart contract -Follow these steps to update the information about your mix node which is publically avaliable from the [Nym API](https://validator.nymtech.net/api/swagger/index.html) and information displayed on the [mixnet explorer](https://explorer.nymtech.net). - -You can either do this graphically via the Desktop Wallet, or the CLI. - -#### Updating node information via the Desktop Wallet -* Navigate to the `Bonding` page and click the `Node Settings` link in the top right corner: -\ -![Bonding page](../images/wallet-screenshots/bonding.png) - -* Update the fields in the `Node Settings` page and click `Submit changes to the blockchain`. -\ -![Node Settings Page](../images/wallet-screenshots/node_settings.png) - -#### Updating node information via the CLI -If you want to bond your mix node via the CLI, then check out the [relevant section in the Nym CLI](../../documentation/docs/src/tools/nym-cli.md#upgrade-a-mix-node) docs. - -### Displaying mix node information - -You can always check the details of your mix node with the `node-details` command: - -``` -./nym-mixnode node-details --id -``` - -## VPS Setup and Automation -### Configure your firewall -The following commands will allow you to set up a firewall using `ufw`. - -``` -# check if you have ufw installed -ufw version - -# if it is not installed, install with -sudo apt install ufw -y - -# enable ufw -sudo ufw enable - -# check the status of the firewall -sudo ufw status -``` - -Finally open your mix node's p2p port, as well as ports for ssh and ports `8000` and `1790` for verloc and measurement pings: - -``` -sudo ufw allow 1789,1790,8000,22/tcp - -# check the status of the firewall -sudo ufw status -``` - -For more information about your mix node's port configuration, check the [mix node port reference table](./mix-node-setup.md#mixnode-port-reference) below. - -### Automating your mix node with tmux and systemd - -It's useful to have the mix node automatically start at system boot time. - -#### tmux - -One way is to use `tmux` shell on top of your current VPS terminal. Tmux is a terminal multiplexer, it allows you to create several terminal windows and panes from a single terminal. Processes started in `tmux` keep running after closing the terminal as long as the given `tmux` window was not terminated. - -Use the following command to get `tmux`. - -Platform|Install Command ----|--- -Arch Linux|`pacman -S tmux` -Debian or Ubuntu|`apt install tmux` -Fedora|`dnf install tmux` -RHEL or CentOS|`yum install tmux` -macOS (using Homebrew|`brew install tmux` -macOS (using MacPorts)|`port install tmux` -openSUSE|`zypper install tmux` - -In case it didn't work for your distribution, see how to build `tmux` from [version control](https://github.com/tmux/tmux#from-version-control). - -**Running tmux** - -No when you installed tmux on your VPS, let's run a mixnode on tmux, which allows you to detach your terminal and let the mixnode run on its own on the VPS. - -* Pause your mixnode -* Start tmux with the command -``` -tmux -``` -* The terminal should stay in the same directory, just the layout changed into tmux default layout. -* Start the mixnode again with a command: -``` -./nym-mixnode run --id -``` -* Now, without closing the tmux window, you can close the whole terminal and the mixnode (and any other process running in tmux) will stay active. -* Next time just start your teminal, ssh into the VPS and run the following command to attach back to your previous session: -``` -tmux attach session -``` -* To see keybinding options of tmux press `ctrl`+`b` and after 1 second `?` - -#### systemd - -Here's a systemd service file to do that: - -```ini -[Unit] -Description=Nym Mixnode ({{mix_node_release_version}}) -StartLimitInterval=350 -StartLimitBurst=10 - -[Service] -User= -LimitNOFILE=65536 -ExecStart=/home///nym-mixnode run --id -KillSignal=SIGINT -Restart=on-failure -RestartSec=30 - -[Install] -WantedBy=multi-user.target -``` - -Put the above file onto your system at `/etc/systemd/system/nym-mixnode.service`. - -Change the `` in `ExecStart` to point at your mix node binary (`nym-mixnode`), and the `` so it is the user you are running as. - -If you have built nym in the `$HOME` directory on your server, and your username is `jetpanther`, then the start command might look like this: - -`ExecStart=/home/jetpanther/nym/target/release/nym-mixnode run --id your-id`. Basically, you want the full `/path/to/nym-mixnode run --id whatever-your-node-id-is` - -Then run: - -``` -systemctl enable nym-mixnode.service -``` - -Start your node: - -``` -service nym-mixnode start -``` - -This will cause your node to start at system boot time. If you restart your machine, the node will come back up automatically. - -You can also do `service nym-mixnode stop` or `service nym-mixnode restart`. - -Note: if you make any changes to your systemd script after you've enabled it, you will need to run: - -``` -systemctl daemon-reload -``` - -This lets your operating system know it's ok to reload the service configuration. - -#### Setting the ulimit - -Linux machines limit how many open files a user is allowed to have. This is called a `ulimit`. - -`ulimit` is 1024 by default on most systems. It needs to be set higher, because mix nodes make and receive a lot of connections to other nodes. - -If you see errors such as: - -``` -Failed to accept incoming connection - Os { code: 24, kind: Other, message: "Too many open files" } -``` - -This means that the operating system is preventing network connections from being made. - -##### Set the ulimit via `systemd` service file - -Query the `ulimit` of your mix node with: - -``` -grep -i "open files" /proc/$(ps -A -o pid,cmd|grep nym-mixnode | grep -v grep |head -n 1 | awk '{print $1}')/limits -``` - -You'll get back the hard and soft limits, which looks something like this: - -``` -Max open files 65536 65536 files -``` - -If your output is **the same as above**, your node will not encounter any `ulimit` related issues. - -However if either value is `1024`, you must raise the limit via the systemd service file. Add the line: - -``` -LimitNOFILE=65536 -``` - -Reload the daemon: - -``` -systemctl daemon-reload -``` - -or execute this as root for system-wide setting of `ulimit`: - -``` -echo "DefaultLimitNOFILE=65535" >> /etc/systemd/system.conf -``` - -Reboot your machine and restart your node. When it comes back, use `cat /proc/$(pidof nym-mixnode)/limits | grep "Max open files"` to make sure the limit has changed to 65535. - -##### Set the ulimit on `non-systemd` based distributions - -In case you chose tmux option for mixnode automatization, see your `ulimit` list by running: - -``` -ulimit -a - -# watch for the output line -n --n: file descriptors 1024 -``` - -You can change it either by running a command: - -``` -ulimit -u -n 4096 -``` - -or editing `etc/security/conf` and add the following lines: - -``` -# Example hard limit for max opened files -username hard nofile 4096 - -# Example soft limit for max opened files -username soft nofile 4096 -``` - -Then reboot your server and restart your mixnode. - -## Node Description -Node description is a short text that describes your node. It is displayed in the `./nym-mixnode list` command and in the `./nym-mixnode node-details` command. It also shows up in the node explorer to let people know what your node is about and link to your website. - -To set your node description, create a file called `description.toml` and put it in the same directory as your `config.toml` file (`~/.nym/mixnodes//description.toml`). The file should look like this example: +You can set your node description, by creating a file called `description.toml` and put it in the same directory as your `config.toml` file (`~/.nym/mixnodes//description.toml`). The file should look like this example: ```toml name = "Winston Smith" @@ -463,7 +217,7 @@ link = "https://nymtech.net" location = "Giza, Egypt" ``` -You will need to restart your node for the changes to take effect. +> Remember to restart your mix node process in order for the new description to be propagated. ## Node Families @@ -573,101 +327,7 @@ For more details see [Troubleshooting FAQ](../nodes/troubleshooting.md) -### Virtual IPs and hosting via Google & AWS -For tru internet decentralization we encourage operators to use diverse VPS providers instead of the largest companies offering such services. If for some reasons you have already running AWS or Google and want to setup a mixnode there, please read the following. +## Maintenance -On some services (AWS, Google, etc) the machine's available bind address is not the same as the public IP address. In this case, bind `--host` to the local machine address returned by `$(curl ifconfig.me)`, but also specify `--announce-host` with the public IP. Please make sure that you pass the correct, routable `--announce-host`. - -For example, on a Google machine, you may see the following output from the `ifconfig` command: - -``` -ens4: flags=4163 mtu 1460 - inet 10.126.5.7 netmask 255.255.255.255 broadcast 0.0.0.0 - ... -``` - -The `ens4` interface has the IP `10.126.5.7`. But this isn't the public IP of the machine, it's the IP of the machine on Google's internal network. Google uses virtual routing, so the public IP of this machine is something else, maybe `36.68.243.18`. - -`./nym-mixnode init --host 10.126.5.7`, initalises the mix node, but no packets will be routed because `10.126.5.7` is not on the public internet. - -Trying `nym-mixnode init --host 36.68.243.18`, you'll get back a startup error saying `AddrNotAvailable`. This is because the mix node doesn't know how to bind to a host that's not in the output of `ifconfig`. - -The right thing to do in this situation is to init with a command: -``` -./nym-mixnode init --host 10.126.5.7 --announce-host 36.68.243.18 -``` - -This will bind the mix node to the available host `10.126.5.7`, but announce the mix node's public IP to the directory server as `36.68.243.18`. It's up to you as a node operator to ensure that your public and private IPs match up properly. - -To find the right IP configuration, contact your VPS provider for support. - -## Metrics / API endpoints -The mix node binary exposes several API endpoints that can be pinged in order to gather information about the node, and the Nym API (previously 'Validator API') exposes numerous mix node related endpoints which provide network-wide information about mix nodes, the network topology (the list of avaliable mix nodes for packet routing), and information regarding uptime monitoring and rewarding history. - -### Mix node API endpoints -Since the mix node binary exposes several API endpoints itself, you can ping these easily via curl: - -| Endpoint | Description | Command | -| -------------------- | ------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------- | -| `/description` | Returns the description of the node set with the `describe` command | `curl :8000/description` | -| `/hardware` | Returns the hardware information of the node | `curl :8000/hardware` | -| `/verloc` | Returns the verloc information of the node, updated every 12 hours | `curl :8000/verloc` | - -The code for exposed API endpoints can be found [here](https://github.com/nymtech/nym/tree/release/{{platform_release_version}}/mixnode/src/node/http). - -> You can get more detailed info by appending `?debug` to the URL, like so: -> ``` -> curl http://:8000/stats?debug -> ``` - -### Mix node related Nym API (previously 'Validator API') endpoints -Numerous endpoints are documented on the Nym API (previously 'Validator API')'s [Swagger Documentation](https://validator.nymtech.net/api/swagger/index.html). There you can also try out various requests from your browser, and download the response from the API. Swagger will also show you what commands it is running, so that you can run these from an app or from your CLI if you prefer. - -#### Mix node Reward Estimation API endpoint - -The Reward Estimation API endpoint allows mixnode operators to estimate the rewards they could earn for running a Nym mixnode with a specific `MIX_ID`. - -> The `` can be found in the "Mix ID" column of the [Network Explorer](https://explorer.nymtech.net/network-components/mixnodes/active). - -The endpoint is a particularly common for mixnode operators as it can provide an estimate of potential earnings based on factors such as the amount of traffic routed through the mixnode, the quality of the mixnode's performance, and the overall demand for mixnodes in the network. This information can be useful for mixnode operators in deciding whether or not to run a mix node and in optimizing its operations for maximum profitability. - -Using this API endpoint returns information about the Reward Estimation: - -``` -/status/mixnode//reward-estimation -``` - -Query Response: - -``` - "estimation": { - "total_node_reward": "942035.916721770541325331", - "operator": "161666.263307386408152071", - "delegates": "780369.65341438413317326", - "operating_cost": "54444.444444444444444443" - }, -``` - -> The unit of value is measured in `uNYM`. - -- `estimated_total_node_reward` - An estimate of the total amount of rewards that a particular mix node can expect to receive during the current epoch. This value is calculated by the Nym Validator based on a number of factors, including the current state of the network, the number of mix nodes currently active in the network, and the amount of network traffic being processed by the mix node. - -- `estimated_operator_reward` - An estimate of the amount of rewards that a particular mix node operator can expect to receive. This value is calculated by the Nym Validator based on a number of factors, including the amount of traffic being processed by the mix node, the quality of service provided by the mix node, and the operator's stake in the network. - -- `estimated_delegators_reward` - An estimate of the amount of rewards that mix node delegators can expect to receive individually. This value is calculated by the Nym Validator based on a number of factors, including the amount of traffic being processed by the mix node, the quality of service provided by the mix node, and the delegator's stake in the network. - -- `estimated_node_profit` - An estimate of the profit that a particular mix node operator can expect to earn. This value is calculated by subtracting the mix node operator's `operating_costs` from their `estimated_operator_reward` for the current epoch. - -- `estimated_operator_cost` - An estimate of the total cost that a particular mix node operator can expect to incur for their participation. This value is calculated by the Nym Validator based on a number of factors, including the cost of running a mix node, such as server hosting fees, and other expenses associated with operating the mix node. - -## Ports -All mix node-specific port configuration can be found in `$HOME/.nym/mixnodes//config/config.toml`. If you do edit any port configs, remember to restart your mix node. - -### Mix node port reference -| Default port | Use | -| ------------ | ------------------------- | -| `1789` | Listen for mixnet traffic | -| `1790` | Listen for VerLoc traffic | -| `8000` | Metrics http API endpoint | -/ +For mixnode upgrade, firewall setup, port configuration, API endpoints, VPS suggestions, automation and more, see the [maintenance page](./maintenance.md) From d422741be43afca177d586cb39d792aaa3f77640 Mon Sep 17 00:00:00 2001 From: serinko <97586125+serinko@users.noreply.github.com> Date: Mon, 24 Jul 2023 12:53:32 +0200 Subject: [PATCH 208/361] operators-guide: create NR page & edit maintenance page --- documentation/operators/src/SUMMARY.md | 10 +- .../operators/src/nodes/gateway-setup.md | 2 +- .../operators/src/nodes/maintenance.md | 83 +++++++- .../operators/src/nodes/mix-node-setup.md | 2 +- .../src/nodes/network-requester-setup.md | 179 +++++++++++++++++- 5 files changed, 269 insertions(+), 7 deletions(-) diff --git a/documentation/operators/src/SUMMARY.md b/documentation/operators/src/SUMMARY.md index 796c7d41ed..3e0fcf4280 100644 --- a/documentation/operators/src/SUMMARY.md +++ b/documentation/operators/src/SUMMARY.md @@ -1,9 +1,17 @@ +# Summary + - [Introduction](introduction.md) +# Binaries +- [Pre-built Binaries](../../docs/src/binaries/pre-built-binaries.md) +- [Building from Source](../../docs/src/binaries/building-nym.md) +- [Binary Initialisation and Configuration](../../docs/src/binaries/init-and-config.md) + + # Operators Guides - [Node Setup Guides](./nodes/setup-guides.md) -- [Preliminary Steps](./preliminary-steps.md) + - [Preliminary Steps](./preliminary-steps.md) - [Mix Node](./nodes/mix-node-setup.md) - [Gateway](./nodes/gateway-setup.md) - [Network Requester](./nodes/network-requester-setup.md) diff --git a/documentation/operators/src/nodes/gateway-setup.md b/documentation/operators/src/nodes/gateway-setup.md index eaacfaf52a..dd5c65e2ad 100644 --- a/documentation/operators/src/nodes/gateway-setup.md +++ b/documentation/operators/src/nodes/gateway-setup.md @@ -4,7 +4,7 @@ TODO ---> # Gateways -> The Nym gateway was built in the [building nym](../binaries/building-nym.md) section. If you haven't yet built Nym and want to run the code, go there first. +> The Nym gateway was built in the [building nym](https://nymtech.net/operators/binaries/building-nym.html) section. If you haven't yet built Nym and want to run the code, go there first. > Any syntax in `<>` brackets is a user's unique variable. Exchange with a corresponding name without the `<>` brackets. diff --git a/documentation/operators/src/nodes/maintenance.md b/documentation/operators/src/nodes/maintenance.md index 583bb6aa47..9216556050 100644 --- a/documentation/operators/src/nodes/maintenance.md +++ b/documentation/operators/src/nodes/maintenance.md @@ -20,6 +20,8 @@ Follow these steps to upgrade your mix node binary and update its config file: * re-run `init` with the same values as you used initially. **This will just update the config file, it will not overwrite existing keys**. * restart your mix node process with the new binary. +> In case of a network requester this is all all, the following step is only for mix nodes and gateways. + ### Step 2: Updating your node information in the smart contract Follow these steps to update the information about your `` which is publicly available from the [Nym API](https://validator.nymtech.net/api/swagger/index.html) and information displayed on the [mixnet explorer](https://explorer.nymtech.net). @@ -38,6 +40,37 @@ You can either do this graphically via the Desktop Wallet, or the CLI. If you want to bond your `` via the CLI, then check out the [relevant section in the Nym CLI](../../documentation/docs/src/tools/nym-cli.md#upgrade-a-mix-node) docs. +### Upgrading Network Requester to >= v1.1.10 from +``` + +Copy the old keys from your client to the network-requester configuration that was created above: + +``` +cp -vr ~/.nym/clients/myoldclient/data/* ~/.nym/service-providers/network-requester//data +``` + +Edit the configuration to match what you used on your client. Specifically, edit the configuration file at: + +``` +~/.nym/service-providers/network-requester//config/config.toml +``` + +Ensure that the fields `gateway_id`, `gateway_owner`, `gateway_listener` in the new config match those in the old client config at: + +``` +~/.nym/clients/myoldclient/config/config.toml +``` + + ## VPS Setup and Automation ### Configure your firewall Although your `` is now ready to recieve traffic, your server may not be. The following commands will allow you to set up a firewall using `ufw`. @@ -65,11 +98,16 @@ sudo ufw allow 1789,1790,8000,22/tcp # for gateway sudo ufw allow 1789,22,9000/tcp -# check the status of the firewall +# for network requester +sudo ufw allow 22,9000/tcp +``` + +Check the status of the firewall: +``` sudo ufw status ``` -For more information about your mix node's port configuration, check the [mix node port reference table](./mix-node-setup.md#mixnode-port-reference) or [gateway port reference table](https://nymtech.net/docs/nodes/gateway-setup.html#gateway-port-reference) below. +For more information about your node's port configuration, check the [port reference table](https://nymtech.net/docs/nodes/gateway-setup.html#ports) below. ### Automating your node with tmux and systemd @@ -162,6 +200,39 @@ WantedBy=multi-user.target * Put the above file onto your system at `/etc/systemd/system/nym-gateway.service`. +For network requester: + +```ini +[Unit] +Description=Nym Network Requester ({{platform_release_version}}) +StartLimitInterval=350 +StartLimitBurst=10 + +[Service] +User=nym # replace this with whatever user you wish +LimitNOFILE=65536 +# remember to add the `--enable-statistics` flag if running as part of a service grant and check the path to your nym-network-requester binary +ExecStart=/home/nym/nym-network-requester run --id +KillSignal=SIGINT +Restart=on-failure +RestartSec=30 + +[Install] +WantedBy=multi-user.target +``` + +Now enable and start your requester: + +``` +systemctl enable nym-network-requester.service +systemctl start nym-network-requester.service + +# you can always check your requester has succesfully started with: +systemctl status nym-network-requester.service +``` +* Put the above file onto your system at `/etc/systemd/system/nym-network-requester.service`. + + Change the `` in `ExecStart` to point at your `` binary (`nym-mixnode` or `nym-gateway), and the `` so it is the user you are running as. If you have built nym in the `$HOME` directory on your server, and your username is `jetpanther`, then the start command for nym mixnode might look like this: @@ -351,7 +422,7 @@ Query Response: - `estimated_operator_cost` - An estimate of the total cost that a particular mix node operator can expect to incur for their participation. This value is calculated by the Nym Validator based on a number of factors, including the cost of running a mix node, such as server hosting fees, and other expenses associated with operating the mix node. ## Ports -All ``-specific port configuration can be found in `$HOME/.nym///config/config.toml`. If you do edit any port configs, remember to restart your mix node. +All ``-specific port configuration can be found in `$HOME/.nym///config/config.toml`. If you do edit any port configs, remember to restart your client and node processes. ### Mix node port reference | Default port | Use | @@ -367,4 +438,10 @@ All ``-specific port configuration can be found in `$HOME/.nym// The Nym mix node binary was built in the [building nym](https://nymtech.net/docs/binaries/building-nym.html) section. If you haven't yet built Nym and want to run the code, go there first. +> The Nym mix node binary was built in the [building nym](https://nymtech.net/operators/binaries/building-nym.html) section. If you haven't yet built Nym and want to run the code, go there first. > Any syntax in `<>` brackets is a user's unique variable. Exchange with a corresponding name without the `<>` brackets. diff --git a/documentation/operators/src/nodes/network-requester-setup.md b/documentation/operators/src/nodes/network-requester-setup.md index 797dfd7795..1f5e5eee0c 100644 --- a/documentation/operators/src/nodes/network-requester-setup.md +++ b/documentation/operators/src/nodes/network-requester-setup.md @@ -1 +1,178 @@ -# Network Requester + +# Network Requesters + +> The Nym gateway was built in the [building nym](https://nymtech.net/operators/binaries/building-nym.html) section. If you haven't yet built Nym and want to run the code, go there first. + +> Any syntax in `<>` brackets is a user's unique variable. Exchange with a corresponding name without the `<>` brackets. + +## Current version +``` + +``` + +## Preliminary steps + +Make sure you do the preparation listed in the [preliminary steps page](../preliminary-steps.md) before setting up your network requester. + +## Network Requester Whitelist +If you have access to a server, you can run the network requester, which allows Nym users to send outbound requests from their local machine through the mixnet to a server, which then makes the request on their behalf, shielding them (and their metadata) from clearnet, untrusted and unknown infrastructure, such as email or message client servers. + +By default the network requester is **not** an open proxy (although it can be used as one). It uses a file called `allowed.list` (located in `~/.nym/service-providers/network-requester//`) as a whitelist for outbound requests. + +Any request to a URL which is not on this list will be blocked. + +On startup, if this file is not present, the requester will grab the default whitelist from [Nym's default list](https://nymtech.net/.wellknown/network-requester/standard-allowed-list.txt) automatically. + +This default whitelist is useful for knowing that the majority of network requesters are able to support certain apps 'out of the box'. + +**Operators of a network requester are of course free to edit this file and add the URLs of services they wish to support to it!** You can find instructions below on adding your own URLs or IPs to this list. + +The domains and IPs on the default whitelist can be broken down by application as follows: + + +``` +# Keybase +keybaseapi.com +s3.amazonaws.com +amazonaws.com +twitter.com +keybase.io +gist.githubusercontent.com + +# Used to for uptime healthcheck (see the section on testing your requester below for more) +nymtech.net + +# Blockstream Green Bitcoin Wallet +blockstream.info +blockstream.com +greenaddress.it + +# Electrum Bitcoin Wallet +electrum.org + +# Helios Ethereum Client +alchemy.com +lightclientdata.org +p2pify.com + +# Telegram - these IPs have been copied from https://core.telegram.org/resources/cidr.txt as Telegram does +# not seem to route by domain as the other apps on this list do +91.108.56.0/22 +91.108.4.0/22 +91.108.8.0/22 +91.108.16.0/22 +91.108.12.0/22 +149.154.160.0/20 +91.105.192.0/23 +91.108.20.0/22 +185.76.151.0/24 +2001:b28:f23d::/48 +2001:b28:f23f::/48 +2001:67c:4e8::/48 +2001:b28:f23c::/48 +2a0a:f280::/32 + +# Matrix +matrix.org + +``` + +## Network Requester Directory +You can find a list of Network Requesters running the default whitelist in the [explorer](https://explorer.nymtech.net/network-components/service-providers). This list comprises of the NRs running as infrastructure for NymConnect. + +> We are currently working on a smart-contract based solution more in line with how Mix nodes and Gateways announce themselves to the network. + +## Viewing command help + +To begin, move to `/taget/release` directory from which you run the node commands: + +``` +cd target/release +``` + +The `./nym-network-requester --help ` command can be used to show a list of available parameters. + +~~~admonish example collapsible=true title="Console output" +``` + +``` +~~~ + +You can check the required parameters for available commands by running: + +``` +./nym-network-requester --help +``` + +## Initializing and running your network requester + +The network-requester needs to be initialized before it can be run. This is required for the embedded nym-client to connect successfully to the mixnet. We want to specify an `` using the `--id` command and give it a value of your choice. The following command will achieve that: + +``` + ./nym-network-requester init --id +``` + +In the following we used `example`. + +~~~admonish example collapsible=true title="Console output" +``` + +``` +~~~ + + +Now that we have initialized our network-requester, we can start it with the following command: + +``` + ./nym-network-requester run --id +``` + +## Maintenance + +For network requester upgrade (including an upgrade from `= v1.1.10`), firewall setup, port configuration, API endpoints, VPS suggestions, automation and more, see the [maintenance page](./maintenance.md). + + +## Using your network requester + +The next thing to do is use your requester, share its address with friends (or whoever you want to help privacy-enhance their app traffic). Is this safe to do? If it was an open proxy, this would be unsafe, because any Nym user could make network requests to any system on the internet. + +To make things a bit less stressful for administrators, the Network Requester drops all incoming requests by default. In order for it to make requests, you need to add specific domains to the `allowed.list` file at `$HOME/.nym/service-providers/network-requester/allowed.list`. + +### Supporting custom domains with your network requester +It is easy to add new domains and services to your network requester - simply find out which endpoints (both URLs and raw IP addresses are supported) you need to whitelist, and then add these endpoints to your `allowed.list`. + +How to go about this? Have a look in your nym-network-requester config directory: + +``` +ls $HOME/.nym/service-providers/network-requester/ + +# returns: allowed.list unknown.list +``` + +We already know that `allowed.list` is what lets requests go through. All unknown requests are logged to `unknown.list`. If you want to try using a new client type, just start the new application, point it at your local [socks client](https://nymtech.net/docs/clients/socks5-client.html) (configured to use your remote `nym-network-requester`), and keep copying URLs from `unknown.list` into `allowed.list` (it may take multiple tries until you get all of them, depending on the complexity of the application). Make sure to restart your network requester! + +> If you are adding custom domains, please note that whilst they may appear in the logs of your network-requester as something like `api-0.core.keybaseapi.com:443`, you **only need** to include the main domain name, in this instance `keybaseapi.com` + +### Running an open proxy +If you *really* want to run an open proxy, perhaps for testing purposes for your own use or among a small group of trusted friends, it is possible to do so. You can disable network checks by passing the flag `--open-proxy` flag when you run it. If you run in this configuration, you do so at your own risk. + +## Testing your network requester +1. Make sure `nymtech.net` is in your `allowed.list` (remember to restart your network requester). + +2. Ensure that your network-requester is initialized and running. + +3. In another terminal window, run the following: + +``` +curl -x socks5h://localhost:1080 https://nymtech.net/.wellknown/connect/healthcheck.json +``` + +This command should return the following: + +``` +{ "status": "ok" } +``` + From 990f7d64ddba9bff7a019bed2a66cb25f5009ace Mon Sep 17 00:00:00 2001 From: serinko <97586125+serinko@users.noreply.github.com> Date: Wed, 26 Jul 2023 11:46:36 +0200 Subject: [PATCH 209/361] operators-guide: create validator tutorial & add maintenance points --- documentation/operators/src/SUMMARY.md | 6 +- documentation/operators/src/introduction.md | 2 +- .../operators/src/nodes/maintenance.md | 219 +++++++++++++++++- .../operators/src/nodes/troubleshooting.md | 17 ++ 4 files changed, 231 insertions(+), 13 deletions(-) diff --git a/documentation/operators/src/SUMMARY.md b/documentation/operators/src/SUMMARY.md index 3e0fcf4280..f767aec6f7 100644 --- a/documentation/operators/src/SUMMARY.md +++ b/documentation/operators/src/SUMMARY.md @@ -10,12 +10,14 @@ # Operators Guides -- [Node Setup Guides](./nodes/setup-guides.md) +- [Mixnet Nodes Setup](./nodes/setup-guides.md) - [Preliminary Steps](./preliminary-steps.md) - [Mix Node](./nodes/mix-node-setup.md) - [Gateway](./nodes/gateway-setup.md) - [Network Requester](./nodes/network-requester-setup.md) - - [Validator](./nodes/validator-setup.md) +- [Nyx Validator Setup](./nodes/validator-setup.md) - [Maintenance](./nodes/maintenance.md) - [Troubleshooting](./nodes/troubleshooting.md) + diff --git a/documentation/operators/src/introduction.md b/documentation/operators/src/introduction.md index a97860f292..48b5984eef 100644 --- a/documentation/operators/src/introduction.md +++ b/documentation/operators/src/introduction.md @@ -1,6 +1,6 @@ # Introduction -This is Nym's operators guide, containing information and setup tutorials about the various pieces of Nym mixnet infrastructure nodes, such as mix node, gateway and network requester. +This is Nym's operators guide, containing information and setup tutorials about the various pieces of Nym mixnet infrastructure nodes, such as mix node, gateway and network requester as well as Nyx chain validators. If you are new to Nym and want to learn about the mixnet, how to integrate with the network, developer tutorials and quickstart guides, check out the [Developer Portal](https://nymtech.net/developers/). diff --git a/documentation/operators/src/nodes/maintenance.md b/documentation/operators/src/nodes/maintenance.md index 9216556050..1ac31c959c 100644 --- a/documentation/operators/src/nodes/maintenance.md +++ b/documentation/operators/src/nodes/maintenance.md @@ -2,8 +2,7 @@ ## Upgrading your node @@ -70,10 +69,34 @@ Ensure that the fields `gateway_id`, `gateway_owner`, `gateway_listener` in the ~/.nym/clients/myoldclient/config/config.toml ``` +### Upgrading your validator + +Upgrading from `v0.31.1` -> `v0.32.0` process is fairly simple. Grab the `v0.32.0` release tarball from the [`nyxd` releases page](https://github.com/nymtech/nyxd/releases), and untar it. Inside are two files: + +- the new validator (`nyxd`) v0.32.0 +- the new wasmvm (it depends on your platform, but most common filename is `libwasmvm.x86_64.so`) + +Wait for the upgrade height to be reached and the chain to halt awaiting upgrade, then: + +* copy `libwasmvm.x86_64.so` to the default LD_LIBRARY_PATH on your system (on Ubuntu 20.04 this is `/lib/x86_64-linux-gnu/`) replacing your existing file with the same name. +* swap in your new `nyxd` binary and restart. + +You can also use something like [Cosmovisor](https://github.com/cosmos/cosmos-sdk/tree/main/tools/cosmovisor) - grab the relevant information from the current upgrade proposal [here](https://nym.explorers.guru/proposal/9). + +Note: Cosmovisor will swap the `nyxd` binary, but you'll need to already have the `libwasmvm.x86_64.so` in place. + +#### Common reasons for your validator being jailed + +The most common reason for your validator being jailed is that your validator is out of memory because of bloated syslogs. + +Running the command `df -H` will return the size of the various partitions of your VPS. + +If the `/dev/sda` partition is almost full, try pruning some of the `.gz` syslog archives and restart your validator process. + ## VPS Setup and Automation ### Configure your firewall -Although your `` is now ready to recieve traffic, your server may not be. The following commands will allow you to set up a firewall using `ufw`. +Although your `` is now ready to receive traffic, your server may not be. The following commands will allow you to set up a firewall using `ufw`. ``` # check if you have ufw installed @@ -100,6 +123,9 @@ sudo ufw allow 1789,22,9000/tcp # for network requester sudo ufw allow 22,9000/tcp + +# for validator +sudo ufw allow 1317,26656,26660,22,80,443/tcp ``` Check the status of the firewall: @@ -156,7 +182,7 @@ tmux attach-session Here's a systemd service file to do that: -For mixnode: +##### For Mixnode ```ini [Unit] @@ -178,7 +204,7 @@ WantedBy=multi-user.target * Put the above file onto your system at `/etc/systemd/system/nym-mixnode.service`. -For gateway: +##### For Gateway ```ini [Unit] @@ -200,7 +226,7 @@ WantedBy=multi-user.target * Put the above file onto your system at `/etc/systemd/system/nym-gateway.service`. -For network requester: +##### For Network requester ```ini [Unit] @@ -232,8 +258,41 @@ systemctl status nym-network-requester.service ``` * Put the above file onto your system at `/etc/systemd/system/nym-network-requester.service`. +##### For Validator -Change the `` in `ExecStart` to point at your `` binary (`nym-mixnode` or `nym-gateway), and the `` so it is the user you are running as. +Below is a systemd unit file to place at `/etc/systemd/system/nymd.service`: + +```ini +[Unit] +Description=Nyxd +StartLimitInterval=350 +StartLimitBurst=10 + +[Service] +User=nyx # change to your user +Type=simple +Environment="LD_LIBRARY_PATH=/home/youruser/path/to/nyx/binaries" # change to correct path +ExecStart=/home/youruser/path/to/nyx/binaries/nymd start # change to correct path +Restart=on-failure +RestartSec=30 +LimitNOFILE=infinity + +[Install] +WantedBy=multi-user.target +``` + +Proceed to start it with: + +``` +systemctl daemon-reload # to pickup the new unit file +systemctl enable nymd # to enable the service +systemctl start nymd # to actually start the service +journalctl -f # to monitor system logs showing the service start +``` + +##### Following steps for Nym Mixnet nodes + +Change the `` in `ExecStart` to point at your `` binary (`nym-mixnode`, `nym-gateway` or `nym-network-requester`), and the `` so it is the user you are running as. If you have built nym in the `$HOME` directory on your server, and your username is `jetpanther`, then the start command for nym mixnode might look like this: @@ -288,14 +347,22 @@ This means that the operating system is preventing network connections from bein #### Set the ulimit via `systemd` service file +> Replace `` variable with `nym-mixnode`, `nym-gateway` or `nym-network-requester` according the node you running on your machine. + The ulimit setup is relevant for maintenance of nym mixnode only. -Query the `ulimit` of your mix node with: +Query the `ulimit` of your `` with: ``` -grep -i "open files" /proc/$(ps -A -o pid,cmd|grep nym-mixnode | grep -v grep |head -n 1 | awk '{print $1}')/limits +# for nym-mixnode, nym-gateway and nym-network requester: +grep -i "open files" /proc/$(ps -A -o pid,cmd|grep | grep -v grep |head -n 1 | awk '{print $1}')/limits + +# for nyx validator: +grep -i "open files" /proc/$(ps -A -o pid,cmd|grep nymd | grep -v grep |head -n 1 | awk '{print $1}')/limits ``` + + You'll get back the hard and soft limits, which looks something like this: ``` @@ -322,7 +389,15 @@ or execute this as root for system-wide setting of `ulimit`: echo "DefaultLimitNOFILE=65535" >> /etc/systemd/system.conf ``` -Reboot your machine and restart your node. When it comes back, use `cat /proc/$(pidof nym-mixnode)/limits | grep "Max open files"` to make sure the limit has changed to 65535. +Reboot your machine and restart your node. When it comes back, use: +``` +# for nym-mixnode, nym-gateway and nym-network requester: +cat /proc/$(pidof )/limits | grep "Max open files"` to make sure the limit has changed to 65535. + +# for validator +cat /proc/$(pidof nym-validator)/limits | grep "Max open files" +``` +Make sure the limit has changed to 65535. #### Set the ulimit on `non-systemd` based distributions @@ -421,6 +496,121 @@ Query Response: - `estimated_operator_cost` - An estimate of the total cost that a particular mix node operator can expect to incur for their participation. This value is calculated by the Nym Validator based on a number of factors, including the cost of running a mix node, such as server hosting fees, and other expenses associated with operating the mix node. +### Validator: Installing and configuring nginx for HTTPS +#### Setup +[Nginx](https://www.nginx.com/resources/glossary/nginx/#:~:text=NGINX%20is%20open%20source%20software,%2C%20media%20streaming%2C%20and%20more.&text=In%20addition%20to%20its%20HTTP,%2C%20TCP%2C%20and%20UDP%20servers.) is an open source software used for operating high-performance web servers. It allows us to set up reverse proxying on our validator server to improve performance and security. + +Install `nginx` and allow the 'Nginx Full' rule in your firewall: + +``` +sudo ufw allow 'Nginx Full' +``` + +Check nginx is running via systemctl: + +``` +systemctl status nginx +``` + +Which should return: + +``` +● nginx.service - A high performance web server and a reverse proxy server + Loaded: loaded (/lib/systemd/system/nginx.service; enabled; vendor preset: enabled) + Active: active (running) since Fri 2018-04-20 16:08:19 UTC; 3 days ago + Docs: man:nginx(8) + Main PID: 2369 (nginx) + Tasks: 2 (limit: 1153) + CGroup: /system.slice/nginx.service + ├─2369 nginx: master process /usr/sbin/nginx -g daemon on; master_process on; + └─2380 nginx: worker process +``` + +#### Configuration + +Proxying your validator's port `26657` to nginx port `80` can then be done by creating a file with the following at `/etc/nginx/conf.d/validator.conf`: + +``` +server { + listen 80; + listen [::]:80; + server_name "domain_name"; + + location / { + proxy_pass http://127.0.0.1:26657; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header Host $host; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } +} +``` + +Followed by: + +``` +sudo apt install certbot nginx python3 +certbot --nginx -d nym-validator.yourdomain.com -m you@yourdomain.com --agree-tos --noninteractive --redirect +``` + +```admonish caution title="" +If using a VPS running Ubuntu 20: replace `certbot nginx python3` with `python3-certbot-nginx` +``` + +These commands will get you an https encrypted nginx proxy in front of the API. + +### Configuring Prometheus metrics (optional) + +Configure Prometheus with the following commands (adapted from NodesGuru's [Agoric setup guide](https://nodes.guru/agoric/setup-guide/en)): + +``` +echo 'export OTEL_EXPORTER_PROMETHEUS_PORT=9464' >> $HOME/.bashrc +source ~/.bashrc +sed -i '/\[telemetry\]/{:a;n;/enabled/s/false/true/;Ta}' $HOME/.nymd/config/app.toml +sed -i "s/prometheus-retention-time = 0/prometheus-retention-time = 60/g" $HOME/.nymd/config/app.toml +sudo ufw allow 9464 +echo 'Metrics URL: http://'$(curl -s ifconfig.me)':26660/metrics' +``` + +Your validator's metrics will be available to you at the returned 'Metrics URL'. + +~~~admonish example collapsible=true title="Console output" +``` +# HELP go_gc_duration_seconds A summary of the pause duration of garbage collection cycles. +# TYPE go_gc_duration_seconds summary +go_gc_duration_seconds{quantile="0"} 6.7969e-05 +go_gc_duration_seconds{quantile="0.25"} 7.864e-05 +go_gc_duration_seconds{quantile="0.5"} 8.4591e-05 +go_gc_duration_seconds{quantile="0.75"} 0.000115919 +go_gc_duration_seconds{quantile="1"} 0.001137591 +go_gc_duration_seconds_sum 0.356555301 +go_gc_duration_seconds_count 2448 +# HELP go_goroutines Number of goroutines that currently exist. +# TYPE go_goroutines gauge +go_goroutines 668 +# HELP go_info Information about the Go environment. +# TYPE go_info gauge +go_info{version="go1.15.7"} 1 +# HELP go_memstats_alloc_bytes Number of bytes allocated and still in use. +# TYPE go_memstats_alloc_bytes gauge +go_memstats_alloc_bytes 1.62622216e+08 +# HELP go_memstats_alloc_bytes_total Total number of bytes allocated, even if freed. +# TYPE go_memstats_alloc_bytes_total counter +go_memstats_alloc_bytes_total 2.09341707264e+11 +# HELP go_memstats_buck_hash_sys_bytes Number of bytes used by the profiling bucket hash table. +# TYPE go_memstats_buck_hash_sys_bytes gauge +go_memstats_buck_hash_sys_bytes 5.612319e+06 +# HELP go_memstats_frees_total Total number of frees. +# TYPE go_memstats_frees_total counter +go_memstats_frees_total 2.828263344e+09 +# HELP go_memstats_gc_cpu_fraction The fraction of this program's available CPU time used by the GC since the program started. +# TYPE go_memstats_gc_cpu_fraction gauge +go_memstats_gc_cpu_fraction 0.03357798610671518 +# HELP go_memstats_gc_sys_bytes Number of bytes used for garbage collection system metadata. +# TYPE go_memstats_gc_sys_bytes gauge +go_memstats_gc_sys_bytes 1.3884192e+07 +``` +~~~ + ## Ports All ``-specific port configuration can be found in `$HOME/.nym///config/config.toml`. If you do edit any port configs, remember to restart your client and node processes. @@ -444,4 +634,13 @@ All ``-specific port configuration can be found in `$HOME/.nym// + + + # Troubleshooting + +### Common reasons for your validator being jailed + +The most common reason for your validator being jailed is that your validator is out of memory because of bloated syslogs. + +Running the command `df -H` will return the size of the various partitions of your VPS. + +If the `/dev/sda` partition is almost full, try pruning some of the `.gz` syslog archives and restart your validator process. + From 49d909815d1fc3bcd5ccf81896f40b7aa26cc1d0 Mon Sep 17 00:00:00 2001 From: serinko <97586125+serinko@users.noreply.github.com> Date: Wed, 26 Jul 2023 12:55:05 +0200 Subject: [PATCH 210/361] operators-guide: incorporated PR #3656 --- documentation/operators/src/nodes/gateway-setup.md | 1 + documentation/operators/src/nodes/maintenance.md | 14 ++++++++++++++ .../operators/src/nodes/mix-node-setup.md | 2 ++ .../operators/src/nodes/network-requester-setup.md | 2 ++ 4 files changed, 19 insertions(+) diff --git a/documentation/operators/src/nodes/gateway-setup.md b/documentation/operators/src/nodes/gateway-setup.md index dd5c65e2ad..cdc72700e2 100644 --- a/documentation/operators/src/nodes/gateway-setup.md +++ b/documentation/operators/src/nodes/gateway-setup.md @@ -45,6 +45,7 @@ You can also check the various arguments required for individual commands with: ``` ./nym-gateway --help ``` +> Adding `--no-banner` startup flag will prevent Nym banner being printed even if run in tty environment. ### Initialising your gateway To check available configuration options use: diff --git a/documentation/operators/src/nodes/maintenance.md b/documentation/operators/src/nodes/maintenance.md index 1ac31c959c..d4884bad84 100644 --- a/documentation/operators/src/nodes/maintenance.md +++ b/documentation/operators/src/nodes/maintenance.md @@ -4,6 +4,20 @@ TODO - [x] Compare mixnode, gateway, NR steps and validator of upgrading and automation and make a generic page - this one - for all of them with additional notes for particular nodes ---> +## Useful commands + +> Adding `--no-banner` startup flag will prevent Nym banner being printed even if run in tty environment. + +**build-info** + +A `build-info` command prints the build information like commit hash, rust version, binary version just like what command `--version` does. However, you can also specify an `--output=json` flag that will format the whole output as a json, making it an order of magnitude easier to parse. + +For example `./target/debug/nym-network-requester --no-banner build-info --output json` will return: + +``` +{"binary_name":"nym-network-requester","build_timestamp":"2023-07-24T15:38:37.00657Z","build_version":"1.1.23","commit_sha":"c70149400206dce24cf20babb1e64f22202672dd","commit_timestamp":"2023-07-24T14:45:45Z","commit_branch":"feature/simplify-cli-parsing","rustc_version":"1.71.0","rustc_channel":"stable","cargo_profile":"debug"} +``` + ## Upgrading your node > The process is the similar for mixnode, gateway and network requester. In the following steps we use a placeholder `` in the commands, please change it for the type of node you want to upgrade. Any particularities for the given type of node are included. diff --git a/documentation/operators/src/nodes/mix-node-setup.md b/documentation/operators/src/nodes/mix-node-setup.md index 435f560a59..8797c13056 100644 --- a/documentation/operators/src/nodes/mix-node-setup.md +++ b/documentation/operators/src/nodes/mix-node-setup.md @@ -67,6 +67,8 @@ You can also check the various arguments required for individual commands with: ./nym-mixnode --help ``` +> Adding `--no-banner` startup flag will prevent Nym banner being printed even if run in tty environment. + ### Initialising your mix node To check available configuration options for initializing your node use: diff --git a/documentation/operators/src/nodes/network-requester-setup.md b/documentation/operators/src/nodes/network-requester-setup.md index 1f5e5eee0c..33dc3c7c90 100644 --- a/documentation/operators/src/nodes/network-requester-setup.md +++ b/documentation/operators/src/nodes/network-requester-setup.md @@ -107,6 +107,8 @@ You can check the required parameters for available commands by running: ./nym-network-requester --help ``` +> Adding `--no-banner` startup flag will prevent Nym banner being printed even if run in tty environment. + ## Initializing and running your network requester The network-requester needs to be initialized before it can be run. This is required for the embedded nym-client to connect successfully to the mixnet. We want to specify an `` using the `--id` command and give it a value of your choice. The following command will achieve that: From a002ddda8d9003e0ef20710c7be0fc97ec11cbfa Mon Sep 17 00:00:00 2001 From: serinko <97586125+serinko@users.noreply.github.com> Date: Thu, 27 Jul 2023 12:41:22 +0200 Subject: [PATCH 211/361] operators-guide:troubleshooting.md done - content ready to go out --- documentation/operators/src/faq.md | 3 +- .../operators/src/nodes/maintenance.md | 41 ++- .../operators/src/nodes/mix-node-setup.md | 10 +- .../operators/src/nodes/troubleshooting.md | 325 +++++++++++++++++- 4 files changed, 365 insertions(+), 14 deletions(-) diff --git a/documentation/operators/src/faq.md b/documentation/operators/src/faq.md index e09a78c21c..88836f17f2 100644 --- a/documentation/operators/src/faq.md +++ b/documentation/operators/src/faq.md @@ -3,7 +3,8 @@ TODO - [ ] Explain active set and needed metrics - [ ] List friendly VPS providers - [ ] Explain difficulty running a node on a self-hosted machine -- [ ] Explain uNYM vs NYM. Why, ratio, etc +- [ ] Explain uNYM vs NYM/uNYX vs NYX. Why, ratio, etc +- [ ] Explain why validators set is whitelisted Some community questions: Hi, do you know if it's on the roadmap for network nodes (requesters, socks5 client, etc.) to reconnect to another gateway if the current gateway's connection drops or has issues? I have to reset my requester and socks5 clients to connect them to a new gateway almost every day...😕 having to delete the entire folders associated with them diff --git a/documentation/operators/src/nodes/maintenance.md b/documentation/operators/src/nodes/maintenance.md index d4884bad84..d809027150 100644 --- a/documentation/operators/src/nodes/maintenance.md +++ b/documentation/operators/src/nodes/maintenance.md @@ -3,6 +3,11 @@ ## Useful commands @@ -107,6 +112,32 @@ Running the command `df -H` will return the size of the various partitions of yo If the `/dev/sda` partition is almost full, try pruning some of the `.gz` syslog archives and restart your validator process. +## Moving a node + +In case of a need to move a node from one machine to another and avoiding to lose the delegation, here are few steps how to do it. + +The following examples transfers a mixnode (in case of other nodes, change the `mixnodes` in the command for the `` of your desire. + +* Pause your node process. + +Assuming both machines are remote VPS. + +* Make sure your `~/.ssh/.pub` is in both of the machines `~/.ssh/authorized_keys` file +* Create a `mixnodes` folder in the target VPS. Ssh in from your terminal and run: +``` +# in case none of the nym configs was created previously +mkdir ~/.nym + +#in case no nym mixnode was initialized previously +mkdir ~/.nym/mixnodes +``` +* Move the node data (keys) and config file to the new machine by opening a local terminal (as that one's ssh key is authorized in both of the machines) and running: +``` +scp -r @:~/.nym/mixnodes/ @:~/.nym/mixnodes/ +``` +* Re-run init (remember that init doesn't overwrite existing keys) to generate a config with the new listening address etc. +* Change the node smart contract info via the wallet interface. Otherwise the keys will point to the old IP address in the smart contract, and the node will not be able to be connected, and it will fail up-time checks. +* Re-run the node from the new location. ## VPS Setup and Automation ### Configure your firewall @@ -149,10 +180,18 @@ sudo ufw status For more information about your node's port configuration, check the [port reference table](https://nymtech.net/docs/nodes/gateway-setup.html#ports) below. -### Automating your node with tmux and systemd +### Automating your node with nohup, tmux and systemd Although it’s not totally necessary, it's useful to have the mix node automatically start at system boot time. +#### nohup + +`nohup` is a command with which your terminal is told to ignore the `HUP` or 'hangup' signal. This will stop the node process ending if you kill your session. + +``` +nohup ./ run --id # where `` is the id you set during the `init` command and depends on which node you starting +``` + #### tmux One way is to use `tmux` shell on top of your current VPS terminal. Tmux is a terminal multiplexer, it allows you to create several terminal windows and panes from a single terminal. Processes started in `tmux` keep running after closing the terminal as long as the given `tmux` window was not terminated. diff --git a/documentation/operators/src/nodes/mix-node-setup.md b/documentation/operators/src/nodes/mix-node-setup.md index 8797c13056..7e1cafa64c 100644 --- a/documentation/operators/src/nodes/mix-node-setup.md +++ b/documentation/operators/src/nodes/mix-node-setup.md @@ -8,13 +8,7 @@ TODO - [x] Add step 2/3 in the wallet bonding - [x] Better picture alignment. - [x] Add second console output to Initialising your mixnode manually as the automatized is not working -- [ ] Point users to understand the requirements for becoming a part of the active set. -- [ ] Include how to move a mix node from one VPS to another while keeping the delegation etc (ie init a new mix node and move `/home/nym/.nym/mixnodes/data/*` there ?) - - copy that dir over to the new vps - - re-run init - remember that init doesn't overwrite existing keys - to generate a config with the new listening address etc - - change the node info via the wallet - - Since otherwise their keys will point to the old IP address in the smart contract, and their node will (a) not be able to be connected to, and (b) fail uptime checks -- [ ] Explain more about self hosted nodes in the 6.1 not only as a troubleshooting +- [x] Explain more about self hosted nodes in the 6.1 not only as a troubleshooting - [x] Move the upgrade, automation, ulimit, vps, API and port parts to maintenance.md ---> @@ -327,7 +321,7 @@ There are also 2 community explorers which have been created by [Nodes Guru](htt For more details see [Troubleshooting FAQ](../nodes/troubleshooting.md) - + ## Maintenance diff --git a/documentation/operators/src/nodes/troubleshooting.md b/documentation/operators/src/nodes/troubleshooting.md index b27a417063..e5333bcfe4 100644 --- a/documentation/operators/src/nodes/troubleshooting.md +++ b/documentation/operators/src/nodes/troubleshooting.md @@ -1,13 +1,323 @@ - - # Troubleshooting +## Binary Build Problems + +### I am trying to build from the GitHub archive files and the build fails + +GitHub automatically includes .zip and tar.gz files of the Nym repository in its release. You cannot extract these and build - you'll see something like this: + +``` + process didn't exit successfully: `/build/nym/src/nym-0.12.1/target/release/build/nym-socks5-client-c1d0f76a8c7d7e9a/build-script-build` (exit status: 101) + --- stderr + thread 'main' panicked at 'failed to extract build metadata: could not find repository from '/build/nym/src/nym-0.12.1/clients/socks5'; class=Repository (6); code=NotFound (-3)', clients/socks5/build.rs:7:31 + note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace +warning: build failed, waiting for other jobs to finish... +error: build failed +``` + +Why does this happen? + +We have scripts which automatically include the Git commit hash and Git tag in the binary for easier debugging later. If you download a .zip and try building from that, it's not a Git repository and build will fail as above. + +What to do? + +* Open terminal in the directory where you want to have a git repository +* To get Nym repository for the first time, run: +``` +git clone https://github.com/nymtech/nym.git +``` +* Follow the instructions to build the platform +* To upgrade, pause your nodes, in the same terminal window run `git pull`, follow the upgrade instructions and re-start your nodes. + +## General Node Config + +### Where can I find my private and public keys and config? + +All config and keys files are stored in a directory named after your `id` which you chose during the `init` process, and can be found at the following PATH: `$HOME/.nym//` where `$HOME` is a home directory of the user (your current user in this case) that launched the node or client. + +The directory structure for each node will be roughly as follows: + +``` +bob@nym:~$ tree /home/nym/.nym/mixnodes/ +/home/nym/.nym/mixnodes/ +|-- nym010 +| |-- config +| | `-- config.toml +| `-- data +| |-- private_identity.pem +| |-- private_sphinx.pem +| |-- public_identity.pem +| `-- public_sphinx.pem +``` + +> If you `cat` the `public_sphinx.pem` key, the output will be different from the public key you will see on Nym [dashboard](https://sandbox-explorer.nymtech.net/). The reason for this is that `.pem` files are encoded in **base64**, however on the web they are in **base58**. Don't be confused if your keys look different. They are the same keys, just with different encoding :) + + +## Mix Nodes + +### How can I tell my node is up and running and mixing traffic? + +First of all check the 'Mixnodes' section of either the Nym Network Explorers: +* [Mainnet](https://explorer.nymtech.net/) +* [Sandbox testnet](https://sandbox-explorer.nymtech.net/) + +Enter your **identity key** to find your node. Check the contents of the `Mixnode stats` and `Routing score` sections. + +There are 2 community explorers currently, which have been created by [Nodes Guru](https://nodes.guru): +* [Mainnet](https://mixnet.explorers.guru/) +* [Sandbox testnet](https://sandbox.mixnet.explorers.guru/) + +If you want more information, or if your node isn't showing up on the explorer of your choice and you want to double-check, here are some examples on how to check if the node is configured properly. + +#### Check from your VPS + +Additional details can be obtained via various methods after you connect to your VPS: + +##### Socket statistics with `ss` + +``` +sudo ss -s -t | grep 1789 # if you have specified a different port in your mixnode config, change accordingly +``` + +This command should return a lot of data containing `ESTAB`. This command should work on every unix based system. + +##### List open files and reliant processes with `lsof` + +``` +# check if lsof is installed: +lsof -v +# install if not installed +sudo apt install lsof +# run against mixnode port +sudo lsof -i TCP:1789 # if you have specified a different port in your mixnode config, change accordingly +``` + +This command should return something like this: + +``` +nym-mixno 103349 root 53u IPv6 1333229972 0t0 TCP [2a03:b0c0:3:d0::ff3:f001]:57844->[2a01:4f9:c011:38ae::5]:1789 (ESTABLISHED) +nym-mixno 103349 root 54u IPv4 1333229973 0t0 TCP nym:57104->194.5.78.73:1789 (ESTABLISHED) +nym-mixno 103349 root 55u IPv4 1333229974 0t0 TCP nym:48130->static.236.109.119.168.clients.your-server.de:1789 (ESTABLISHED) +nym-mixno 103349 root 56u IPv4 1333229975 0t0 TCP nym:52548->vmi572614.contaboserver.net:1789 (ESTABLISHED) +nym-mixno 103349 root 57u IPv6 1333229976 0t0 TCP [2a03:b0c0:3:d0::ff3:f001]:43244->[2600:1f18:1031:2401:c04b:2f25:ca79:fef3]:1789 (ESTABLISHED) +``` + +##### Query `systemd` journal with `journalctl` + +``` +sudo journalctl -u nym-mixnode -o cat | grep "Since startup mixed" +``` + +If you have created `nym-mixnode.service` file (i.e. you are running your mixnode via `systemd`) then this command shows you how many packets have you mixed so far, and should return a list of messages like this: + +``` +2021-05-18T12:35:24.057Z INFO nym_mixnode::node::metrics > Since startup mixed 233639 packets! +2021-05-18T12:38:02.178Z INFO nym_mixnode::node::metrics > Since startup mixed 233739 packets! +2021-05-18T12:40:32.344Z INFO nym_mixnode::node::metrics > Since startup mixed 233837 packets! +2021-05-18T12:46:08.549Z INFO nym_mixnode::node::metrics > Since startup mixed 234081 packets! +2021-05-18T12:56:57.129Z INFO nym_mixnode::node::metrics > Since startup mixed 234491 packets! +``` + +You can add ` | tail` to the end of the command to watch for new entries in real time if needed. + +##### build-info + +A `build-info` command prints the build information like commit hash, rust version, binary version just like what command `--version` does. However, you can also specify an `--output=json` flag that will format the whole output as a json, making it an order of magnitude easier to parse. + +For example `./target/debug/nym-network-requester --no-banner build-info --output json` will return: + +``` +{"binary_name":"nym-network-requester","build_timestamp":"2023-07-24T15:38:37.00657Z","build_version":"1.1.23","commit_sha":"c70149400206dce24cf20babb1e64f22202672dd","commit_timestamp":"2023-07-24T14:45:45Z","commit_branch":"feature/simplify-cli-parsing","rustc_version":"1.71.0","rustc_channel":"stable","cargo_profile":"debug"} +``` + +#### Check from your local machine + +##### Scan ports with `nmap`: + +``` +nmap -p 1789 -Pn +``` + +If your mixnode is configured properly it should output something like this: + +``` +bob@desktop:~$ nmap -p 1789 95.296.134.220 -Pn + +Host is up (0.053s latency). + +PORT STATE SERVICE +1789/tcp open hello +``` + +##### Query online nodes: + +``` +curl --location --request GET 'https://validator.nymtech.net/api/v1/mixnodes/' +``` + +Will return a list all nodes currently online. + +You can query gateways by replacing `mixnodes` with `gateways` in the above command, and can query for the mixnodes and gateways on the Sandbox testnet by replacing `validator` with `sandbox-validator`. + + +#### Check with Network API + +We currently have an API set up returning our metrics tests of the network. There are two endpoints to ping for information about your mixnode, `report` and `history`. Find more information about this in the [Mixnodes metrics documentation](./maintenance.md#metrics--api-endpoints). + +### Why is my node not mixing any packets? + +If you are still unable to see your node on the dashboard, or your node is declaring it has not mixed any packets, there are several potential issues: + +- The firewall on your host machine is not configured properly. Checkout the [instructions](./maintenance.md#configure-your-firewall). +- You provided incorrect information when bonding your node. +- You are running your mixnode from a VPS without IPv6 support. +- You did not use the `--announce-host` flag while running the mixnode from your local machine behind NAT. +- You did not configure your router firewall while running the mixnode from your local machine behind NAT, or you are lacking IPv6 support. +- Your mixnode is not running at all, it has either exited / panicked or you closed the session without making the node persistent. Check out the [instructions](./maintenance.md#automating-your-node-with-tmux-and-systemd). + +```admonish caution +Your mixnode **must speak both IPv4 and IPv6** in order to cooperate with other nodes and route traffic. This is a common reason behind many errors we are seeing among node operators, so check with your provider that your VPS is able to do this! +``` + +#### Incorrect bonding information + +Check that you have provided the correct information when bonding your mixnode in the web wallet [interface](https://sandbox-wallet.nymtech.net/). When in doubt, un-bond and then re-bond your node! + +> All delegated stake will be lost when un-bonding! However the mixnode must be operational in the first place for the delegation to have any effect. + +#### Missing `announce-host` flag + +On certain cloud providers such as AWS and Google Cloud, you need to do some additional configuration of your firewall and use `--host` with your **local ip** and `--announce-host` with the **public ip** of your mixnode host. + +If the difference between the two is unclear, contact the help desk of your VPS provider. + +#### No IPv6 connectivity + +Make sure that your VPS has IPv6 connectivity available with whatever provider you are using. + +To get all ip addresses of your host, try following commands: + +``` +hostname -i +``` + +Will return your **local ip** address. + +``` +hostname -I +``` + +Will return all of the ip addresses of your host. This output should look something like this: + +``` +bob@nym:~$ hostname -I +88.36.11.23 172.18.0.1 2a01:28:ca:102::1:641 +``` + +- The first **ipv4** is the public ip you need to use for the `--announce-host` flag. +- The second **ipv4** is the local ip you need to use for the `--host` flag. +- The 3rd output should confirm if your machine has ipv6 available. + +### Running on a local machine behind NAT with no fixed IP address + +Your ISP has to be IPv6 ready if you want to run a mixnode on your local machine. Sadly, in 2020, most of them are not and you won't get an IPv6 address by default from your ISP. Usually it is a extra paid service or they simply don't offer it. + +Before you begin, check if you have IPv6 [here](https://test-ipv6.cz/) or by running command explained in the [section above](./troubleshooting.md#no-ipv6-connectivity). If not, then don't waste your time to run a node which won't ever be able to mix any packet due to this limitation. Call your ISP and ask for IPv6, there is a plenty of it for everyone! + +If all goes well and you have IPv6 available, then you will need to `init` the mixnode with an extra flag, `--announce-host`. You will also need to edit your `config.toml` file each time your IPv4 address changes, that could be a few days or a few weeks. Check the your IPv4 in the [section above](./troubleshooting.md#no-ipv6-connectivity). + +Additional configuration on your router might also be needed to allow traffic in and out to port 1789 and IPv6 support. + +Here is a sample of the `init` command example to create the mixnode config. + +``` +./nym-mixnode init --id --host 0.0.0.0 --announce-host 85.160.12.13 +``` + +- `--host 0.0.0.0` should work every time even if your local machine IPv4 address changes. For example on Monday your router gives your machine an address `192.168.0.13` and on Wednesday, the DHCP lease will end and you will be asigned `192.168.0.14`. Using `0.0.0.0` should avoid this without having to set any static ip in your router`s configuration. + +- you can get your current IPv4 address by either using `curl ipinfo.io` if you're on MacOS or Linux or visiting [whatsmyip site](https://www.whatsmyip.org/). Simply copy it and use it as `--anounce-host` address. + +Make sure you check if your node is really mixing. We are aiming to improve the setup for operators running locally, however you may need a bit of patience to set this up from your home behind NAT. + +### Accidentally killing your node process on exiting session + +When you close your current terminal session, you need to make sure you don't kill the mixnode process! There are multiple ways on how to make it persistent even after exiting your ssh session, the easiest solution is to use `tmux` or `nohup`, and the more elegant solution is to run the node with `systemd`. Read the automation manual [here](./maintenance.md#automating-your-node-with-tmux-and-systemd). + +### Common errors and warnings + +Most of the `ERROR` and `WARN` messages in your node logs are benign - as long as your node outputs `since startup mixed X packets!` (`X` bust be > 0) in your logs (and this number increases over time), your node is mixing packets. If you want to be sure, check the Nym [dashboard](https://sandbox-explorer.nymtech.net/) or see other ways on how to check if your node is mixing properly as outlined in the section [**How can I tell my node is up and running and mixing traffic?**](./troubleshooting.md#how-can-i-tell-my-node-is-up-and-running-and-mixing-traffic?) above. + +More specific errors and warnings are covered below. + +#### `tokio runtime worker` error + +If you are running into issues with an error including the following: + +``` +thread 'tokio-runtime-worker' panicked at 'Failed to create TCP listener: Os { code: 99, kind: AddrNotAvailable, message: "Cannot assign requested address" }' +``` + +Then you need to `--announce-host ` and `--host ` on startup. This issue is addressed [above](./troubleshooting.md#missing-`announce-host`-flag) + + +### Can I use a port other than 1789 ? + +Yes! Here is what you will need to do: + +Assuming you would like to use port `1337` for your mixnode, you need to open the new port (and close the old one): + +``` +sudo ufw allow 1337 +sudo ufw deny 1789 +``` + +And then edit the mixnode's config. + +> If you want to change the port for an already running node, you need to stop the process before editing your config file. + +The config file is located at `~/.nym/mixnodes//config/config.toml`. + +For example, assuming `` was chosen to be `alice-node`: + +``` +nano ~/.nym/mixnodes/alice-node/config/config.toml +``` + +You will need to edit two parts of the file. `announce_address` and `listening_address` in the config.toml file. Simply replace `:1789` (the default port) with `:1337` (your new port) after your IP address. + +Finally, restart your node. You should see if the mixnode is using the port you have changed in the config.toml file right after you run the node. + +### What is `verloc` and do I have to configure my mixnode to implement it? + +`verloc` is short for _verifiable location_. Mixnodes and gateways now measure speed-of-light distances to each other, in an attempt to verify how far apart they are. In later releases, this will allow us to algorithmically verify node locations in a non-fakeable and trustworthy manner. + +You don't have to do any additional configuration for your node to implement this, it is a passive process that runs in the background of the mixnet from version `0.10.1` onward. + +## Validators + ### Common reasons for your validator being jailed The most common reason for your validator being jailed is that your validator is out of memory because of bloated syslogs. @@ -16,3 +326,10 @@ Running the command `df -H` will return the size of the various partitions of yo If the `/dev/sda` partition is almost full, try pruning some of the `.gz` syslog archives and restart your validator process. + +## Where can I get more help? + +The fastest way to reach one of us or get a help from the community, visit our [Telegram Node Setup Help Chat](https://t.me/nymchan_help_chat) or head to our [Discord](https://Discord.gg/nym). + +For more tech heavy question join our [Matrix core community channel](https://matrix.to/#/#general:nymtech.chat), where you can meet other builders and Nym core team members. + From 50b755cfde74f639cca8369e6b263c977b010fc6 Mon Sep 17 00:00:00 2001 From: serinko <97586125+serinko@users.noreply.github.com> Date: Thu, 27 Jul 2023 13:01:09 +0200 Subject: [PATCH 212/361] corrected scp -3 option --- documentation/operators/src/nodes/maintenance.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/documentation/operators/src/nodes/maintenance.md b/documentation/operators/src/nodes/maintenance.md index d809027150..26d5375a7d 100644 --- a/documentation/operators/src/nodes/maintenance.md +++ b/documentation/operators/src/nodes/maintenance.md @@ -133,7 +133,7 @@ mkdir ~/.nym/mixnodes ``` * Move the node data (keys) and config file to the new machine by opening a local terminal (as that one's ssh key is authorized in both of the machines) and running: ``` -scp -r @:~/.nym/mixnodes/ @:~/.nym/mixnodes/ +scp -r -3 @:~/.nym/mixnodes/ @:~/.nym/mixnodes/ ``` * Re-run init (remember that init doesn't overwrite existing keys) to generate a config with the new listening address etc. * Change the node smart contract info via the wallet interface. Otherwise the keys will point to the old IP address in the smart contract, and the node will not be able to be connected, and it will fail up-time checks. From b006e1820f3fa497de6b5633176a10e9bc17bbca Mon Sep 17 00:00:00 2001 From: serinko <97586125+serinko@users.noreply.github.com> Date: Fri, 28 Jul 2023 13:22:40 +0200 Subject: [PATCH 213/361] faq.md: initial questions and answers - in progress --- documentation/operators/src/faq.md | 67 ++- .../operators/src/nodes/validator-setup.md | 558 ++++++++++++++++++ 2 files changed, 620 insertions(+), 5 deletions(-) create mode 100644 documentation/operators/src/nodes/validator-setup.md diff --git a/documentation/operators/src/faq.md b/documentation/operators/src/faq.md index 88836f17f2..4fbc17694a 100644 --- a/documentation/operators/src/faq.md +++ b/documentation/operators/src/faq.md @@ -1,10 +1,10 @@ +---> +# Frequently Asked Questions + +## Mixnet nodes + +### What determines the rewards when running a mixnode? + +The stake required for a mix node to achieve maximum rewards is called to the mix node saturation point os simply stake saturation point. This is calculated from the staking supply (all circulating supply + part of unlocked tokens). The target level of staking is to have 50% of the staking supply locked in mix nodes. + +The node stake saturation point, which we denote by Nsat, is given by the stake supply, target level of staking divided by the number of rewarded (active) nodes. + +This design ensures the nodes aim to have a same size of stake (reputation) which can be done by delegation staking, as well as it ensures that there is a decentralization of staking as any higher level of staked tokens per node results in worse rewards. On the contrary, the more mix nodes are active, the lower is Nsat. The equilibrium is reached when the staked tokens are delegated equally across the active mix-nodes and that's our basis for this incentive system. + +For more detailed calculation, read our blog post [Nym Token Economics update](https://blog.nymtech.net/nym-token-economics-update-fedff0ed5267). More info on staking can be found [here](https://blog.nymtech.net/staking-in-nym-introducing-mainnet-mixmining-f9bb1cbc7c36). And [here](https://blog.nymtech.net/want-to-stake-in-nym-here-is-how-to-choose-a-mix-node-to-delegate-nym-to-c3b862add165) is more info on how to choose a mix node for delegation. And finally an [update](https://blog.nymtech.net/quarterly-token-economic-parameter-update-b2862948710f) on token economics from July 2023. + +### Which VPS providers would you recommend? + +Consider in which jurisdiction they reside and where they want to run a mix node. Do you want to pay by crypto or not and what are the other important particularities for your case? We always recommend operators to try to chose smaller and decentralized VPS providers over the most known ones controlling a majority of the internet. We recieve some good feedback on these: Hertzner, DigitalOcean, Linode, Ghandi and Exoscale. Do your own research and share with the community. + +### Why is a mixnode setup on a self-hosted machine so tricky? + +### What's the Sphinx packet size? + +The sizes are shown in the configs [here](https://github.com/nymtech/nym/blob/1ba6444e722e7757f1175a296bed6e31e25b8db8/common/nymsphinx/params/src/packet_sizes.rs#L12) (default is the one clients use, the others are for research purposes, not to be used in production as this would fragment the anonymity set). More info can be found [here](https://github.com/nymtech/nym/blob/4844ac953a12b29fa27688609ec193f1d560c996/common/nymsphinx/anonymous-replies/src/reply_surb.rs#L80). + +### Why a mix node and a gateway cannot be bond to the same wallet? + +Becauase of the way the smart contract works we keep it one-node one-address at the moment. + +### Which nodes are the most needed to be setup to strengthen Nym infrastructure and which ones bring rewards? + +Right now only mix nodes are rewarded. We're working on gateway and service payments. Gateways are the weak link right now due mostly to lack of incentivisation. Services like Network Requesters are obviously the most necessary for people to start using the platform, and we're working on smart contracts to allow for people to start advertising them the same way they do mix nodes. + +### Are mixnodes whitelisted? + +Nope, anyone can run a mixnode. Purely reliant on the node's reputation (self stake + delegations) & routing score. + +## Validators and tokens + +### What's the difference between NYM and uNYM? + +1 NYM = 1 000 000 uNYM + + + +### Can I run a validator? + +We are currently working towards building up a closed set of reputable validators. You can ask us for coins to get in, but please don't be offended if we say no - validators are part of our system's core security and we are starting out with people we already know or who have a solid reputation. + +### Why is validator set entry whitelisted? + +We understand that the early days of the Nyx blockchain will face possible vulnerabilities in terms of size - easy to disrupt or halt the chain if a malicious party entered with a large portion of stake. Besides that, there are some legal issues we need to address before we can distribute the validator set in a fully permissions fashion. + +### Why does Nym do many airdrops? + +It is part of ensuring decentralisation - we need to avoid a handful of people having too much control over the token and market. Of course ideally people will stake the tokens and contribute to the project at this stage. We run surveys to better understand what people are doing with their tokens and what usability issues there are for staking. Any feedback is appreciated as it helps us improve all aspects of using the token and participating in the ecosystem. diff --git a/documentation/operators/src/nodes/validator-setup.md b/documentation/operators/src/nodes/validator-setup.md new file mode 100644 index 0000000000..6eafb49106 --- /dev/null +++ b/documentation/operators/src/nodes/validator-setup.md @@ -0,0 +1,558 @@ + +# Validators + +> Nym has two main codebases: +> - the [Nym platform](https://github.com/nymtech/nym), written in Rust. This contains all of our code except for the validators. +> - the [Nym validators](https://github.com/nymtech/nyxd), written in Go. + +The validator is built using [Cosmos SDK](https://cosmos.network) and [Tendermint](https://tendermint.com), with a [CosmWasm](https://cosmwasm.com) smart contract controlling the directory service, node bonding, and delegated mixnet staking. + +> We are currently working towards building up a closed set of reputable validators. You can ask us for coins to get in, but please don't be offended if we say no - validators are part of our system's core security and we are starting out with people we already know or who have a solid reputation. + +## Building your validator + +> Any syntax in `<>` brackets is a user's unique variable. Exchange it with a corresponding name without the `<>` brackets. + +### Prerequisites +#### `git`, `gcc`, `jq` + +* Debian-based systems: +``` +apt install git build-essential jq + +# optional additional manual pages can be installed with: +apt-get install manpages-dev +``` + +* Arch-based systems: +Install `git`, `gcc` and `jq` with the following: +``` +pacman -S git gcc jq +``` + +#### `Go` +`Go` can be installed via the following commands (taken from the [Go Download and install page](https://go.dev/doc/install)): + +``` +# First remove any existing old Go installation and extract the archive you just downloaded into /usr/local: +# You may need to run the command as root or through sudo +rm -rf /usr/local/go && tar -C /usr/local -xzf go1.20.6.linux-amd64.tar.gz + +# Add /usr/local/go/bin to the PATH environment variable +export PATH=$PATH:/usr/local/go/bin +source $HOME/.profile +``` + + +Verify `Go` is installed with: + +``` +go version +# Should return something like: +go version go1.20.4 linux/amd64 +``` + +### Download a precompiled validator binary +You can find pre-compiled binaries for Ubuntu `22.04` and `20.04` [here](https://github.com/nymtech/nyxd/releases). + +```admonish caution title="" +There are seperate releases for Mainnet and the Sandbox testnet - make sure to download the correct binary to avoid `bech32Prefix` mismatches. +``` + +### Manually compiling your validator binary +The codebase for the Nyx validators can be found [here](https://github.com/nymtech/nyxd). + +The validator binary can be compiled by running the following commands: +``` +git clone https://github.com/nymtech/nyxd.git +cd nyxd +git checkout release/ + +# Mainnet +make build + +# Sandbox testnet +BECH32_PREFIX=nymt make build +``` + +At this point, you will have a copy of the `nyxd` binary in your `build/` directory. Test that it's compiled properly by running: + +``` +./build/nyxd +``` + +You should see a similar help menu printed to you: + +~~~admonish example collapsible=true title="Console output" +``` +Wasm Daemon (server) + +Usage: + nyxd [command] + +Available Commands: + add-genesis-account Add a genesis account to genesis.json + add-wasm-genesis-message Wasm genesis subcommands + collect-gentxs Collect genesis txs and output a genesis.json file + config Create or query an application CLI configuration file + debug Tool for helping with debugging your application + export Export state to JSON + gentx Generate a genesis tx carrying a self delegation + help Help about any command + init Initialize private validator, p2p, genesis, and application configuration files + keys Manage your application's keys + query Querying subcommands + rollback rollback cosmos-sdk and tendermint state by one height + start Run the full node + status Query remote node for status + tendermint Tendermint subcommands + tx Transactions subcommands + validate-genesis validates the genesis file at the default location or at the location passed as an arg + version Print the application binary version information + +Flags: + -h, --help help for nyxd + --home string directory for config and data (default "/home/willow/.nyxd") + --log_format string The logging format (json|plain) (default "plain") + --log_level string The logging level (trace|debug|info|warn|error|fatal|panic) (default "info") + --trace print out full stack trace on errors + +Use "nyxd [command] --help" for more information about a command. + +``` +~~~ + +### Linking `nyxd` to `libwasmvm.so` + +`libwasmvm.so` is the wasm virtual machine which is needed to execute smart contracts in `v0.26.1`. This file is renamed in `libwasmvm.x86_64.so` in `v0.31.1`. + +If you downloaded your `nyxd` binary from Github, you will have seen this file when un-`tar`-ing the `.tar.gz` file from the releases page. + +If you are seeing an error concerning this file when trying to run `nyxd`, then you need to move the `libwasmvm.so` file to correct location. + +Simply `cp` or `mv` that file to `/lib/x86_64-linux-gnu/` and re-run `nyxd`. + +### Adding `nyxd` to your `$PATH` +You'll need to set `LD_LIBRARY_PATH` in your user's `~/.bashrc` or `~/.zshrc` file (depends on the terminal you use), and add that to our path. Replace `/home///binaries` in the command below to the locations of `nyxd` and `libwasmvm.so` and run it. If you have compiled these on the server, they will be in the `build/` folder: + +``` +NYX_BINARIES=/home/// + +# if you are using another shell like zsh replace '.bashrc' with the relevant config file +echo 'export LD_LIBRARY_PATH=${LD_LIBRARY_PATH}:'NYX_BINARIES >> ~/.bashrc +echo 'export PATH=$PATH:'${NYX_BINARIES} >> ~/.bashrc +source ~/.bashrc +``` + +Test everything worked: + +``` +nyxd +``` + +This should return the regular help menu: + +~~~admonish example collapsible=true title="Console output" +``` +Wasm Daemon (server) + +Usage: + nyxd [command] + +Available Commands: + add-genesis-account Add a genesis account to genesis.json + add-wasm-genesis-message Wasm genesis subcommands + collect-gentxs Collect genesis txs and output a genesis.json file + config Create or query an application CLI configuration file + debug Tool for helping with debugging your application + export Export state to JSON + gentx Generate a genesis tx carrying a self delegation + help Help about any command + init Initialize private validator, p2p, genesis, and application configuration files + keys Manage your application's keys + query Querying subcommands + rollback rollback cosmos-sdk and tendermint state by one height + start Run the full node + status Query remote node for status + tendermint Tendermint subcommands + tx Transactions subcommands + validate-genesis validates the genesis file at the default location or at the location passed as an arg + version Print the application binary version information + +Flags: + -h, --help help for nyxd + --home string directory for config and data (default "/home/willow/.nyxd") + --log_format string The logging format (json|plain) (default "plain") + --log_level string The logging level (trace|debug|info|warn|error|fatal|panic) (default "info") + --trace print out full stack trace on errors + +Use "nyxd [command] --help" for more information about a command. + +``` +~~~ + +## Initialising your validator +### Prerequisites: + +- FQDN Domain name +- IPv4 and IPv6 connectivity + +Choose a name for your validator and use it in place of `` in the following command: + +``` +# Mainnet +nyxd init --chain-id=nyx + +# Sandbox testnet +nyxd init --chain-id=sandbox +``` + +```admonish caution title="" +`init` generates `priv_validator_key.json` and `node_key.json`. + +If you have already set up a validator on a network, **make sure to back up the key located at** +`~/.nyxd/config/priv_validator_key.json`. + +If you don't save the validator key, then it can't sign blocks and will be jailed all the time, and +there is no way to deterministically (re)generate this key. +``` + +At this point, you have a new validator, with its own genesis file located at `$HOME/.nyxd/config/genesis.json`. You will need to replace the contents of that file that with either the Nyx Mainnet or Sandbox Testnet genesis file. + +You can use the following command to download them for the correct network: + +``` +# Mainnet +wget -O $HOME/.nyxd/config/genesis.json https://nymtech.net/genesis/genesis.json + +# Sandbox testnet +wget -O $HOME/.nyxd/config/genesis.json https://sandbox-validator1.nymtech.net/snapshots/genesis.json +``` + +### `config.toml` configuration + +Edit the following config options in `$HOME/.nyxd/config/config.toml` to match the information below for your network: + +``` +# Mainnet +persistent_peers = "ee03a6777fb76a2efd0106c3769daaa064a3fcb5@51.79.21.187:26656" +create_empty_blocks = false +laddr = "tcp://0.0.0.0:26656" +``` + +``` +# Sandbox testnet +cors_allowed_origins = ["*"] +persistent_peers = "8421c0a3d90d490e27e8061f2abcb1276c8358b6@sandbox-validator1.nymtech.net:26666" +create_empty_blocks = false +laddr = "tcp://0.0.0.0:26656" +``` + +These affect the following: +* `persistent_peers = "@.nymtech.net:26666"` allows your validator to start pulling blocks from other validators. **The main sandbox validator listens on `26666` instead of the default `26656` for debugging**. It is recommended you do not change your port from `26656`. +* `create_empty_blocks = false` will save space +* `laddr = "tcp://0.0.0.0:26656"` is in your p2p configuration options + +Optionally, if you want to enable [Prometheus](https://prometheus.io/) metrics then the following must also match in the `config.toml`: + +- `prometheus = true` +- `prometheus_listen_addr = ":26660"` + +> Remember to enable metrics in the 'Configuring Prometheus metrics' section below as well. + +And if you wish to add a human-readable moniker to your node: + +- `moniker = ""` + +Finally, if you plan on using [Cockpit](https://cockpit-project.org/documentation.html) on your server, change the `grpc` port from `9090` as this is the port used by Cockpit. + +### `app.toml` configuration +In the file `$HOME/nyxd/config/app.toml`, set the following values: + +``` +# Mainnet +minimum-gas-prices = "0.025unym,0.025unyx" +enable = true in the `[api]` section to get the API server running +``` +``` +# Sandbox Testnet +minimum-gas-prices = "0.025unymt,0.025unyxt" +enable = true` in the `[api]` section to get the API server running +``` + +### Setting up your validator's admin user +You'll need an admin account to be in charge of your validator. Set that up with: + +``` +nyxd keys add nyxd-admin +``` + +This will add keys for your administrator account to your system's keychain and log your name, address, public key, and mnemonic. As the instructions say, remember to **write down your mnemonic**. + +You can get the admin account's address with: + +``` +nyxd keys show nyxd-admin -a +``` + +Type in your keychain **password**, not the mnemonic, when asked. + +## Starting your validator + +```admonish caution title="" +If you are running a Sandbox testnet validator, please skip the `validate-genesis` command: it will fail due to the size of the genesis file as this is a fork of an existing chain state. +``` + +Everything should now be ready to go. You've got the validator set up, all changes made in `config.toml` and `app.toml`, the Nym genesis file copied into place (replacing the initial auto-generated one). Now let's validate the whole setup: + +``` +nyxd validate-genesis +``` + +If this check passes, you should receive the following output: + +``` +File at /path/to/genesis.json is a valid genesis file +``` + +> If this test did not pass, check that you have replaced the contents of `//.nymd/config/genesis.json` with that of the correct genesis file. + +### Open firewall ports + +Before starting the validator, we will need to open the firewall ports: + +``` +# if ufw is not already installed: +sudo apt install ufw +sudo ufw enable +sudo ufw allow 1317,26656,26660,22,80,443/tcp +# to check everything worked +sudo ufw status +``` + +Ports `22`, `80`, and `443` are for ssh, http, and https connections respectively. The rest of the ports are documented [here](https://docs.cosmos.network/main/core/grpc_rest). + +For more information about your validator's port configuration, check the [validator port reference table](https://nymtech.net/operators/binaries/maintenance.html#ports) below. + +> If you are planning to use [Cockpit](https://cockpit-project.org/) on your validator server then you will have defined a different `grpc` port in your `config.toml` above: remember to open this port as well. + +Start the validator: + +``` +nyxd start +``` + +Once your validator starts, it will start requesting blocks from other validators. This may take several hours. Once it's up to date, you can issue a request to join the validator set with the command below. + +### Syncing from a snapshot +If you wish to sync from a snapshot on **mainnet** use Polkachu's [mainnet](https://polkachu.com/networks/nym) resources. + +If you wish to sync from a snapshot on **Sandbox testnet** use the below commands, which are a modified version of Polkachu's excellent resources. These commands assume you are running an OS with `apt` as the package manager: + +``` +# install lz4 if necessary +sudo apt install snapd -y +sudo snap install lz4 + +# download the snapshot +wget -O nyxd-sandbox-snapshot-data.tar.lz4 https://sandbox-validator1.nymtech.net/snapshots/nyxd-sandbox-snapshot-data.tar.lz4 + +# reset your validator state +nyxd tendermint unsafe-reset-all + +# unpack the snapshot +lz4 -c -d nyxd-sandbox-snapshot-data.tar.lz4 | tar -x -C $HOME/.nyxd +``` + +You can then restart `nyxd` - it should start syncing from a block > 2000000. + +### Joining Consensus +```admonish caution title="" +When joining consensus, make sure that you do not disrupt (or worse - halt) the network by coming in with a disproportionately large amount of staked tokens. + +Please initially stake a small amount of tokens compared to existing validators, then delegate to yourself in tranches over time. +``` + +Once your validator has synced and you have received tokens, you can join consensus and produce blocks. + +``` +# Mainnet +nyxd tx staking create-validator + --amount=10000000unyx + --fees=0unyx + --pubkey=$(/home///nyxd/binaries/nyxd tendermint show-validator) + --moniker="" + --chain-id=nyx + --commission-rate="0.10" + --commission-max-rate="0.20" + --commission-max-change-rate="0.01" + --min-self-delegation="1" + --gas="auto" + --gas-adjustment=1.15 + --from="KEYRING_NAME" + --node https://rpc-1.nyx.nodes.guru:443 +``` +``` +# Sandbox Testnet +nyxd tx staking create-validator + --amount=10000000unyxt + --fees=5000unyxt + --pubkey=$(/home///nym/binaries/nyxd tendermint show-validator) + --moniker="" + --chain-id=sandbox + --commission-rate="0.10" + --commission-max-rate="0.20" + --commission-max-change-rate="0.01" + --min-self-delegation="1" + --gas="auto" + --gas-adjustment=1.15 + --from="KEYRING_NAME" + --node https://sandbox-validator1.nymtech.net:443 +``` + +You'll need either `unyxt` tokens on Sandbox, or `unyx` tokens on mainnet to perform this command. + +> We are currently working towards building up a closed set of reputable validators. You can ask us for coins to get in, but please don't be offended if we say no - validators are part of our system's core security and we are starting out with people we already know or who have a solid reputation. + +If you want to edit some details for your node you will use a command like this: + +``` +# Mainnet +nyxd tx staking edit-validator + --chain-id=nyx + --moniker="" + --details="Nyx validator" + --security-contact="" + --identity="" + --gas="auto" + --gas-adjustment=1.15 + --from="KEYRING_NAME" + --fees 2000unyx +``` +``` +# Sandbox testnet +nyxd tx staking edit-validator + --chain-id=sandbox + --moniker="" + --details="Sandbox testnet validator" + --security-contact="your email" + --identity="" + --gas="auto" + --gas-adjustment=1.15 + --from="KEYRING_NAME" + --fees 2000unyxt +``` + +With above command you can specify the `gpg` key last numbers (as used in `keybase`) as well as validator details and your email for security contact. + +### Automating your validator with systemd +You will most likely want to automate your validator restarting if your server reboots. Checkout the [maintenance page](https://nymtech.net/operators/binaries/maintenance.html#systemd) with a quick tutorial. + +### Installing and configuring nginx for HTTPS + +If you want to set up a reverse proxying on the validator server to improve security and performance, using [nginx](https://www.nginx.com/resources/glossary/nginx/#:~:text=NGINX%20is%20open%20source%20software,%2C%20media%20streaming%2C%20and%20more.&text=In%20addition%20to%20its%20HTTP,%2C%20TCP%2C%20and%20UDP%20servers.), follow the manual on the [maintenance page](https://nymtech.net/operators/binaries/maintenance.html#setup). + +### Setting the ulimit + +Linux machines limit how many open files a user is allowed to have. This is called a `ulimit`. We need to set it to a higher value than the default 1024. Follow the instructions in the [maintenance page](https://nymtech.net/operators/binaries/maintenance.html#Setting-the-ulimit) to change the `ulimit` value for validators. + +## Using your validator +### Unjailing your validator +If your validator gets jailed, you can fix it with the following command: + +``` +# Mainnet +nyxd tx slashing unjail + --broadcast-mode=block + --from="KEYRING_NAME" + --chain-id=nyx + --gas=auto + --gas-adjustment=1.4 + --fees=7000unyx +``` +``` +# Sandbox Testnet +nyxd tx slashing unjail + --broadcast-mode=block + --from="KEYRING_NAME" + --chain-id=sandbox + --gas=auto + --gas-adjustment=1.4 + --fees=7000unyxt +``` + +### Upgrading your validator + +To upgrade your validator, follow the steps on the [maintenance page](https://nymtech.net/operators/binaries/maintenance.html#Setting-the-ulimit). + +#### Common reasons for your validator being jailed + +The most common reason for your validator being jailed is that your validator is out of memory because of bloated syslogs. + +Running the command `df -H` will return the size of the various partitions of your VPS. + +If the `/dev/sda` partition is almost full, try pruning some of the `.gz` syslog archives and restart your validator process. + +### Day 2 operations with your validator + +You can check your current balances with: + +``` +nymd query bank balances ${ADDRESS} +``` + +For example, on the Sanbox testnet this would return: + +```yaml +balances: +- amount: "919376" +denom: unymt +pagination: +next_key: null +total: "0" +``` + +You can, of course, stake back the available balance to your validator with the following command. + +> Remember to save some tokens for gas costs! + +``` +# Mainnet +nyxd tx staking delegate VALOPERADDRESS AMOUNTunym + --from="KEYRING_NAME" + --keyring-backend=os + --chain-id=nyx + --gas="auto" + --gas-adjustment=1.15 + --fees 5000unyx +``` +``` +# Sandbox Testnet +nyxd tx staking delegate VALOPERADDRESS AMOUNTunymt + --from="KEYRING_NAME" + --keyring-backend=os + --chain-id=sandbox + --gas="auto" + --gas-adjustment=1.15 + --fees 5000unyxt +``` + From f0c0ddb1c27d7b66c6f689189129265c69d5f135 Mon Sep 17 00:00:00 2001 From: serinko <97586125+serinko@users.noreply.github.com> Date: Mon, 31 Jul 2023 13:25:11 +0200 Subject: [PATCH 214/361] operators: book build - minor issues remain --- documentation/operators/book.toml | 10 ++++++---- documentation/operators/src/SUMMARY.md | 3 +-- documentation/operators/src/faq.md | 2 +- .../operators/src/nodes/mix-node-setup.md | 14 -------------- 4 files changed, 8 insertions(+), 21 deletions(-) diff --git a/documentation/operators/book.toml b/documentation/operators/book.toml index c39bb1c6a1..4202abc3f9 100644 --- a/documentation/operators/book.toml +++ b/documentation/operators/book.toml @@ -52,9 +52,11 @@ minimum_rust_version = "1.66" platform_release_version = "v1.1.22" wallet_release_version = "v1.2.5" -[preprocessor.last-changed] -command = "mdbook-last-changed" -renderer = ["html"] + +# [preprocessor.last-changed] +# command = "mdbook-last-changed" +# renderer = ["html"] + # used for grabbing output of binary commands for automation # https://github.com/FauconFan/mdbook-cmdrun @@ -85,7 +87,7 @@ curly-quotes = true # mathjax-support = false # useful if we want to pull equations in copy-fonts = true no-section-label = false -additional-css = ["theme/pagetoc.css", "././mdbook-admonish.css", "last-changed.css"] +additional-css = ["theme/pagetoc.css", "././mdbook-admonish.css"] #,"last-changed.css"] additional-js = ["theme/pagetoc.js"] git-repository-url = "https://github.com/nymtech/nym" git-repository-icon = "fa-github" diff --git a/documentation/operators/src/SUMMARY.md b/documentation/operators/src/SUMMARY.md index f767aec6f7..04d855952f 100644 --- a/documentation/operators/src/SUMMARY.md +++ b/documentation/operators/src/SUMMARY.md @@ -18,6 +18,5 @@ - [Nyx Validator Setup](./nodes/validator-setup.md) - [Maintenance](./nodes/maintenance.md) - [Troubleshooting](./nodes/troubleshooting.md) - + diff --git a/documentation/operators/src/faq.md b/documentation/operators/src/faq.md index 4fbc17694a..0de60740bd 100644 --- a/documentation/operators/src/faq.md +++ b/documentation/operators/src/faq.md @@ -31,7 +31,7 @@ For more detailed calculation, read our blog post [Nym Token Economics update](h ### Which VPS providers would you recommend? -Consider in which jurisdiction they reside and where they want to run a mix node. Do you want to pay by crypto or not and what are the other important particularities for your case? We always recommend operators to try to chose smaller and decentralized VPS providers over the most known ones controlling a majority of the internet. We recieve some good feedback on these: Hertzner, DigitalOcean, Linode, Ghandi and Exoscale. Do your own research and share with the community. +Consider in which jurisdiction you reside and where do you want to run a mix node. Do you want to pay by crypto or not and what are the other important particularities for your case? We always recommend operators to try to chose smaller and decentralized VPS providers over the most known ones controlling a majority of the internet. We recieve some good feedback on these: Hertzner, DigitalOcean, Linode, Ghandi and Exoscale. Do your own research and share with the community. ### Why is a mixnode setup on a self-hosted machine so tricky? diff --git a/documentation/operators/src/nodes/mix-node-setup.md b/documentation/operators/src/nodes/mix-node-setup.md index 7e1cafa64c..f8981fed68 100644 --- a/documentation/operators/src/nodes/mix-node-setup.md +++ b/documentation/operators/src/nodes/mix-node-setup.md @@ -1,17 +1,3 @@ - - # Mix Nodes > The Nym mix node binary was built in the [building nym](https://nymtech.net/operators/binaries/building-nym.html) section. If you haven't yet built Nym and want to run the code, go there first. From a0418daaea504216703818b0220bf5e714198acb Mon Sep 17 00:00:00 2001 From: serinko <97586125+serinko@users.noreply.github.com> Date: Mon, 31 Jul 2023 13:47:57 +0200 Subject: [PATCH 215/361] operators: comment missing info --- documentation/operators/src/faq.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/documentation/operators/src/faq.md b/documentation/operators/src/faq.md index 0de60740bd..3c832b68ed 100644 --- a/documentation/operators/src/faq.md +++ b/documentation/operators/src/faq.md @@ -33,7 +33,7 @@ For more detailed calculation, read our blog post [Nym Token Economics update](h Consider in which jurisdiction you reside and where do you want to run a mix node. Do you want to pay by crypto or not and what are the other important particularities for your case? We always recommend operators to try to chose smaller and decentralized VPS providers over the most known ones controlling a majority of the internet. We recieve some good feedback on these: Hertzner, DigitalOcean, Linode, Ghandi and Exoscale. Do your own research and share with the community. -### Why is a mixnode setup on a self-hosted machine so tricky? + ### What's the Sphinx packet size? From 585cf55026ead8bab80d970e9f9a32d08f8654f1 Mon Sep 17 00:00:00 2001 From: serinko <97586125+serinko@users.noreply.github.com> Date: Mon, 31 Jul 2023 13:53:21 +0200 Subject: [PATCH 216/361] operators: typo edit --- documentation/operators/src/faq.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/documentation/operators/src/faq.md b/documentation/operators/src/faq.md index 3c832b68ed..faefd70b18 100644 --- a/documentation/operators/src/faq.md +++ b/documentation/operators/src/faq.md @@ -21,7 +21,7 @@ make a note that the keys will be overwritten in bold when you're noting it in t ### What determines the rewards when running a mixnode? -The stake required for a mix node to achieve maximum rewards is called to the mix node saturation point os simply stake saturation point. This is calculated from the staking supply (all circulating supply + part of unlocked tokens). The target level of staking is to have 50% of the staking supply locked in mix nodes. +The stake required for a mix node to achieve maximum rewards is called mix node saturation point. This is calculated from the staking supply (all circulating supply + part of unlocked tokens). The target level of staking is to have 50% of the staking supply locked in mix nodes. The node stake saturation point, which we denote by Nsat, is given by the stake supply, target level of staking divided by the number of rewarded (active) nodes. From 09b15ae020ead3b0553beb1feaf5d2fe4daed707 Mon Sep 17 00:00:00 2001 From: serinko <97586125+serinko@users.noreply.github.com> Date: Tue, 1 Aug 2023 10:58:29 +0200 Subject: [PATCH 217/361] operators guide: add src/binaries/ files, fix links --- documentation/operators/src/SUMMARY.md | 6 +- .../operators/src/binaries/building-nym.html | 350 ++++++++++++++++++ .../operators/src/binaries/building-nym.md | 71 ++++ .../src/binaries/init-and-config.html | 290 +++++++++++++++ .../operators/src/binaries/init-and-config.md | 13 + .../src/binaries/pre-built-binaries.html | 286 ++++++++++++++ .../src/binaries/pre-built-binaries.md | 6 + .../src/binaries/version-compatiblity.md | 38 ++ .../operators/src/nodes/gateway-setup.md | 2 +- .../operators/src/nodes/mix-node-setup.md | 4 +- .../src/nodes/network-requester-setup.md | 2 +- .../operators/src/nodes/setup-guides.md | 8 +- .../operators/src/nodes/validator-setup.md | 10 +- .../operators/src/preliminary-steps.md | 2 +- 14 files changed, 1071 insertions(+), 17 deletions(-) create mode 100644 documentation/operators/src/binaries/building-nym.html create mode 100644 documentation/operators/src/binaries/building-nym.md create mode 100644 documentation/operators/src/binaries/init-and-config.html create mode 100644 documentation/operators/src/binaries/init-and-config.md create mode 100644 documentation/operators/src/binaries/pre-built-binaries.html create mode 100644 documentation/operators/src/binaries/pre-built-binaries.md create mode 100644 documentation/operators/src/binaries/version-compatiblity.md diff --git a/documentation/operators/src/SUMMARY.md b/documentation/operators/src/SUMMARY.md index 04d855952f..a3f55376c8 100644 --- a/documentation/operators/src/SUMMARY.md +++ b/documentation/operators/src/SUMMARY.md @@ -3,9 +3,9 @@ - [Introduction](introduction.md) # Binaries -- [Pre-built Binaries](../../docs/src/binaries/pre-built-binaries.md) -- [Building from Source](../../docs/src/binaries/building-nym.md) -- [Binary Initialisation and Configuration](../../docs/src/binaries/init-and-config.md) +- [Pre-built Binaries](./binaries/pre-built-binaries.md) +- [Building from Source](./binaries/building-nym.md) +- [Binary Initialisation and Configuration](./binaries/init-and-config.md) # Operators Guides diff --git a/documentation/operators/src/binaries/building-nym.html b/documentation/operators/src/binaries/building-nym.html new file mode 100644 index 0000000000..2f73af4b4f --- /dev/null +++ b/documentation/operators/src/binaries/building-nym.html @@ -0,0 +1,350 @@ + + + + + + Building from Source - Nym Operators Guides + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

    + + + + + + + + + + + + + + +
    + +
    + + + + + + + + +
    +
    + {comment} +
    + + {comment} +
    + + {comment} +
    + + {comment} +
    + + {comment} +
    + + {comment} +
    + + {comment} +
    + + {comment} +
    + + {comment} +
    + + {comment} +
    + + {comment} +
    + + {comment} +
    + + {comment} +
    + + {comment} +
    + + {comment} +
    + + {comment} +
    + + {comment} +
    + + {comment} +
    + + {comment} +
    + + {comment} +
    + + {comment} +
    + +

    Building from Source

    +
    +

    Nym runs on Mac OS X, Linux, and Windows. All nodes except the Desktop Wallet and NymConnect on Windows should be considered experimental - it works fine if you’re an app developer but isn’t recommended for running nodes.

    +
    +

    Building Nym

    +

    Nym has two main codebases:

    +
      +
    • the Nym platform, written in Rust. This contains all of our code except for the validators.
    • +
    • the Nym validators, written in Go.
    • +
    +
    +

    This page details how to build the main Nym platform code. If you want to build and run a validator, go here instead.

    +
    +

    Prerequisites

    +
      +
    • Debian/Ubuntu: pkg-config, build-essential, libssl-dev, curl, jq, git
    • +
    +
    apt install pkg-config build-essential libssl-dev curl jq git
    +
    +
      +
    • Arch/Manjaro: base-devel
    • +
    +
    pacman -S base-devel
    +
    +
      +
    • Mac OS X: pkg-config , brew, openss1, protobuf, curl, git +Running the following the script installs Homebrew and the above dependencies:
    • +
    +
    /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
    +
    +
      +
    • Rust & cargo >= 1.66
    • +
    +

    We recommend using the Rust shell script installer. Installing cargo from your package manager (e.g. apt) is not recommended as the packaged versions are usually too old.

    +

    If you really don’t want to use the shell script installer, the Rust installation docs contain instructions for many platforms.

    +

    Download and build Nym binaries

    +

    The following commands will compile binaries into the nym/target/release directory:

    +
    rustup update
    +git clone https://github.com/nymtech/nym.git
    +cd nym
    +
    +git reset --hard # in case you made any changes on your branch
    +git pull # in case you've checked it out before
    +
    +git checkout release/v1.1.22 # checkout to the latest release branch: `develop` will most likely be incompatible with deployed public networks
    +
    +cargo build --release # build your binaries with **mainnet** configuration
    +NETWORK=sandbox cargo build --release # build your binaries with **sandbox** configuration
    +
    +

    Quite a bit of stuff gets built. The key working parts are:

    + +

    The repository also contains Typescript applications which aren’t built in this process. These can be built by following the instructions on their respective docs pages.

    + +
    +

    You cannot build from GitHub’s .zip or .tar.gz archive files on the releases page - the Nym build scripts automatically include the current git commit hash in the built binary during compilation, so the build will fail if you use the archive code (which isn’t a Git repository). Check the code out from github using git clone instead.

    +
    + +
    + + +
    +
    + + + +
    + + + + + + + + + + + + + + + + + + + + + +
    + + diff --git a/documentation/operators/src/binaries/building-nym.md b/documentation/operators/src/binaries/building-nym.md new file mode 100644 index 0000000000..8610fcb63a --- /dev/null +++ b/documentation/operators/src/binaries/building-nym.md @@ -0,0 +1,71 @@ +# Building from Source + +> Nym runs on Mac OS X, Linux, and Windows. All nodes **except the Desktop Wallet and NymConnect** on Windows should be considered experimental - it works fine if you're an app developer but isn't recommended for running nodes. + +## Building Nym +Nym has two main codebases: + +- the [Nym platform](https://github.com/nymtech/nym), written in Rust. This contains all of our code _except_ for the validators. +- the [Nym validators](https://github.com/nymtech/nyxd), written in Go. + +> This page details how to build the main Nym platform code. **If you want to build and run a validator, [go here](../nodes/validator-setup.md) instead.** + +## Prerequisites +- Debian/Ubuntu: `pkg-config`, `build-essential`, `libssl-dev`, `curl`, `jq`, `git` + +``` +apt install pkg-config build-essential libssl-dev curl jq git +``` + +- Arch/Manjaro: `base-devel` + +``` +pacman -S base-devel +``` + +- Mac OS X: `pkg-config` , `brew`, `openss1`, `protobuf`, `curl`, `git` +Running the following the script installs Homebrew and the above dependencies: + +``` +/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" +``` + +- `Rust & cargo >= {{minimum_rust_version}}` + +We recommend using the [Rust shell script installer](https://www.rust-lang.org/tools/install). Installing cargo from your package manager (e.g. `apt`) is not recommended as the packaged versions are usually too old. + +If you really don't want to use the shell script installer, the [Rust installation docs](https://forge.rust-lang.org/infra/other-installation-methods.html) contain instructions for many platforms. + +## Download and build Nym binaries +The following commands will compile binaries into the `nym/target/release` directory: + +``` +rustup update +git clone https://github.com/nymtech/nym.git +cd nym + +git reset --hard # in case you made any changes on your branch +git pull # in case you've checked it out before + +git checkout release/{{platform_release_version}} # checkout to the latest release branch: `develop` will most likely be incompatible with deployed public networks + +cargo build --release # build your binaries with **mainnet** configuration +NETWORK=sandbox cargo build --release # build your binaries with **sandbox** configuration +``` + +Quite a bit of stuff gets built. The key working parts are: + +* [mix node](../nodes/mix-node-setup.md): `nym-mixnode` +* [gateway node](../nodes/gateway-setup.md): `nym-gateway` +* [websocket client](https://nymtech.net/docs/clients/websocket-client.html): `nym-client` +* [socks5 client](https://nymtech.net/docs/clients/socks5-client.html): `nym-socks5-client` +* [webassembly client](https://nymtech.net/docs/clients/webassembly-client.html): `webassembly-client` +* [network requester](../nodes/network-requester-setup.md): `nym-network-requester` +* [nym-cli tool](https://nymtech.net/docs/tools/nym-cli.html): `nym-cli` + +The repository also contains Typescript applications which aren't built in this process. These can be built by following the instructions on their respective docs pages. +* [Nym Wallet](https://nymtech.net/docs/wallet/desktop-wallet.html) +* [Nym Connect](https://nymtech.net/developers/quickstart/nymconnect-gui.html) +* [Network Explorer UI](https://nymtech.net/docs/explorers/mixnet-explorer.html) + +> You cannot build from GitHub's .zip or .tar.gz archive files on the releases page - the Nym build scripts automatically include the current git commit hash in the built binary during compilation, so the build will fail if you use the archive code (which isn't a Git repository). Check the code out from github using `git clone` instead. diff --git a/documentation/operators/src/binaries/init-and-config.html b/documentation/operators/src/binaries/init-and-config.html new file mode 100644 index 0000000000..147c89cc2d --- /dev/null +++ b/documentation/operators/src/binaries/init-and-config.html @@ -0,0 +1,290 @@ + + + + + + Binary Initialisation and Configuration - Nym Operators Guides + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + + + + + + + + + + +
    + +
    + + + + + + + + +
    +
    + {comment} +
    + + {comment} +
    + + {comment} +
    + + {comment} +
    + + {comment} +
    + + {comment} +
    + + {comment} +
    + + {comment} +
    + + {comment} +
    + + {comment} +
    + + {comment} +
    + + {comment} +
    + + {comment} +
    + + {comment} +
    + + {comment} +
    + + {comment} +
    + + {comment} +
    + + {comment} +
    + + {comment} +
    + + {comment} +
    + + {comment} +
    + +

    Binary Initialisation and Configuration

    +

    All Nym binaries must first be initialised with init before being run.

    +

    The init command is usually where you pass flags specifying configuration arguments such as the gateway you wish to communicate with, the ports you wish your binary to listen on, etc.

    +

    The init command will also create the necessary keypairs and configuration files at ~/.nym/<BINARY_TYPE>/<BINARY_ID>/ if these files do not already exist. It will not overwrite existing keypairs if they are present.

    +

    You can reconfigure your binaries at any time by editing the config file located at ~/.nym/<BINARY_TYPE>/<BINARY_ID>/config/config.toml and restarting the binary process.

    +

    Once you have run init, you can start your binary with the run command, usually only accompanied by the id of the binary that you specified.

    +

    This id is never transmitted over the network, and is used to select which local config and key files to use for startup.

    + +
    + + +
    +
    + + + +
    + + + + + + + + + + + + + + + + + + + + + +
    + + diff --git a/documentation/operators/src/binaries/init-and-config.md b/documentation/operators/src/binaries/init-and-config.md new file mode 100644 index 0000000000..e749662b8e --- /dev/null +++ b/documentation/operators/src/binaries/init-and-config.md @@ -0,0 +1,13 @@ +# Binary Initialisation and Configuration + +All Nym binaries must first be initialised with `init` before being `run`. + +The `init` command is usually where you pass flags specifying configuration arguments such as the gateway you wish to communicate with, the ports you wish your binary to listen on, etc. + +The `init` command will also create the necessary keypairs and configuration files at `~/.nym///` if these files do not already exist. **It will not overwrite existing keypairs if they are present.** + +You can reconfigure your binaries at any time by editing the config file located at `~/.nym///config/config.toml` and restarting the binary process. + +Once you have run `init`, you can start your binary with the `run` command, usually only accompanied by the `id` of the binary that you specified. + +This `id` is **never** transmitted over the network, and is used to select which local config and key files to use for startup. diff --git a/documentation/operators/src/binaries/pre-built-binaries.html b/documentation/operators/src/binaries/pre-built-binaries.html new file mode 100644 index 0000000000..3497c15d85 --- /dev/null +++ b/documentation/operators/src/binaries/pre-built-binaries.html @@ -0,0 +1,286 @@ + + + + + + Pre-built Binaries - Nym Operators Guides + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + + + + + + + + + + +
    + +
    + + + + + + + + +
    +
    + {comment} +
    + + {comment} +
    + + {comment} +
    + + {comment} +
    + + {comment} +
    + + {comment} +
    + + {comment} +
    + + {comment} +
    + + {comment} +
    + + {comment} +
    + + {comment} +
    + + {comment} +
    + + {comment} +
    + + {comment} +
    + + {comment} +
    + + {comment} +
    + + {comment} +
    + + {comment} +
    + + {comment} +
    + + {comment} +
    + + {comment} +
    + +

    Pre-built Binaries

    +

    The Github releases page has pre-built binaries which should work on Ubuntu 20.04 and other Debian-based systems, but at this stage cannot be guaranteed to work everywhere.

    +

    If the pre-built binaries don’t work or are unavailable for your system, you will need to build the platform yourself.

    + +
    + + +
    +
    + + + +
    + + + + + + + + + + + + + + + + + + + + + +
    + + diff --git a/documentation/operators/src/binaries/pre-built-binaries.md b/documentation/operators/src/binaries/pre-built-binaries.md new file mode 100644 index 0000000000..269c26276c --- /dev/null +++ b/documentation/operators/src/binaries/pre-built-binaries.md @@ -0,0 +1,6 @@ +# Pre-built Binaries + +The [Github releases page](https://github.com/nymtech/nym/releases) has pre-built binaries which should work on Ubuntu 20.04 and other Debian-based systems, but at this stage cannot be guaranteed to work everywhere. + +If the pre-built binaries don't work or are unavailable for your system, you will need to build the platform yourself. + diff --git a/documentation/operators/src/binaries/version-compatiblity.md b/documentation/operators/src/binaries/version-compatiblity.md new file mode 100644 index 0000000000..67e8a71ce6 --- /dev/null +++ b/documentation/operators/src/binaries/version-compatiblity.md @@ -0,0 +1,38 @@ +# Version Compatibility Table + +There are numerous components to Nym which are released independently of one another aside from when breaking changes occur in the [core platform code](https://github.com/nymtech/nym/) - mix nodes, gateways, network requesters, and clients. + +Whilst in general it recommended to be running the most recent version of any software, if you cannot do that for whatever reason this table will tell you which versions of different components are mutually compatible with which platform code releases. + + +| Core Platform | SDK | Wallet | NymConnect | Network Explorer | Mixnet contract | Vesting contract | +| ---------------- | ------------- | --------------- | --------------- | ---------------- | --------------- | ---------------- | +| 1.1.13 - 1.1.14* | 1.1.7 | 1.1.12 - 1.1.13 | 1.1.12 - 1.1.13 | 1.1.2 | 1.2.0 - 1.3.0 | 1.2.0 - 1.3.0 | +| 1.1.1 - 1.1.12 | 1.1.4 - 1.1.7 | 1.1.0 - 1.1.12 | 1.1.1 - 1.1.12 | 1.1.0 - 1.1.2 | 1.1.0 - 1.1.3 | 1.1.0 - 1.1.3 | +| 1.1.0 - 1.1.1 | 1.1.4 | 1.1.0 | 1.1.0 - 1.1.1 | 1.1.0 | 1.1.0 | 1.1.0 | +| 1.1.0 | x | 1.1.0 | 1.1.0 | 1.1.0 | 1.1.0 | 1.1.0 | + +`*` the `nym-mixnode` binary is currently one point ahead of the main platform release version + +> There are seperate changelogs for [`NymConnect`](https://github.com/nymtech/nym/blob/release/{{platform_release_version}}/nym-connect/CHANGELOG.md) and the [`Desktop Wallet`](https://github.com/nymtech/nym/blob/release/{{platform_release_version}}/nym-wallet/CHANGELOG.md). The changelog referenced below is for the core platform code. + +| Platform release changelog | +| ---------------------------------------------------------------------------------------- | +| 1.1.14 ([CHANGELOG](https://github.com/nymtech/nym/blob/release/v1.1.14/CHANGELOG.md)) | +| 1.1.13 ([CHANGELOG](https://github.com/nymtech/nym/blob/release/v1.1.13/CHANGELOG.md)) | +| 1.1.12 ([CHANGELOG](https://github.com/nymtech/nym/blob/release/v1.1.12/CHANGELOG.md)) | +| 1.1.11 ([CHANGELOG](https://github.com/nymtech/nym/blob/release/v1.1.11/CHANGELOG.md)) | +| 1.1.10 ([CHANGELOG](https://github.com/nymtech/nym/blob/release/v1.1.10/CHANGELOG.md)) | +| 1.1.9 ([CHANGELOG](https://github.com/nymtech/nym/blob/release/v1.1.9/CHANGELOG.md)) | +| 1.1.8 ([CHANGELOG](https://github.com/nymtech/nym/blob/release/v1.1.8/CHANGELOG.md)) | +| 1.1.7 ([CHANGELOG](https://github.com/nymtech/nym/blob/release/v1.1.7/CHANGELOG.md)) | +| 1.1.6 ([CHANGELOG](https://github.com/nymtech/nym/blob/release/v1.1.6/CHANGELOG.md)) | +| 1.1.5 ([CHANGELOG](https://github.com/nymtech/nym/blob/release/v1.1.5/CHANGELOG.md)) | +| 1.1.4 ([CHANGELOG](https://github.com/nymtech/nym/blob/release/v1.1.4/CHANGELOG.md)) | +| 1.1.3 ([CHANGELOG](https://github.com/nymtech/nym/blob/release/v1.1.3/CHANGELOG.md)) | +| 1.1.2 ([CHANGELOG](https://github.com/nymtech/nym/blob/release/v1.1.2/CHANGELOG.md)) | +| 1.1.1 ([CHANGELOG](https://github.com/nymtech/nym/blob/release/v1.1.1/CHANGELOG.md)) | +| 1.1.0 ([CHANGELOG](https://github.com/nymtech/nym/blob/release/v1.1.0/CHANGELOG.md)) | +| 1.0.2 ([CHANGELOG](https://github.com/nymtech/nym/blob/nym-binaries-1.0.2/CHANGELOG.md)) | +| 1.0.1 ([CHANGELOG](https://github.com/nymtech/nym/blob/nym-binaries-1.0.1/CHANGELOG.md)) | +| 1.0.0 ([CHANGELOG](https://github.com/nymtech/nym/blob/nym-binaries-1.0.0/CHANGELOG.md)) | diff --git a/documentation/operators/src/nodes/gateway-setup.md b/documentation/operators/src/nodes/gateway-setup.md index cdc72700e2..31cff54c22 100644 --- a/documentation/operators/src/nodes/gateway-setup.md +++ b/documentation/operators/src/nodes/gateway-setup.md @@ -4,7 +4,7 @@ TODO ---> # Gateways -> The Nym gateway was built in the [building nym](https://nymtech.net/operators/binaries/building-nym.html) section. If you haven't yet built Nym and want to run the code, go there first. +> The Nym gateway was built in the [building nym](../binaries/building-nym.md) section. If you haven't yet built Nym and want to run the code, go there first. > Any syntax in `<>` brackets is a user's unique variable. Exchange with a corresponding name without the `<>` brackets. diff --git a/documentation/operators/src/nodes/mix-node-setup.md b/documentation/operators/src/nodes/mix-node-setup.md index f8981fed68..0e711b6faf 100644 --- a/documentation/operators/src/nodes/mix-node-setup.md +++ b/documentation/operators/src/nodes/mix-node-setup.md @@ -1,6 +1,6 @@ # Mix Nodes -> The Nym mix node binary was built in the [building nym](https://nymtech.net/operators/binaries/building-nym.html) section. If you haven't yet built Nym and want to run the code, go there first. +> The Nym mix node binary was built in the [building nym](../binaries/building-nym.md) section. If you haven't yet built Nym and want to run the code, go there first. > Any syntax in `<>` brackets is a user's unique variable. Exchange with a corresponding name without the `<>` brackets. @@ -17,7 +17,7 @@ Make sure you do the preparation listed in the [preliminary steps page](../preli ## Mix node setup -Now that you have built the [codebase](https://nymtech.net/docs/binaries/building-nym.html), set up your [wallet](https://nymtech.net/docs/wallet/desktop-wallet.html), and have a VPS with the `nym-mixnode` binary, you can set up your mix node with the instructions below. +Now that you have built the [codebase](../binaries/building-nym.md), set up your [wallet](https://nymtech.net/docs/wallet/desktop-wallet.html), and have a VPS with the `nym-mixnode` binary, you can set up your mix node with the instructions below. To begin, move to `/taget/release` directory from which you run the node commands: diff --git a/documentation/operators/src/nodes/network-requester-setup.md b/documentation/operators/src/nodes/network-requester-setup.md index 33dc3c7c90..1cfee00e9b 100644 --- a/documentation/operators/src/nodes/network-requester-setup.md +++ b/documentation/operators/src/nodes/network-requester-setup.md @@ -4,7 +4,7 @@ TODO: ---> # Network Requesters -> The Nym gateway was built in the [building nym](https://nymtech.net/operators/binaries/building-nym.html) section. If you haven't yet built Nym and want to run the code, go there first. +> The Nym gateway was built in the [building nym](../binaries/building-nym.md) section. If you haven't yet built Nym and want to run the code, go there first. > Any syntax in `<>` brackets is a user's unique variable. Exchange with a corresponding name without the `<>` brackets. diff --git a/documentation/operators/src/nodes/setup-guides.md b/documentation/operators/src/nodes/setup-guides.md index b19cf07deb..37955a6549 100644 --- a/documentation/operators/src/nodes/setup-guides.md +++ b/documentation/operators/src/nodes/setup-guides.md @@ -1,13 +1,13 @@ # Node Setup Guides -To setup any type of Nym's node, start with building [Nym's platform](https://nymtech.net/docs/binaries/building-nym.html) on the machine (VPS) where you want to run the node. Nodes will need to be bond to Nym's wallet, setup one [here](https://nymtech.net/docs/wallet/desktop-wallet.html). +To setup any type of Nym's node, start with building [Nym's platform](../binaries/building-nym.md) on the machine (VPS) where you want to run the node. Nodes will need to be bond to Nym's wallet, setup one [here](https://nymtech.net/docs/wallet/desktop-wallet.html). This section contains setup guides for the following node types: * [Mix node](./mix-node-setup.md) diff --git a/documentation/operators/src/nodes/validator-setup.md b/documentation/operators/src/nodes/validator-setup.md index 6eafb49106..1c843a4cab 100644 --- a/documentation/operators/src/nodes/validator-setup.md +++ b/documentation/operators/src/nodes/validator-setup.md @@ -351,7 +351,7 @@ sudo ufw status Ports `22`, `80`, and `443` are for ssh, http, and https connections respectively. The rest of the ports are documented [here](https://docs.cosmos.network/main/core/grpc_rest). -For more information about your validator's port configuration, check the [validator port reference table](https://nymtech.net/operators/binaries/maintenance.html#ports) below. +For more information about your validator's port configuration, check the [validator port reference table](./maintenance.md#ports) below. > If you are planning to use [Cockpit](https://cockpit-project.org/) on your validator server then you will have defined a different `grpc` port in your `config.toml` above: remember to open this port as well. @@ -465,15 +465,15 @@ nyxd tx staking edit-validator With above command you can specify the `gpg` key last numbers (as used in `keybase`) as well as validator details and your email for security contact. ### Automating your validator with systemd -You will most likely want to automate your validator restarting if your server reboots. Checkout the [maintenance page](https://nymtech.net/operators/binaries/maintenance.html#systemd) with a quick tutorial. +You will most likely want to automate your validator restarting if your server reboots. Checkout the [maintenance page](./maintenance.md#systemd) with a quick tutorial. ### Installing and configuring nginx for HTTPS -If you want to set up a reverse proxying on the validator server to improve security and performance, using [nginx](https://www.nginx.com/resources/glossary/nginx/#:~:text=NGINX%20is%20open%20source%20software,%2C%20media%20streaming%2C%20and%20more.&text=In%20addition%20to%20its%20HTTP,%2C%20TCP%2C%20and%20UDP%20servers.), follow the manual on the [maintenance page](https://nymtech.net/operators/binaries/maintenance.html#setup). +If you want to set up a reverse proxying on the validator server to improve security and performance, using [nginx](https://www.nginx.com/resources/glossary/nginx/#:~:text=NGINX%20is%20open%20source%20software,%2C%20media%20streaming%2C%20and%20more.&text=In%20addition%20to%20its%20HTTP,%2C%20TCP%2C%20and%20UDP%20servers.), follow the manual on the [maintenance page](./maintenance.md#setup). ### Setting the ulimit -Linux machines limit how many open files a user is allowed to have. This is called a `ulimit`. We need to set it to a higher value than the default 1024. Follow the instructions in the [maintenance page](https://nymtech.net/operators/binaries/maintenance.html#Setting-the-ulimit) to change the `ulimit` value for validators. +Linux machines limit how many open files a user is allowed to have. This is called a `ulimit`. We need to set it to a higher value than the default 1024. Follow the instructions in the [maintenance page](./maintenance.md#Setting-the-ulimit) to change the `ulimit` value for validators. ## Using your validator ### Unjailing your validator @@ -502,7 +502,7 @@ nyxd tx slashing unjail ### Upgrading your validator -To upgrade your validator, follow the steps on the [maintenance page](https://nymtech.net/operators/binaries/maintenance.html#Setting-the-ulimit). +To upgrade your validator, follow the steps on the [maintenance page](./maintenance.md#setting-the-ulimit). #### Common reasons for your validator being jailed diff --git a/documentation/operators/src/preliminary-steps.md b/documentation/operators/src/preliminary-steps.md index 1c738b34bb..5cb76d8e19 100644 --- a/documentation/operators/src/preliminary-steps.md +++ b/documentation/operators/src/preliminary-steps.md @@ -1,6 +1,6 @@ # Preliminary Steps -> The Nym mixnode, gateway and network requester binaries were built in the [building nym](https://nymtech.net/docs/binaries/building-nym.html) section. If you haven't yet built Nym and want to run the code, go there first. +> The Nym mixnode, gateway and network requester binaries were built in the [building nym](../binaries/building-nym.md) section. If you haven't yet built Nym and want to run the code, go there first. ## Preliminary steps From 09c4df844843698b2d7f965e6ce02c31ec43a83b Mon Sep 17 00:00:00 2001 From: serinko <97586125+serinko@users.noreply.github.com> Date: Tue, 1 Aug 2023 11:04:11 +0200 Subject: [PATCH 218/361] operators guide: book built --- docs/src/binaries/building-nym.html | 242 ++++++++++++ docs/src/binaries/building-nym.md | 1 + docs/src/binaries/init-and-config.html | 242 ++++++++++++ docs/src/binaries/init-and-config.md | 1 + docs/src/binaries/pre-built-binaries.html | 242 ++++++++++++ docs/src/binaries/pre-built-binaries.md | 1 + .../docs/src/binaries/building-nym.html | 350 ++++++++++++++++++ .../docs/src/binaries/init-and-config.html | 290 +++++++++++++++ .../docs/src/binaries/pre-built-binaries.html | 286 ++++++++++++++ 9 files changed, 1655 insertions(+) create mode 100644 docs/src/binaries/building-nym.html create mode 100644 docs/src/binaries/building-nym.md create mode 100644 docs/src/binaries/init-and-config.html create mode 100644 docs/src/binaries/init-and-config.md create mode 100644 docs/src/binaries/pre-built-binaries.html create mode 100644 docs/src/binaries/pre-built-binaries.md create mode 100644 documentation/docs/src/binaries/building-nym.html create mode 100644 documentation/docs/src/binaries/init-and-config.html create mode 100644 documentation/docs/src/binaries/pre-built-binaries.html diff --git a/docs/src/binaries/building-nym.html b/docs/src/binaries/building-nym.html new file mode 100644 index 0000000000..90e9c937cc --- /dev/null +++ b/docs/src/binaries/building-nym.html @@ -0,0 +1,242 @@ + + + + + + Building from Source - Nym Operators Guides + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + + + + + + + + + + +
    + +
    + + + + + + + + +
    +
    + {comment} +
    + + {comment} +
    + + {comment} +
    + + {comment} +
    + + {comment} +
    + + {comment} +
    + + {comment} +
    + +

    Building from Source

    + +
    + + +
    +
    + + + +
    + + + + + + + + + + + + + + + + + + + + + +
    + + diff --git a/docs/src/binaries/building-nym.md b/docs/src/binaries/building-nym.md new file mode 100644 index 0000000000..f195be32f9 --- /dev/null +++ b/docs/src/binaries/building-nym.md @@ -0,0 +1 @@ +# Building from Source diff --git a/docs/src/binaries/init-and-config.html b/docs/src/binaries/init-and-config.html new file mode 100644 index 0000000000..2a9390a316 --- /dev/null +++ b/docs/src/binaries/init-and-config.html @@ -0,0 +1,242 @@ + + + + + + Binary Initialisation and Configuration - Nym Operators Guides + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + + + + + + + + + + +
    + +
    + + + + + + + + +
    +
    + {comment} +
    + + {comment} +
    + + {comment} +
    + + {comment} +
    + + {comment} +
    + + {comment} +
    + + {comment} +
    + +

    Binary Initialisation and Configuration

    + +
    + + +
    +
    + + + +
    + + + + + + + + + + + + + + + + + + + + + +
    + + diff --git a/docs/src/binaries/init-and-config.md b/docs/src/binaries/init-and-config.md new file mode 100644 index 0000000000..9087d5cf00 --- /dev/null +++ b/docs/src/binaries/init-and-config.md @@ -0,0 +1 @@ +# Binary Initialisation and Configuration diff --git a/docs/src/binaries/pre-built-binaries.html b/docs/src/binaries/pre-built-binaries.html new file mode 100644 index 0000000000..a02f51d2bd --- /dev/null +++ b/docs/src/binaries/pre-built-binaries.html @@ -0,0 +1,242 @@ + + + + + + Pre-built Binaries - Nym Operators Guides + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + + + + + + + + + + +
    + +
    + + + + + + + + +
    +
    + {comment} +
    + + {comment} +
    + + {comment} +
    + + {comment} +
    + + {comment} +
    + + {comment} +
    + + {comment} +
    + +

    Pre-built Binaries

    + +
    + + +
    +
    + + + +
    + + + + + + + + + + + + + + + + + + + + + +
    + + diff --git a/docs/src/binaries/pre-built-binaries.md b/docs/src/binaries/pre-built-binaries.md new file mode 100644 index 0000000000..8c52595ce9 --- /dev/null +++ b/docs/src/binaries/pre-built-binaries.md @@ -0,0 +1 @@ +# Pre-built Binaries diff --git a/documentation/docs/src/binaries/building-nym.html b/documentation/docs/src/binaries/building-nym.html new file mode 100644 index 0000000000..2f73af4b4f --- /dev/null +++ b/documentation/docs/src/binaries/building-nym.html @@ -0,0 +1,350 @@ + + + + + + Building from Source - Nym Operators Guides + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + + + + + + + + + + +
    + +
    + + + + + + + + +
    +
    + {comment} +
    + + {comment} +
    + + {comment} +
    + + {comment} +
    + + {comment} +
    + + {comment} +
    + + {comment} +
    + + {comment} +
    + + {comment} +
    + + {comment} +
    + + {comment} +
    + + {comment} +
    + + {comment} +
    + + {comment} +
    + + {comment} +
    + + {comment} +
    + + {comment} +
    + + {comment} +
    + + {comment} +
    + + {comment} +
    + + {comment} +
    + +

    Building from Source

    +
    +

    Nym runs on Mac OS X, Linux, and Windows. All nodes except the Desktop Wallet and NymConnect on Windows should be considered experimental - it works fine if you’re an app developer but isn’t recommended for running nodes.

    +
    +

    Building Nym

    +

    Nym has two main codebases:

    +
      +
    • the Nym platform, written in Rust. This contains all of our code except for the validators.
    • +
    • the Nym validators, written in Go.
    • +
    +
    +

    This page details how to build the main Nym platform code. If you want to build and run a validator, go here instead.

    +
    +

    Prerequisites

    +
      +
    • Debian/Ubuntu: pkg-config, build-essential, libssl-dev, curl, jq, git
    • +
    +
    apt install pkg-config build-essential libssl-dev curl jq git
    +
    +
      +
    • Arch/Manjaro: base-devel
    • +
    +
    pacman -S base-devel
    +
    +
      +
    • Mac OS X: pkg-config , brew, openss1, protobuf, curl, git +Running the following the script installs Homebrew and the above dependencies:
    • +
    +
    /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
    +
    +
      +
    • Rust & cargo >= 1.66
    • +
    +

    We recommend using the Rust shell script installer. Installing cargo from your package manager (e.g. apt) is not recommended as the packaged versions are usually too old.

    +

    If you really don’t want to use the shell script installer, the Rust installation docs contain instructions for many platforms.

    +

    Download and build Nym binaries

    +

    The following commands will compile binaries into the nym/target/release directory:

    +
    rustup update
    +git clone https://github.com/nymtech/nym.git
    +cd nym
    +
    +git reset --hard # in case you made any changes on your branch
    +git pull # in case you've checked it out before
    +
    +git checkout release/v1.1.22 # checkout to the latest release branch: `develop` will most likely be incompatible with deployed public networks
    +
    +cargo build --release # build your binaries with **mainnet** configuration
    +NETWORK=sandbox cargo build --release # build your binaries with **sandbox** configuration
    +
    +

    Quite a bit of stuff gets built. The key working parts are:

    + +

    The repository also contains Typescript applications which aren’t built in this process. These can be built by following the instructions on their respective docs pages.

    + +
    +

    You cannot build from GitHub’s .zip or .tar.gz archive files on the releases page - the Nym build scripts automatically include the current git commit hash in the built binary during compilation, so the build will fail if you use the archive code (which isn’t a Git repository). Check the code out from github using git clone instead.

    +
    + +
    + + +
    +
    + + + +
    + + + + + + + + + + + + + + + + + + + + + +
    + + diff --git a/documentation/docs/src/binaries/init-and-config.html b/documentation/docs/src/binaries/init-and-config.html new file mode 100644 index 0000000000..147c89cc2d --- /dev/null +++ b/documentation/docs/src/binaries/init-and-config.html @@ -0,0 +1,290 @@ + + + + + + Binary Initialisation and Configuration - Nym Operators Guides + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + + + + + + + + + + +
    + +
    + + + + + + + + +
    +
    + {comment} +
    + + {comment} +
    + + {comment} +
    + + {comment} +
    + + {comment} +
    + + {comment} +
    + + {comment} +
    + + {comment} +
    + + {comment} +
    + + {comment} +
    + + {comment} +
    + + {comment} +
    + + {comment} +
    + + {comment} +
    + + {comment} +
    + + {comment} +
    + + {comment} +
    + + {comment} +
    + + {comment} +
    + + {comment} +
    + + {comment} +
    + +

    Binary Initialisation and Configuration

    +

    All Nym binaries must first be initialised with init before being run.

    +

    The init command is usually where you pass flags specifying configuration arguments such as the gateway you wish to communicate with, the ports you wish your binary to listen on, etc.

    +

    The init command will also create the necessary keypairs and configuration files at ~/.nym/<BINARY_TYPE>/<BINARY_ID>/ if these files do not already exist. It will not overwrite existing keypairs if they are present.

    +

    You can reconfigure your binaries at any time by editing the config file located at ~/.nym/<BINARY_TYPE>/<BINARY_ID>/config/config.toml and restarting the binary process.

    +

    Once you have run init, you can start your binary with the run command, usually only accompanied by the id of the binary that you specified.

    +

    This id is never transmitted over the network, and is used to select which local config and key files to use for startup.

    + +
    + + +
    +
    + + + +
    + + + + + + + + + + + + + + + + + + + + + +
    + + diff --git a/documentation/docs/src/binaries/pre-built-binaries.html b/documentation/docs/src/binaries/pre-built-binaries.html new file mode 100644 index 0000000000..3497c15d85 --- /dev/null +++ b/documentation/docs/src/binaries/pre-built-binaries.html @@ -0,0 +1,286 @@ + + + + + + Pre-built Binaries - Nym Operators Guides + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + + + + + + + + + + +
    + +
    + + + + + + + + +
    +
    + {comment} +
    + + {comment} +
    + + {comment} +
    + + {comment} +
    + + {comment} +
    + + {comment} +
    + + {comment} +
    + + {comment} +
    + + {comment} +
    + + {comment} +
    + + {comment} +
    + + {comment} +
    + + {comment} +
    + + {comment} +
    + + {comment} +
    + + {comment} +
    + + {comment} +
    + + {comment} +
    + + {comment} +
    + + {comment} +
    + + {comment} +
    + +

    Pre-built Binaries

    +

    The Github releases page has pre-built binaries which should work on Ubuntu 20.04 and other Debian-based systems, but at this stage cannot be guaranteed to work everywhere.

    +

    If the pre-built binaries don’t work or are unavailable for your system, you will need to build the platform yourself.

    + +
    + + +
    +
    + + + +
    + + + + + + + + + + + + + + + + + + + + + +
    + + From 8fd832e217d002eae51431b613955c74beee1ba0 Mon Sep 17 00:00:00 2001 From: serinko <97586125+serinko@users.noreply.github.com> Date: Tue, 1 Aug 2023 16:30:07 +0200 Subject: [PATCH 219/361] reinitialized the book - simplified book.toml --- documentation/operators/.gitignore | 26 -- documentation/operators/book.toml | 120 +----- documentation/operators/mdbook-admonish.css | 352 ------------------ documentation/operators/src/faq.md | 17 - .../operators/src/nodes/gateway-setup.md | 4 - .../src/nodes/network-requester-setup.md | 4 - .../operators/src/nodes/setup-guides.md | 7 - .../operators/src/nodes/validator-setup.md | 8 - 8 files changed, 1 insertion(+), 537 deletions(-) delete mode 100644 documentation/operators/mdbook-admonish.css diff --git a/documentation/operators/.gitignore b/documentation/operators/.gitignore index e84b38bdfb..7585238efe 100644 --- a/documentation/operators/.gitignore +++ b/documentation/operators/.gitignore @@ -1,27 +1 @@ -# mdbook files book -book/* -theme/index.hbs -theme/pagetoc.css -theme/pagetoc.js -theme/* -theme - -# --------------- -# Compiled assets -# --------------- -.sass-cache -_site - -# ---------- -# Developing -# ---------- -todo.md -.vscode - -# OSX -.DS_Store - -theme/ -theme -theme/* diff --git a/documentation/operators/book.toml b/documentation/operators/book.toml index 4202abc3f9..45ffb514ae 100644 --- a/documentation/operators/book.toml +++ b/documentation/operators/book.toml @@ -1,125 +1,7 @@ [book] -title = "Nym Operators Guides" +title = "Nym Operators Guide" authors = ["Max Hampshire", "serinko"] description = "Guides to setup Nym infrastructure" language = "en" multilingual = false # for the moment - ideally work on chinese, brazillian, spanish next src = "src" - -################# -# PREPROCESSORS # -################# - -# Note if changing these fields: -# - make sure you aren't running `mdbook serve` -# - change value of `turn-off = true` to `false`. This is set to `true` usually to avoid a known constant-reload bug with `mdbook serve` and this preprocessor -# - delete `./theme/` dir -# - change the preprocessor values that you want to edit -# - run `mdbook build`: this will rebuild the `./theme` directory -# - change value of `turn-off` back to `true` -[preprocessor.theme] -pagetoc = true -# some variables related (defined in theme/css/variables.css) -pagetoc-width = "13%" -content-max-width = "70%" # 82 -pagetoc-fontsize = "14.5px" -sidebar-width = "300px" -menu-bar-height = "40px" # memu-bar = the bar on the top -page-padding = "20px" -mobile-content-max-width = "98%" - -# layout -content-padding = "0 10px" -content-main-margin-left = "10%" -content-main-margin-right = "10%" -nav-chapters-max-width = "auto" -nav-chapters-min-width = "auto" -chapter-line-height = "2em" -section-line-height = "1.5em" - -# if true, never read and touch the files in theme dir: this is used to stop the looping reload issue referred to in the readme -turn-off = false - -[preprocessor.admonish] -command = "mdbook-admonish" -assets_version = "2.0.0" # do not edit: managed by `mdbook-admonish install` - -# variables preprocessor: import variables into files -# https://gitlab.com/tglman/mdbook-variables/ -[preprocessor.variables.variables] -minimum_rust_version = "1.66" -# vars for links: TODO think on how to streamline updating -platform_release_version = "v1.1.22" -wallet_release_version = "v1.2.5" - - -# [preprocessor.last-changed] -# command = "mdbook-last-changed" -# renderer = ["html"] - - -# used for grabbing output of binary commands for automation -# https://github.com/FauconFan/mdbook-cmdrun -[preprocessor.cmdrun] - -# more pre-processor plugins to look into from https://github.com/rust-lang/mdBook/wiki/Third-party-plugins & https://lib.rs/keywords/mdbook-preprocessor -# mdbook-i18n - -######### -# BUILD # -######### - -[build] -build-dir = "book" # the directory where the output is placed -create-missing = true # whether or not to create missing pages -use-default-preprocessors = true # use the default preprocessors -extra-watch-dirs = [] # directories to watch for triggering builds - -########## -# OUTPUT # -########## - -[output.html] -theme = "theme" # this is the default if not explicitly set -default-theme = "coal" -preferred-dark-theme = "coal" -curly-quotes = true -# mathjax-support = false # useful if we want to pull equations in -copy-fonts = true -no-section-label = false -additional-css = ["theme/pagetoc.css", "././mdbook-admonish.css"] #,"last-changed.css"] -additional-js = ["theme/pagetoc.js"] -git-repository-url = "https://github.com/nymtech/nym" -git-repository-icon = "fa-github" -# edit-url-template = "https://github.com/rust-lang/mdBook/edit/master/guide/{path}" -# site-url = "/docs/" -# cname = "nymtech.net" -# input-404 = "not-found.md" # todo make custom 404 page - -[output.html.fold] -enable = true # whether or not to enable section folding -level = 0 # the depth to start folding - -# controlling rust sample code blocks -[output.html.playground] -editable = false # allows editing the source code -copyable = true # include the copy button for copying code snippets -copy-js = true # includes the JavaScript for the code editor -line-numbers = false # displays line numbers for editable code -runnable = true # displays a run button for rust code - -# options for the built in text search -[output.html.search] -enable = true # enables the search feature -limit-results = 30 # maximum number of search results -teaser-word-count = 30 # number of words used for a search result teaser -use-boolean-and = true # multiple search terms must all match -boost-title = 2 # ranking boost factor for matches in headers -boost-hierarchy = 1 # ranking boost factor for matches in page names -boost-paragraph = 1 # ranking boost factor for matches in text -expand = true # partial words will match longer terms -heading-split-level = 3 # link results to heading levels -copy-js = true # include Javascript code for search - -# [output.linkcheck] -# warning-policy = "warn" diff --git a/documentation/operators/mdbook-admonish.css b/documentation/operators/mdbook-admonish.css deleted file mode 100644 index 5e360387df..0000000000 --- a/documentation/operators/mdbook-admonish.css +++ /dev/null @@ -1,352 +0,0 @@ -@charset "UTF-8"; -:root { - --md-admonition-icon--note: - url("data:image/svg+xml;charset=utf-8,"); - --md-admonition-icon--abstract: - url("data:image/svg+xml;charset=utf-8,"); - --md-admonition-icon--info: - url("data:image/svg+xml;charset=utf-8,"); - --md-admonition-icon--tip: - url("data:image/svg+xml;charset=utf-8,"); - --md-admonition-icon--success: - url("data:image/svg+xml;charset=utf-8,"); - --md-admonition-icon--question: - url("data:image/svg+xml;charset=utf-8,"); - --md-admonition-icon--warning: - url("data:image/svg+xml;charset=utf-8,"); - --md-admonition-icon--failure: - url("data:image/svg+xml;charset=utf-8,"); - --md-admonition-icon--danger: - url("data:image/svg+xml;charset=utf-8,"); - --md-admonition-icon--bug: - url("data:image/svg+xml;charset=utf-8,"); - --md-admonition-icon--example: - url("data:image/svg+xml;charset=utf-8,"); - --md-admonition-icon--quote: - url("data:image/svg+xml;charset=utf-8,"); - --md-details-icon: - url("data:image/svg+xml;charset=utf-8,"); -} - -:is(.admonition) { - display: flow-root; - margin: 1.5625em 0; - padding: 0 1.2rem; - color: var(--fg); - page-break-inside: avoid; - background-color: var(--bg); - border: 0 solid black; - border-inline-start-width: 0.4rem; - border-radius: 0.2rem; - box-shadow: 0 0.2rem 1rem rgba(0, 0, 0, 0.05), 0 0 0.1rem rgba(0, 0, 0, 0.1); -} -@media print { - :is(.admonition) { - box-shadow: none; - } -} -:is(.admonition) > * { - box-sizing: border-box; -} -:is(.admonition) :is(.admonition) { - margin-top: 1em; - margin-bottom: 1em; -} -:is(.admonition) > .tabbed-set:only-child { - margin-top: 0; -} -html :is(.admonition) > :last-child { - margin-bottom: 1.2rem; -} - -a.admonition-anchor-link { - display: none; - position: absolute; - left: -1.2rem; - padding-right: 1rem; -} -a.admonition-anchor-link:link, a.admonition-anchor-link:visited { - color: var(--fg); -} -a.admonition-anchor-link:link:hover, a.admonition-anchor-link:visited:hover { - text-decoration: none; -} -a.admonition-anchor-link::before { - content: "§"; -} - -:is(.admonition-title, summary) { - position: relative; - margin-block: 0; - margin-inline: -1.6rem -1.2rem; - padding-block: 0.8rem; - padding-inline: 4.4rem 1.2rem; - font-weight: 700; - background-color: rgba(68, 138, 255, 0.1); - display: flex; -} -:is(.admonition-title, summary) p { - margin: 0; -} -html :is(.admonition-title, summary):last-child { - margin-bottom: 0; -} -:is(.admonition-title, summary)::before { - position: absolute; - top: 0.625em; - inset-inline-start: 1.6rem; - width: 2rem; - height: 2rem; - background-color: #448aff; - mask-image: url('data:image/svg+xml;charset=utf-8,'); - -webkit-mask-image: url('data:image/svg+xml;charset=utf-8,'); - mask-repeat: no-repeat; - -webkit-mask-repeat: no-repeat; - mask-size: contain; - -webkit-mask-size: contain; - content: ""; -} -:is(.admonition-title, summary):hover a.admonition-anchor-link { - display: initial; -} - -details.admonition > summary.admonition-title::after { - position: absolute; - top: 0.625em; - inset-inline-end: 1.6rem; - height: 2rem; - width: 2rem; - background-color: currentcolor; - mask-image: var(--md-details-icon); - -webkit-mask-image: var(--md-details-icon); - mask-repeat: no-repeat; - -webkit-mask-repeat: no-repeat; - mask-size: contain; - -webkit-mask-size: contain; - content: ""; - transform: rotate(0deg); - transition: transform 0.25s; -} -details[open].admonition > summary.admonition-title::after { - transform: rotate(90deg); -} - -:is(.admonition):is(.note) { - border-color: #448aff; -} - -:is(.note) > :is(.admonition-title, summary) { - background-color: rgba(68, 138, 255, 0.1); -} -:is(.note) > :is(.admonition-title, summary)::before { - background-color: #448aff; - mask-image: var(--md-admonition-icon--note); - -webkit-mask-image: var(--md-admonition-icon--note); - mask-repeat: no-repeat; - -webkit-mask-repeat: no-repeat; - mask-size: contain; - -webkit-mask-repeat: no-repeat; -} - -:is(.admonition):is(.abstract, .summary, .tldr) { - border-color: #00b0ff; -} - -:is(.abstract, .summary, .tldr) > :is(.admonition-title, summary) { - background-color: rgba(0, 176, 255, 0.1); -} -:is(.abstract, .summary, .tldr) > :is(.admonition-title, summary)::before { - background-color: #00b0ff; - mask-image: var(--md-admonition-icon--abstract); - -webkit-mask-image: var(--md-admonition-icon--abstract); - mask-repeat: no-repeat; - -webkit-mask-repeat: no-repeat; - mask-size: contain; - -webkit-mask-repeat: no-repeat; -} - -:is(.admonition):is(.info, .todo) { - border-color: #00b8d4; -} - -:is(.info, .todo) > :is(.admonition-title, summary) { - background-color: rgba(0, 184, 212, 0.1); -} -:is(.info, .todo) > :is(.admonition-title, summary)::before { - background-color: #00b8d4; - mask-image: var(--md-admonition-icon--info); - -webkit-mask-image: var(--md-admonition-icon--info); - mask-repeat: no-repeat; - -webkit-mask-repeat: no-repeat; - mask-size: contain; - -webkit-mask-repeat: no-repeat; -} - -:is(.admonition):is(.tip, .hint, .important) { - border-color: #00bfa5; -} - -:is(.tip, .hint, .important) > :is(.admonition-title, summary) { - background-color: rgba(0, 191, 165, 0.1); -} -:is(.tip, .hint, .important) > :is(.admonition-title, summary)::before { - background-color: #00bfa5; - mask-image: var(--md-admonition-icon--tip); - -webkit-mask-image: var(--md-admonition-icon--tip); - mask-repeat: no-repeat; - -webkit-mask-repeat: no-repeat; - mask-size: contain; - -webkit-mask-repeat: no-repeat; -} - -:is(.admonition):is(.success, .check, .done) { - border-color: #00c853; -} - -:is(.success, .check, .done) > :is(.admonition-title, summary) { - background-color: rgba(0, 200, 83, 0.1); -} -:is(.success, .check, .done) > :is(.admonition-title, summary)::before { - background-color: #00c853; - mask-image: var(--md-admonition-icon--success); - -webkit-mask-image: var(--md-admonition-icon--success); - mask-repeat: no-repeat; - -webkit-mask-repeat: no-repeat; - mask-size: contain; - -webkit-mask-repeat: no-repeat; -} - -:is(.admonition):is(.question, .help, .faq) { - border-color: #64dd17; -} - -:is(.question, .help, .faq) > :is(.admonition-title, summary) { - background-color: rgba(100, 221, 23, 0.1); -} -:is(.question, .help, .faq) > :is(.admonition-title, summary)::before { - background-color: #64dd17; - mask-image: var(--md-admonition-icon--question); - -webkit-mask-image: var(--md-admonition-icon--question); - mask-repeat: no-repeat; - -webkit-mask-repeat: no-repeat; - mask-size: contain; - -webkit-mask-repeat: no-repeat; -} - -:is(.admonition):is(.warning, .caution, .attention) { - border-color: #ff9100; -} - -:is(.warning, .caution, .attention) > :is(.admonition-title, summary) { - background-color: rgba(255, 145, 0, 0.1); -} -:is(.warning, .caution, .attention) > :is(.admonition-title, summary)::before { - background-color: #ff9100; - mask-image: var(--md-admonition-icon--warning); - -webkit-mask-image: var(--md-admonition-icon--warning); - mask-repeat: no-repeat; - -webkit-mask-repeat: no-repeat; - mask-size: contain; - -webkit-mask-repeat: no-repeat; -} - -:is(.admonition):is(.failure, .fail, .missing) { - border-color: #ff5252; -} - -:is(.failure, .fail, .missing) > :is(.admonition-title, summary) { - background-color: rgba(255, 82, 82, 0.1); -} -:is(.failure, .fail, .missing) > :is(.admonition-title, summary)::before { - background-color: #ff5252; - mask-image: var(--md-admonition-icon--failure); - -webkit-mask-image: var(--md-admonition-icon--failure); - mask-repeat: no-repeat; - -webkit-mask-repeat: no-repeat; - mask-size: contain; - -webkit-mask-repeat: no-repeat; -} - -:is(.admonition):is(.danger, .error) { - border-color: #ff1744; -} - -:is(.danger, .error) > :is(.admonition-title, summary) { - background-color: rgba(255, 23, 68, 0.1); -} -:is(.danger, .error) > :is(.admonition-title, summary)::before { - background-color: #ff1744; - mask-image: var(--md-admonition-icon--danger); - -webkit-mask-image: var(--md-admonition-icon--danger); - mask-repeat: no-repeat; - -webkit-mask-repeat: no-repeat; - mask-size: contain; - -webkit-mask-repeat: no-repeat; -} - -:is(.admonition):is(.bug) { - border-color: #f50057; -} - -:is(.bug) > :is(.admonition-title, summary) { - background-color: rgba(245, 0, 87, 0.1); -} -:is(.bug) > :is(.admonition-title, summary)::before { - background-color: #f50057; - mask-image: var(--md-admonition-icon--bug); - -webkit-mask-image: var(--md-admonition-icon--bug); - mask-repeat: no-repeat; - -webkit-mask-repeat: no-repeat; - mask-size: contain; - -webkit-mask-repeat: no-repeat; -} - -:is(.admonition):is(.example) { - border-color: #7c4dff; -} - -:is(.example) > :is(.admonition-title, summary) { - background-color: rgba(124, 77, 255, 0.1); -} -:is(.example) > :is(.admonition-title, summary)::before { - background-color: #7c4dff; - mask-image: var(--md-admonition-icon--example); - -webkit-mask-image: var(--md-admonition-icon--example); - mask-repeat: no-repeat; - -webkit-mask-repeat: no-repeat; - mask-size: contain; - -webkit-mask-repeat: no-repeat; -} - -:is(.admonition):is(.quote, .cite) { - border-color: #9e9e9e; -} - -:is(.quote, .cite) > :is(.admonition-title, summary) { - background-color: rgba(158, 158, 158, 0.1); -} -:is(.quote, .cite) > :is(.admonition-title, summary)::before { - background-color: #9e9e9e; - mask-image: var(--md-admonition-icon--quote); - -webkit-mask-image: var(--md-admonition-icon--quote); - mask-repeat: no-repeat; - -webkit-mask-repeat: no-repeat; - mask-size: contain; - -webkit-mask-repeat: no-repeat; -} - -.navy :is(.admonition) { - background-color: var(--sidebar-bg); -} - -.ayu :is(.admonition), .coal :is(.admonition) { - background-color: var(--theme-hover); -} - -.rust :is(.admonition) { - background-color: var(--sidebar-bg); - color: var(--sidebar-fg); -} -.rust .admonition-anchor-link:link, .rust .admonition-anchor-link:visited { - color: var(--sidebar-fg); -} diff --git a/documentation/operators/src/faq.md b/documentation/operators/src/faq.md index faefd70b18..bc3c210352 100644 --- a/documentation/operators/src/faq.md +++ b/documentation/operators/src/faq.md @@ -1,20 +1,3 @@ - # Frequently Asked Questions ## Mixnet nodes diff --git a/documentation/operators/src/nodes/gateway-setup.md b/documentation/operators/src/nodes/gateway-setup.md index 31cff54c22..026e7afde0 100644 --- a/documentation/operators/src/nodes/gateway-setup.md +++ b/documentation/operators/src/nodes/gateway-setup.md @@ -1,7 +1,3 @@ - # Gateways > The Nym gateway was built in the [building nym](../binaries/building-nym.md) section. If you haven't yet built Nym and want to run the code, go there first. diff --git a/documentation/operators/src/nodes/network-requester-setup.md b/documentation/operators/src/nodes/network-requester-setup.md index 1cfee00e9b..049b139de6 100644 --- a/documentation/operators/src/nodes/network-requester-setup.md +++ b/documentation/operators/src/nodes/network-requester-setup.md @@ -1,7 +1,3 @@ - # Network Requesters > The Nym gateway was built in the [building nym](../binaries/building-nym.md) section. If you haven't yet built Nym and want to run the code, go there first. diff --git a/documentation/operators/src/nodes/setup-guides.md b/documentation/operators/src/nodes/setup-guides.md index 37955a6549..ca86dd99f4 100644 --- a/documentation/operators/src/nodes/setup-guides.md +++ b/documentation/operators/src/nodes/setup-guides.md @@ -1,10 +1,3 @@ - # Node Setup Guides To setup any type of Nym's node, start with building [Nym's platform](../binaries/building-nym.md) on the machine (VPS) where you want to run the node. Nodes will need to be bond to Nym's wallet, setup one [here](https://nymtech.net/docs/wallet/desktop-wallet.html). diff --git a/documentation/operators/src/nodes/validator-setup.md b/documentation/operators/src/nodes/validator-setup.md index 1c843a4cab..9e233808d7 100644 --- a/documentation/operators/src/nodes/validator-setup.md +++ b/documentation/operators/src/nodes/validator-setup.md @@ -1,11 +1,3 @@ - # Validators > Nym has two main codebases: From 99a321c54debef1a9ccbf908e053c119476fef7f Mon Sep 17 00:00:00 2001 From: serinko <97586125+serinko@users.noreply.github.com> Date: Tue, 1 Aug 2023 16:43:57 +0200 Subject: [PATCH 220/361] reinit the book, delete *.html in /src --- .../operators/src/binaries/building-nym.html | 350 ------------------ .../src/binaries/init-and-config.html | 290 --------------- .../src/binaries/pre-built-binaries.html | 286 -------------- 3 files changed, 926 deletions(-) delete mode 100644 documentation/operators/src/binaries/building-nym.html delete mode 100644 documentation/operators/src/binaries/init-and-config.html delete mode 100644 documentation/operators/src/binaries/pre-built-binaries.html diff --git a/documentation/operators/src/binaries/building-nym.html b/documentation/operators/src/binaries/building-nym.html deleted file mode 100644 index 2f73af4b4f..0000000000 --- a/documentation/operators/src/binaries/building-nym.html +++ /dev/null @@ -1,350 +0,0 @@ - - - - - - Building from Source - Nym Operators Guides - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    - - - - - - - - - - - - - - -
    - -
    - - - - - - - - -
    -
    - {comment} -
    - - {comment} -
    - - {comment} -
    - - {comment} -
    - - {comment} -
    - - {comment} -
    - - {comment} -
    - - {comment} -
    - - {comment} -
    - - {comment} -
    - - {comment} -
    - - {comment} -
    - - {comment} -
    - - {comment} -
    - - {comment} -
    - - {comment} -
    - - {comment} -
    - - {comment} -
    - - {comment} -
    - - {comment} -
    - - {comment} -
    - -

    Building from Source

    -
    -

    Nym runs on Mac OS X, Linux, and Windows. All nodes except the Desktop Wallet and NymConnect on Windows should be considered experimental - it works fine if you’re an app developer but isn’t recommended for running nodes.

    -
    -

    Building Nym

    -

    Nym has two main codebases:

    -
      -
    • the Nym platform, written in Rust. This contains all of our code except for the validators.
    • -
    • the Nym validators, written in Go.
    • -
    -
    -

    This page details how to build the main Nym platform code. If you want to build and run a validator, go here instead.

    -
    -

    Prerequisites

    -
      -
    • Debian/Ubuntu: pkg-config, build-essential, libssl-dev, curl, jq, git
    • -
    -
    apt install pkg-config build-essential libssl-dev curl jq git
    -
    -
      -
    • Arch/Manjaro: base-devel
    • -
    -
    pacman -S base-devel
    -
    -
      -
    • Mac OS X: pkg-config , brew, openss1, protobuf, curl, git -Running the following the script installs Homebrew and the above dependencies:
    • -
    -
    /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
    -
    -
      -
    • Rust & cargo >= 1.66
    • -
    -

    We recommend using the Rust shell script installer. Installing cargo from your package manager (e.g. apt) is not recommended as the packaged versions are usually too old.

    -

    If you really don’t want to use the shell script installer, the Rust installation docs contain instructions for many platforms.

    -

    Download and build Nym binaries

    -

    The following commands will compile binaries into the nym/target/release directory:

    -
    rustup update
    -git clone https://github.com/nymtech/nym.git
    -cd nym
    -
    -git reset --hard # in case you made any changes on your branch
    -git pull # in case you've checked it out before
    -
    -git checkout release/v1.1.22 # checkout to the latest release branch: `develop` will most likely be incompatible with deployed public networks
    -
    -cargo build --release # build your binaries with **mainnet** configuration
    -NETWORK=sandbox cargo build --release # build your binaries with **sandbox** configuration
    -
    -

    Quite a bit of stuff gets built. The key working parts are:

    - -

    The repository also contains Typescript applications which aren’t built in this process. These can be built by following the instructions on their respective docs pages.

    - -
    -

    You cannot build from GitHub’s .zip or .tar.gz archive files on the releases page - the Nym build scripts automatically include the current git commit hash in the built binary during compilation, so the build will fail if you use the archive code (which isn’t a Git repository). Check the code out from github using git clone instead.

    -
    - -
    - - -
    -
    - - - -
    - - - - - - - - - - - - - - - - - - - - - -
    - - diff --git a/documentation/operators/src/binaries/init-and-config.html b/documentation/operators/src/binaries/init-and-config.html deleted file mode 100644 index 147c89cc2d..0000000000 --- a/documentation/operators/src/binaries/init-and-config.html +++ /dev/null @@ -1,290 +0,0 @@ - - - - - - Binary Initialisation and Configuration - Nym Operators Guides - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    - - - - - - - - - - - - - - -
    - -
    - - - - - - - - -
    -
    - {comment} -
    - - {comment} -
    - - {comment} -
    - - {comment} -
    - - {comment} -
    - - {comment} -
    - - {comment} -
    - - {comment} -
    - - {comment} -
    - - {comment} -
    - - {comment} -
    - - {comment} -
    - - {comment} -
    - - {comment} -
    - - {comment} -
    - - {comment} -
    - - {comment} -
    - - {comment} -
    - - {comment} -
    - - {comment} -
    - - {comment} -
    - -

    Binary Initialisation and Configuration

    -

    All Nym binaries must first be initialised with init before being run.

    -

    The init command is usually where you pass flags specifying configuration arguments such as the gateway you wish to communicate with, the ports you wish your binary to listen on, etc.

    -

    The init command will also create the necessary keypairs and configuration files at ~/.nym/<BINARY_TYPE>/<BINARY_ID>/ if these files do not already exist. It will not overwrite existing keypairs if they are present.

    -

    You can reconfigure your binaries at any time by editing the config file located at ~/.nym/<BINARY_TYPE>/<BINARY_ID>/config/config.toml and restarting the binary process.

    -

    Once you have run init, you can start your binary with the run command, usually only accompanied by the id of the binary that you specified.

    -

    This id is never transmitted over the network, and is used to select which local config and key files to use for startup.

    - -
    - - -
    -
    - - - -
    - - - - - - - - - - - - - - - - - - - - - -
    - - diff --git a/documentation/operators/src/binaries/pre-built-binaries.html b/documentation/operators/src/binaries/pre-built-binaries.html deleted file mode 100644 index 3497c15d85..0000000000 --- a/documentation/operators/src/binaries/pre-built-binaries.html +++ /dev/null @@ -1,286 +0,0 @@ - - - - - - Pre-built Binaries - Nym Operators Guides - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    - - - - - - - - - - - - - - -
    - -
    - - - - - - - - -
    -
    - {comment} -
    - - {comment} -
    - - {comment} -
    - - {comment} -
    - - {comment} -
    - - {comment} -
    - - {comment} -
    - - {comment} -
    - - {comment} -
    - - {comment} -
    - - {comment} -
    - - {comment} -
    - - {comment} -
    - - {comment} -
    - - {comment} -
    - - {comment} -
    - - {comment} -
    - - {comment} -
    - - {comment} -
    - - {comment} -
    - - {comment} -
    - -

    Pre-built Binaries

    -

    The Github releases page has pre-built binaries which should work on Ubuntu 20.04 and other Debian-based systems, but at this stage cannot be guaranteed to work everywhere.

    -

    If the pre-built binaries don’t work or are unavailable for your system, you will need to build the platform yourself.

    - -
    - - -
    -
    - - - -
    - - - - - - - - - - - - - - - - - - - - - -
    - - From d2e59f12f6109c5b4f94c157233bccc0ec1c9aa9 Mon Sep 17 00:00:00 2001 From: serinko <97586125+serinko@users.noreply.github.com> Date: Tue, 1 Aug 2023 17:37:12 +0200 Subject: [PATCH 221/361] operators: book built, no {comments} left, cmdrun and admonish not working --- documentation/operators/book.toml | 118 + documentation/operators/last-changed.css | 6 + documentation/operators/mdbook-admonish.css | 352 ++ documentation/operators/theme/css/chrome.css | 538 +++ documentation/operators/theme/css/general.css | 203 + .../operators/theme/css/variables.css | 3257 +++++++++++++++++ documentation/operators/theme/index.hbs | 1063 ++++++ documentation/operators/theme/pagetoc.css | 47 + documentation/operators/theme/pagetoc.js | 68 + 9 files changed, 5652 insertions(+) create mode 100644 documentation/operators/last-changed.css create mode 100644 documentation/operators/mdbook-admonish.css create mode 100644 documentation/operators/theme/css/chrome.css create mode 100644 documentation/operators/theme/css/general.css create mode 100644 documentation/operators/theme/css/variables.css create mode 100644 documentation/operators/theme/index.hbs create mode 100644 documentation/operators/theme/pagetoc.css create mode 100644 documentation/operators/theme/pagetoc.js diff --git a/documentation/operators/book.toml b/documentation/operators/book.toml index 45ffb514ae..3a0a9f722b 100644 --- a/documentation/operators/book.toml +++ b/documentation/operators/book.toml @@ -5,3 +5,121 @@ description = "Guides to setup Nym infrastructure" language = "en" multilingual = false # for the moment - ideally work on chinese, brazillian, spanish next src = "src" + +################# +# PREPROCESSORS # +################# + +# Note if changing these fields: +# - make sure you aren't running `mdbook serve` +# - change value of `turn-off = true` to `false`. This is set to `true` usually to avoid a known constant-reload bug with `mdbook serve` and this preprocessor +# - delete `./theme/` dir +# - change the preprocessor values that you want to edit +# - run `mdbook build`: this will rebuild the `./theme` directory +# - change value of `turn-off` back to `true` +[preprocessor.theme] +pagetoc = true +# some variables related (defined in theme/css/variables.css) +pagetoc-width = "13%" +content-max-width = "70%" # 82 +pagetoc-fontsize = "14.5px" +sidebar-width = "300px" +menu-bar-height = "40px" # memu-bar = the bar on the top +page-padding = "20px" +mobile-content-max-width = "98%" + +# layout +content-padding = "0 10px" +content-main-margin-left = "10%" +content-main-margin-right = "10%" +nav-chapters-max-width = "auto" +nav-chapters-min-width = "auto" +chapter-line-height = "2em" +section-line-height = "1.5em" + +# if true, never read and touch the files in theme dir: this is used to stop the looping reload issue referred to in the readme +turn-off = false + +[preprocessor.admonish] +command = "mdbook-admonish" +assets_version = "2.0.0" # do not edit: managed by `mdbook-admonish install` + +# variables preprocessor: import variables into files +# https://gitlab.com/tglman/mdbook-variables/ +[preprocessor.variables.variables] +minimum_rust_version = "1.66" +# vars for links: TODO think on how to streamline updating +platform_release_version = "v1.1.22" +wallet_release_version = "v1.2.5" + + +# [preprocessor.last-changed] +# command = "mdbook-last-changed" +# renderer = ["html"] + + +# used for grabbing output of binary commands for automation +# https://github.com/FauconFan/mdbook-cmdrun +[preprocessor.cmdrun] + +# more pre-processor plugins to look into from https://github.com/rust-lang/mdBook/wiki/Third-party-plugins & https://lib.rs/keywords/mdbook-preprocessor +# mdbook-i18n + +######### +# BUILD # +######### + +[build] +build-dir = "book" # the directory where the output is placed +create-missing = true # whether or not to create missing pages +use-default-preprocessors = true # use the default preprocessors +extra-watch-dirs = [] # directories to watch for triggering builds + +########## +# OUTPUT # +########## + +[output.html] +theme = "theme" # this is the default if not explicitly set +default-theme = "coal" +preferred-dark-theme = "coal" +curly-quotes = true +# mathjax-support = false # useful if we want to pull equations in +copy-fonts = true +no-section-label = false +additional-css = ["theme/pagetoc.css", "././mdbook-admonish.css","./last-changed.css"] +additional-js = ["theme/pagetoc.js"] +git-repository-url = "https://github.com/nymtech/nym" +git-repository-icon = "fa-github" +# edit-url-template = "https://github.com/rust-lang/mdBook/edit/master/guide/{path}" +# site-url = "/docs/" +# cname = "nymtech.net" +# input-404 = "not-found.md" # todo make custom 404 page + +[output.html.fold] +enable = true # whether or not to enable section folding +level = 0 # the depth to start folding + +# controlling rust sample code blocks +[output.html.playground] +editable = false # allows editing the source code +copyable = true # include the copy button for copying code snippets +copy-js = true # includes the JavaScript for the code editor +line-numbers = false # displays line numbers for editable code +runnable = true # displays a run button for rust code + +# options for the built in text search +[output.html.search] +enable = true # enables the search feature +limit-results = 30 # maximum number of search results +teaser-word-count = 30 # number of words used for a search result teaser +use-boolean-and = true # multiple search terms must all match +boost-title = 2 # ranking boost factor for matches in headers +boost-hierarchy = 1 # ranking boost factor for matches in page names +boost-paragraph = 1 # ranking boost factor for matches in text +expand = true # partial words will match longer terms +heading-split-level = 3 # link results to heading levels +copy-js = true # include Javascript code for search + +# [output.linkcheck] +# warning-policy = "warn" diff --git a/documentation/operators/last-changed.css b/documentation/operators/last-changed.css new file mode 100644 index 0000000000..0c4228de32 --- /dev/null +++ b/documentation/operators/last-changed.css @@ -0,0 +1,6 @@ +footer { + font-size: 0.8em; + text-align: center; + border-top: 1px solid black; + padding: 5px 0; + } \ No newline at end of file diff --git a/documentation/operators/mdbook-admonish.css b/documentation/operators/mdbook-admonish.css new file mode 100644 index 0000000000..5e360387df --- /dev/null +++ b/documentation/operators/mdbook-admonish.css @@ -0,0 +1,352 @@ +@charset "UTF-8"; +:root { + --md-admonition-icon--note: + url("data:image/svg+xml;charset=utf-8,"); + --md-admonition-icon--abstract: + url("data:image/svg+xml;charset=utf-8,"); + --md-admonition-icon--info: + url("data:image/svg+xml;charset=utf-8,"); + --md-admonition-icon--tip: + url("data:image/svg+xml;charset=utf-8,"); + --md-admonition-icon--success: + url("data:image/svg+xml;charset=utf-8,"); + --md-admonition-icon--question: + url("data:image/svg+xml;charset=utf-8,"); + --md-admonition-icon--warning: + url("data:image/svg+xml;charset=utf-8,"); + --md-admonition-icon--failure: + url("data:image/svg+xml;charset=utf-8,"); + --md-admonition-icon--danger: + url("data:image/svg+xml;charset=utf-8,"); + --md-admonition-icon--bug: + url("data:image/svg+xml;charset=utf-8,"); + --md-admonition-icon--example: + url("data:image/svg+xml;charset=utf-8,"); + --md-admonition-icon--quote: + url("data:image/svg+xml;charset=utf-8,"); + --md-details-icon: + url("data:image/svg+xml;charset=utf-8,"); +} + +:is(.admonition) { + display: flow-root; + margin: 1.5625em 0; + padding: 0 1.2rem; + color: var(--fg); + page-break-inside: avoid; + background-color: var(--bg); + border: 0 solid black; + border-inline-start-width: 0.4rem; + border-radius: 0.2rem; + box-shadow: 0 0.2rem 1rem rgba(0, 0, 0, 0.05), 0 0 0.1rem rgba(0, 0, 0, 0.1); +} +@media print { + :is(.admonition) { + box-shadow: none; + } +} +:is(.admonition) > * { + box-sizing: border-box; +} +:is(.admonition) :is(.admonition) { + margin-top: 1em; + margin-bottom: 1em; +} +:is(.admonition) > .tabbed-set:only-child { + margin-top: 0; +} +html :is(.admonition) > :last-child { + margin-bottom: 1.2rem; +} + +a.admonition-anchor-link { + display: none; + position: absolute; + left: -1.2rem; + padding-right: 1rem; +} +a.admonition-anchor-link:link, a.admonition-anchor-link:visited { + color: var(--fg); +} +a.admonition-anchor-link:link:hover, a.admonition-anchor-link:visited:hover { + text-decoration: none; +} +a.admonition-anchor-link::before { + content: "§"; +} + +:is(.admonition-title, summary) { + position: relative; + margin-block: 0; + margin-inline: -1.6rem -1.2rem; + padding-block: 0.8rem; + padding-inline: 4.4rem 1.2rem; + font-weight: 700; + background-color: rgba(68, 138, 255, 0.1); + display: flex; +} +:is(.admonition-title, summary) p { + margin: 0; +} +html :is(.admonition-title, summary):last-child { + margin-bottom: 0; +} +:is(.admonition-title, summary)::before { + position: absolute; + top: 0.625em; + inset-inline-start: 1.6rem; + width: 2rem; + height: 2rem; + background-color: #448aff; + mask-image: url('data:image/svg+xml;charset=utf-8,'); + -webkit-mask-image: url('data:image/svg+xml;charset=utf-8,'); + mask-repeat: no-repeat; + -webkit-mask-repeat: no-repeat; + mask-size: contain; + -webkit-mask-size: contain; + content: ""; +} +:is(.admonition-title, summary):hover a.admonition-anchor-link { + display: initial; +} + +details.admonition > summary.admonition-title::after { + position: absolute; + top: 0.625em; + inset-inline-end: 1.6rem; + height: 2rem; + width: 2rem; + background-color: currentcolor; + mask-image: var(--md-details-icon); + -webkit-mask-image: var(--md-details-icon); + mask-repeat: no-repeat; + -webkit-mask-repeat: no-repeat; + mask-size: contain; + -webkit-mask-size: contain; + content: ""; + transform: rotate(0deg); + transition: transform 0.25s; +} +details[open].admonition > summary.admonition-title::after { + transform: rotate(90deg); +} + +:is(.admonition):is(.note) { + border-color: #448aff; +} + +:is(.note) > :is(.admonition-title, summary) { + background-color: rgba(68, 138, 255, 0.1); +} +:is(.note) > :is(.admonition-title, summary)::before { + background-color: #448aff; + mask-image: var(--md-admonition-icon--note); + -webkit-mask-image: var(--md-admonition-icon--note); + mask-repeat: no-repeat; + -webkit-mask-repeat: no-repeat; + mask-size: contain; + -webkit-mask-repeat: no-repeat; +} + +:is(.admonition):is(.abstract, .summary, .tldr) { + border-color: #00b0ff; +} + +:is(.abstract, .summary, .tldr) > :is(.admonition-title, summary) { + background-color: rgba(0, 176, 255, 0.1); +} +:is(.abstract, .summary, .tldr) > :is(.admonition-title, summary)::before { + background-color: #00b0ff; + mask-image: var(--md-admonition-icon--abstract); + -webkit-mask-image: var(--md-admonition-icon--abstract); + mask-repeat: no-repeat; + -webkit-mask-repeat: no-repeat; + mask-size: contain; + -webkit-mask-repeat: no-repeat; +} + +:is(.admonition):is(.info, .todo) { + border-color: #00b8d4; +} + +:is(.info, .todo) > :is(.admonition-title, summary) { + background-color: rgba(0, 184, 212, 0.1); +} +:is(.info, .todo) > :is(.admonition-title, summary)::before { + background-color: #00b8d4; + mask-image: var(--md-admonition-icon--info); + -webkit-mask-image: var(--md-admonition-icon--info); + mask-repeat: no-repeat; + -webkit-mask-repeat: no-repeat; + mask-size: contain; + -webkit-mask-repeat: no-repeat; +} + +:is(.admonition):is(.tip, .hint, .important) { + border-color: #00bfa5; +} + +:is(.tip, .hint, .important) > :is(.admonition-title, summary) { + background-color: rgba(0, 191, 165, 0.1); +} +:is(.tip, .hint, .important) > :is(.admonition-title, summary)::before { + background-color: #00bfa5; + mask-image: var(--md-admonition-icon--tip); + -webkit-mask-image: var(--md-admonition-icon--tip); + mask-repeat: no-repeat; + -webkit-mask-repeat: no-repeat; + mask-size: contain; + -webkit-mask-repeat: no-repeat; +} + +:is(.admonition):is(.success, .check, .done) { + border-color: #00c853; +} + +:is(.success, .check, .done) > :is(.admonition-title, summary) { + background-color: rgba(0, 200, 83, 0.1); +} +:is(.success, .check, .done) > :is(.admonition-title, summary)::before { + background-color: #00c853; + mask-image: var(--md-admonition-icon--success); + -webkit-mask-image: var(--md-admonition-icon--success); + mask-repeat: no-repeat; + -webkit-mask-repeat: no-repeat; + mask-size: contain; + -webkit-mask-repeat: no-repeat; +} + +:is(.admonition):is(.question, .help, .faq) { + border-color: #64dd17; +} + +:is(.question, .help, .faq) > :is(.admonition-title, summary) { + background-color: rgba(100, 221, 23, 0.1); +} +:is(.question, .help, .faq) > :is(.admonition-title, summary)::before { + background-color: #64dd17; + mask-image: var(--md-admonition-icon--question); + -webkit-mask-image: var(--md-admonition-icon--question); + mask-repeat: no-repeat; + -webkit-mask-repeat: no-repeat; + mask-size: contain; + -webkit-mask-repeat: no-repeat; +} + +:is(.admonition):is(.warning, .caution, .attention) { + border-color: #ff9100; +} + +:is(.warning, .caution, .attention) > :is(.admonition-title, summary) { + background-color: rgba(255, 145, 0, 0.1); +} +:is(.warning, .caution, .attention) > :is(.admonition-title, summary)::before { + background-color: #ff9100; + mask-image: var(--md-admonition-icon--warning); + -webkit-mask-image: var(--md-admonition-icon--warning); + mask-repeat: no-repeat; + -webkit-mask-repeat: no-repeat; + mask-size: contain; + -webkit-mask-repeat: no-repeat; +} + +:is(.admonition):is(.failure, .fail, .missing) { + border-color: #ff5252; +} + +:is(.failure, .fail, .missing) > :is(.admonition-title, summary) { + background-color: rgba(255, 82, 82, 0.1); +} +:is(.failure, .fail, .missing) > :is(.admonition-title, summary)::before { + background-color: #ff5252; + mask-image: var(--md-admonition-icon--failure); + -webkit-mask-image: var(--md-admonition-icon--failure); + mask-repeat: no-repeat; + -webkit-mask-repeat: no-repeat; + mask-size: contain; + -webkit-mask-repeat: no-repeat; +} + +:is(.admonition):is(.danger, .error) { + border-color: #ff1744; +} + +:is(.danger, .error) > :is(.admonition-title, summary) { + background-color: rgba(255, 23, 68, 0.1); +} +:is(.danger, .error) > :is(.admonition-title, summary)::before { + background-color: #ff1744; + mask-image: var(--md-admonition-icon--danger); + -webkit-mask-image: var(--md-admonition-icon--danger); + mask-repeat: no-repeat; + -webkit-mask-repeat: no-repeat; + mask-size: contain; + -webkit-mask-repeat: no-repeat; +} + +:is(.admonition):is(.bug) { + border-color: #f50057; +} + +:is(.bug) > :is(.admonition-title, summary) { + background-color: rgba(245, 0, 87, 0.1); +} +:is(.bug) > :is(.admonition-title, summary)::before { + background-color: #f50057; + mask-image: var(--md-admonition-icon--bug); + -webkit-mask-image: var(--md-admonition-icon--bug); + mask-repeat: no-repeat; + -webkit-mask-repeat: no-repeat; + mask-size: contain; + -webkit-mask-repeat: no-repeat; +} + +:is(.admonition):is(.example) { + border-color: #7c4dff; +} + +:is(.example) > :is(.admonition-title, summary) { + background-color: rgba(124, 77, 255, 0.1); +} +:is(.example) > :is(.admonition-title, summary)::before { + background-color: #7c4dff; + mask-image: var(--md-admonition-icon--example); + -webkit-mask-image: var(--md-admonition-icon--example); + mask-repeat: no-repeat; + -webkit-mask-repeat: no-repeat; + mask-size: contain; + -webkit-mask-repeat: no-repeat; +} + +:is(.admonition):is(.quote, .cite) { + border-color: #9e9e9e; +} + +:is(.quote, .cite) > :is(.admonition-title, summary) { + background-color: rgba(158, 158, 158, 0.1); +} +:is(.quote, .cite) > :is(.admonition-title, summary)::before { + background-color: #9e9e9e; + mask-image: var(--md-admonition-icon--quote); + -webkit-mask-image: var(--md-admonition-icon--quote); + mask-repeat: no-repeat; + -webkit-mask-repeat: no-repeat; + mask-size: contain; + -webkit-mask-repeat: no-repeat; +} + +.navy :is(.admonition) { + background-color: var(--sidebar-bg); +} + +.ayu :is(.admonition), .coal :is(.admonition) { + background-color: var(--theme-hover); +} + +.rust :is(.admonition) { + background-color: var(--sidebar-bg); + color: var(--sidebar-fg); +} +.rust .admonition-anchor-link:link, .rust .admonition-anchor-link:visited { + color: var(--sidebar-fg); +} diff --git a/documentation/operators/theme/css/chrome.css b/documentation/operators/theme/css/chrome.css new file mode 100644 index 0000000000..febd1d40a6 --- /dev/null +++ b/documentation/operators/theme/css/chrome.css @@ -0,0 +1,538 @@ +/* CSS for UI elements (a.k.a. chrome) */ + +@import 'variables.css'; + +::-webkit-scrollbar { + background: var(--bg); +} +::-webkit-scrollbar-thumb { + background: var(--scrollbar); +} +html { + scrollbar-color: var(--scrollbar) var(--bg); +} +#searchresults a, +.content a:link, +a:visited, +a > .hljs { + color: var(--links); +} + +/* Menu Bar */ + +#menu-bar, +#menu-bar-hover-placeholder { + z-index: 101; + margin: auto calc(0px - var(--page-padding)); +} +#menu-bar { + position: relative; + display: flex; + flex-wrap: wrap; + background-color: var(--bg); + border-bottom-color: var(--bg); + border-bottom-width: 1px; + border-bottom-style: solid; +} +#menu-bar.sticky, +.js #menu-bar-hover-placeholder:hover + #menu-bar, +.js #menu-bar:hover, +.js.sidebar-visible #menu-bar { + position: -webkit-sticky; + position: sticky; + top: 0 !important; +} +#menu-bar-hover-placeholder { + position: sticky; + position: -webkit-sticky; + top: 0; + height: var(--menu-bar-height); +} +#menu-bar.bordered { + border-bottom-color: var(--table-border-color); +} +#menu-bar i, #menu-bar .icon-button { + position: relative; + padding: 0 8px; + z-index: 10; + line-height: var(--menu-bar-height); + cursor: pointer; + transition: color 0.5s; +} +@media only screen and (max-width: 420px) { + #menu-bar i, #menu-bar .icon-button { + padding: 0 5px; + } +} + +.icon-button { + border: none; + background: none; + padding: 0; + color: inherit; +} +.icon-button i { + margin: 0; +} + +.right-buttons { + margin: 0 15px; +} +.right-buttons a { + text-decoration: none; +} + +.left-buttons { + display: flex; + margin: 0 5px; +} +.no-js .left-buttons { + display: none; +} + +.menu-title { + display: inline-block; + font-weight: 200; + font-size: 2.4rem; + line-height: var(--menu-bar-height); + text-align: center; + margin: 0; + flex: 1; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} +.js .menu-title { + cursor: pointer; +} + +.menu-bar, +.menu-bar:visited, +.nav-chapters, +.nav-chapters:visited, +.mobile-nav-chapters, +.mobile-nav-chapters:visited, +.menu-bar .icon-button, +.menu-bar a i { + color: var(--icons); +} + +.menu-bar i:hover, +.menu-bar .icon-button:hover, +.nav-chapters:hover, +.mobile-nav-chapters i:hover { + color: var(--icons-hover); +} + +/* Nav Icons */ + +.nav-chapters { + font-size: 2.5em; + text-align: center; + text-decoration: none; + + position: fixed; + top: 0; + bottom: 0; + margin: 0; + max-width: auto; + min-width: auto; + + display: flex; + justify-content: center; + align-content: center; + flex-direction: column; + + transition: color 0.5s, background-color 0.5s; +} + +.nav-chapters:hover { + text-decoration: none; + background-color: var(--theme-hover); + transition: background-color 0.15s, color 0.15s; +} + +.nav-wrapper { + margin-top: 50px; + display: none; +} + +.mobile-nav-chapters { + font-size: 2.5em; + text-align: center; + text-decoration: none; + width: 90px; + border-radius: 5px; + background-color: var(--sidebar-bg); +} + +.previous { + float: left; +} + +.next { + float: right; + right: var(--page-padding); +} + +@media only screen and (max-width: 1080px) { + .nav-wide-wrapper { display: none; } + .nav-wrapper { display: block; } +} + +@media only screen and (max-width: 1380px) { + .sidebar-visible .nav-wide-wrapper { display: none; } + .sidebar-visible .nav-wrapper { display: block; } +} + +/* Inline code */ + +:not(pre) > .hljs { + display: inline; + padding: 0.1em 0.3em; + border-radius: 3px; +} + +:not(pre):not(a) > .hljs { + color: var(--inline-code-color); + overflow-x: initial; +} + +a:hover > .hljs { + text-decoration: underline; +} + +pre { + position: relative; +} +pre > .buttons { + position: absolute; + z-index: 100; + right: 0px; + top: 2px; + margin: 0px; + padding: 2px 0px; + + color: var(--sidebar-fg); + cursor: pointer; + visibility: hidden; + opacity: 0; + transition: visibility 0.1s linear, opacity 0.1s linear; +} +pre:hover > .buttons { + visibility: visible; + opacity: 1 +} +pre > .buttons :hover { + color: var(--sidebar-active); + border-color: var(--icons-hover); + background-color: var(--theme-hover); +} +pre > .buttons i { + margin-left: 8px; +} +pre > .buttons button { + cursor: inherit; + margin: 0px 5px; + padding: 3px 5px; + font-size: 14px; + + border-style: solid; + border-width: 1px; + border-radius: 4px; + border-color: var(--icons); + background-color: var(--theme-popup-bg); + transition: 100ms; + transition-property: color,border-color,background-color; + color: var(--icons); +} +@media (pointer: coarse) { + pre > .buttons button { + /* On mobile, make it easier to tap buttons. */ + padding: 0.3rem 1rem; + } +} +pre > code { + padding: 1rem; +} + +/* FIXME: ACE editors overlap their buttons because ACE does absolute + positioning within the code block which breaks padding. The only solution I + can think of is to move the padding to the outer pre tag (or insert a div + wrapper), but that would require fixing a whole bunch of CSS rules. +*/ +.hljs.ace_editor { + padding: 0rem 0rem; +} + +pre > .result { + margin-top: 10px; +} + +/* Search */ + +#searchresults a { + text-decoration: none; +} + +mark { + border-radius: 2px; + padding: 0 3px 1px 3px; + margin: 0 -3px -1px -3px; + background-color: var(--search-mark-bg); + transition: background-color 300ms linear; + cursor: pointer; +} + +mark.fade-out { + background-color: rgba(0,0,0,0) !important; + cursor: auto; +} + +.searchbar-outer { + margin-left: auto; + margin-right: auto; + max-width: var(--content-max-width); +} + +#searchbar { + width: 100%; + margin: 5px auto 0px auto; + padding: 10px 16px; + transition: box-shadow 300ms ease-in-out; + border: 1px solid var(--searchbar-border-color); + border-radius: 3px; + background-color: var(--searchbar-bg); + color: var(--searchbar-fg); +} +#searchbar:focus, +#searchbar.active { + box-shadow: 0 0 3px var(--searchbar-shadow-color); +} + +.searchresults-header { + font-weight: bold; + font-size: 1em; + padding: 18px 0 0 5px; + color: var(--searchresults-header-fg); +} + +.searchresults-outer { + margin-left: auto; + margin-right: auto; + max-width: var(--content-max-width); + border-bottom: 1px dashed var(--searchresults-border-color); +} + +ul#searchresults { + list-style: none; + padding-left: 20px; +} +ul#searchresults li { + margin: 10px 0px; + padding: 2px; + border-radius: 2px; +} +ul#searchresults li.focus { + background-color: var(--searchresults-li-bg); +} +ul#searchresults span.teaser { + display: block; + clear: both; + margin: 5px 0 0 20px; + font-size: 0.8em; +} +ul#searchresults span.teaser em { + font-weight: bold; + font-style: normal; +} + +/* Sidebar */ + +.sidebar { + position: fixed; + left: 0; + top: 0; + bottom: 0; + width: var(--sidebar-width); + font-size: 1em; + box-sizing: border-box; + -webkit-overflow-scrolling: touch; + overscroll-behavior-y: contain; + background-color: var(--sidebar-bg); + color: var(--sidebar-fg); +} +.sidebar-resizing { + -moz-user-select: none; + -webkit-user-select: none; + -ms-user-select: none; + user-select: none; +} +.js:not(.sidebar-resizing) .sidebar { + transition: transform 0.3s; /* Animation: slide away */ +} +.sidebar code { + line-height: 2em; +} +.sidebar .sidebar-scrollbox { + overflow-y: auto; + position: absolute; + top: 0; + bottom: 0; + left: 0; + right: 0; + padding: 10px 10px; +} +.sidebar .sidebar-resize-handle { + position: absolute; + cursor: col-resize; + width: 0; + right: 0; + top: 0; + bottom: 0; +} +.js .sidebar .sidebar-resize-handle { + cursor: col-resize; + width: 5px; +} +.sidebar-hidden .sidebar { + transform: translateX(calc(0px - var(--sidebar-width))); +} +.sidebar::-webkit-scrollbar { + background: var(--sidebar-bg); +} +.sidebar::-webkit-scrollbar-thumb { + background: var(--scrollbar); +} + +.sidebar-visible .page-wrapper { + transform: translateX(var(--sidebar-width)); +} +@media only screen and (min-width: 620px) { + .sidebar-visible .page-wrapper { + transform: none; + margin-left: var(--sidebar-width); + } +} + +.chapter { + list-style: none outside none; + padding-left: 0; + line-height: 2em; +} + +.chapter ol { + width: 100%; +} + +.chapter li { + display: flex; + color: var(--sidebar-non-existant); +} +.chapter li a { + display: block; + padding: 0; + text-decoration: none; + color: var(--sidebar-fg); +} + +.chapter li a:hover { + color: var(--sidebar-active); +} + +.chapter li a.active { + color: var(--sidebar-active); +} + +.chapter li > a.toggle { + cursor: pointer; + display: block; + margin-left: auto; + padding: 0 10px; + user-select: none; + opacity: 0.68; +} + +.chapter li > a.toggle div { + transition: transform 0.5s; +} + +/* collapse the section */ +.chapter li:not(.expanded) + li > ol { + display: none; +} + +.chapter li.chapter-item { + line-height: 1.5em; + margin-top: 0.6em; +} + +.chapter li.expanded > a.toggle div { + transform: rotate(90deg); +} + +.spacer { + width: 100%; + height: 3px; + margin: 5px 0px; +} +.chapter .spacer { + background-color: var(--sidebar-spacer); +} + +@media (-moz-touch-enabled: 1), (pointer: coarse) { + .chapter li a { padding: 5px 0; } + .spacer { margin: 10px 0; } +} + +.section { + list-style: none outside none; + padding-left: 20px; + line-height: 1.5em; +} + +/* Theme Menu Popup */ + +.theme-popup { + position: absolute; + left: 10px; + top: var(--menu-bar-height); + z-index: 1000; + border-radius: 4px; + font-size: 0.7em; + color: var(--fg); + background: var(--theme-popup-bg); + border: 1px solid var(--theme-popup-border); + margin: 0; + padding: 0; + list-style: none; + display: none; + /* Don't let the children's background extend past the rounded corners. */ + overflow: hidden; +} +.theme-popup .default { + color: var(--icons); +} +.theme-popup .theme { + width: 100%; + border: 0; + margin: 0; + padding: 2px 20px; + line-height: 25px; + white-space: nowrap; + text-align: left; + cursor: pointer; + color: inherit; + background: inherit; + font-size: inherit; +} +.theme-popup .theme:hover { + background-color: var(--theme-hover); +} + +.theme-selected::before { + display: inline-block; + content: "✓"; + margin-left: -14px; + width: 14px; +} diff --git a/documentation/operators/theme/css/general.css b/documentation/operators/theme/css/general.css new file mode 100644 index 0000000000..a06db43965 --- /dev/null +++ b/documentation/operators/theme/css/general.css @@ -0,0 +1,203 @@ +/* Base styles and content styles */ + +@import 'variables.css'; + +:root { + /* Browser default font-size is 16px, this way 1 rem = 10px */ + font-size: 70%; +} + +html { + font-family: "Open Sans", sans-serif; + color: var(--fg); + background-color: var(--bg); + text-size-adjust: none; + -webkit-text-size-adjust: none; +} + +body { + margin: 0; + font-size: 1.5rem; + overflow-x: hidden; +} + +code { + font-family: var(--mono-font) !important; + font-size: 0.9em; +} + +/* make long words/inline code not x overflow */ +main { + overflow-wrap: break-word; +} + +/* make wide tables scroll if they overflow */ +.table-wrapper { + overflow-x: auto; +} + +/* Don't change font size in headers. */ +h1 code, h2 code, h3 code, h4 code, h5 code, h6 code { + font-size: unset; +} + +.left { float: left; } +.right { float: right; } +.boring { opacity: 0.6; } +.hide-boring .boring { display: none; } +.hidden { display: none !important; } + +h2, h3 { margin-top: 2.5em; } +h4, h5 { margin-top: 2em; } + +.header + .header h3, +.header + .header h4, +.header + .header h5 { + margin-top: 1em; +} + +h1:target::before, +h2:target::before, +h3:target::before, +h4:target::before, +h5:target::before, +h6:target::before { + display: inline-block; + content: "»"; + margin-left: -30px; + width: 30px; +} + +/* This is broken on Safari as of version 14, but is fixed + in Safari Technology Preview 117 which I think will be Safari 14.2. + https://bugs.webkit.org/show_bug.cgi?id=218076 +*/ +:target { + scroll-margin-top: calc(var(--menu-bar-height) + 0.5em); +} + +.page { + outline: 0; + padding: 0 var(--page-padding); + margin-top: calc(0px - var(--menu-bar-height)); /* Compensate for the #menu-bar-hover-placeholder */ +} +.page-wrapper { + box-sizing: border-box; +} +.js:not(.sidebar-resizing) .page-wrapper { + transition: margin-left 0.3s ease, transform 0.3s ease; /* Animation: slide away */ +} + +.content { + overflow-y: auto; + padding: 0 10px; +} +.content main { + margin-left: 10%; + margin-right: 10%; + max-width: var(--content-max-width); +} +.content p { line-height: 1.45em; } +.content ol { line-height: 1.45em; } +.content ul { line-height: 1.45em; } +.content a { text-decoration: none; } +.content a:hover { text-decoration: underline; } +.content img, .content video { max-width: 100%; } +.content .header:link, +.content .header:visited { + color: var(--fg); +} +.content .header:link, +.content .header:visited:hover { + text-decoration: none; +} + +table { + margin: 0 auto; + border-collapse: collapse; +} +table td { + padding: 3px 20px; + border: 1px var(--table-border-color) solid; +} +table thead { + background: var(--table-header-bg); +} +table thead td { + font-weight: 700; + border: none; +} +table thead th { + padding: 3px 20px; +} +table thead tr { + border: 1px var(--table-header-bg) solid; +} +/* Alternate background colors for rows */ +table tbody tr:nth-child(2n) { + background: var(--table-alternate-bg); +} + + +blockquote { + margin: 20px 0; + padding: 0 20px; + color: var(--fg); + background-color: var(--quote-bg); + border-top: .1em solid var(--quote-border); + border-bottom: .1em solid var(--quote-border); +} + +kbd { + background-color: var(--table-border-color); + border-radius: 4px; + border: solid 1px var(--theme-popup-border); + box-shadow: inset 0 -1px 0 var(--theme-hover); + display: inline-block; + font-size: var(--code-font-size); + font-family: var(--mono-font); + line-height: 10px; + padding: 4px 5px; + vertical-align: middle; +} + +:not(.footnote-definition) + .footnote-definition, +.footnote-definition + :not(.footnote-definition) { + margin-top: 2em; +} +.footnote-definition { + font-size: 0.9em; + margin: 0.5em 0; +} +.footnote-definition p { + display: inline; +} + +.tooltiptext { + position: absolute; + visibility: hidden; + color: #fff; + background-color: #333; + transform: translateX(-50%); /* Center by moving tooltip 50% of its width left */ + left: -8px; /* Half of the width of the icon */ + top: -35px; + font-size: 0.8em; + text-align: center; + border-radius: 6px; + padding: 5px 8px; + margin: 5px; + z-index: 1000; +} +.tooltipped .tooltiptext { + visibility: visible; +} + +.chapter li.part-title { + color: var(--sidebar-fg); + margin: 5px 0px; + font-weight: bold; +} + +.result-no-output { + font-style: italic; +} diff --git a/documentation/operators/theme/css/variables.css b/documentation/operators/theme/css/variables.css new file mode 100644 index 0000000000..8dfcf92239 --- /dev/null +++ b/documentation/operators/theme/css/variables.css @@ -0,0 +1,3257 @@ + +/* Globals */ + +:root { + --sidebar-width: 300px; + --page-padding: 20px; + --content-max-width: 70%; + --menu-bar-height: 40px; + --mono-font: "Source Code Pro", Consolas, "Ubuntu Mono", Menlo, "DejaVu Sans Mono", monospace, monospace; + --code-font-size: 0.875em /* please adjust the ace font size accordingly in editor.js */ + --pagetoc-width: 13%; + --pagetoc-fontsize: 14.5px; +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +@media only screen and (max-width:1439px) { + :root{ + --content-max-width: 98%; + } +} + +/* Themes */ + +.ayu { + --bg: hsl(210, 25%, 8%); + --fg: #c5c5c5; + + --sidebar-bg: #14191f; + --sidebar-fg: #c8c9db; + --sidebar-non-existant: #5c6773; + --sidebar-active: #ffb454; + --sidebar-spacer: #2d334f; + + --scrollbar: var(--sidebar-fg); + + --icons: #737480; + --icons-hover: #b7b9cc; + + --links: #0096cf; + + --inline-code-color: #ffb454; + + --theme-popup-bg: #14191f; + --theme-popup-border: #5c6773; + --theme-hover: #191f26; + + --quote-bg: hsl(226, 15%, 17%); + --quote-border: hsl(226, 15%, 22%); + + --table-border-color: hsl(210, 25%, 13%); + --table-header-bg: hsl(210, 25%, 28%); + --table-alternate-bg: hsl(210, 25%, 11%); + + --searchbar-border-color: #848484; + --searchbar-bg: #424242; + --searchbar-fg: #fff; + --searchbar-shadow-color: #d4c89f; + --searchresults-header-fg: #666; + --searchresults-border-color: #888; + --searchresults-li-bg: #252932; + --search-mark-bg: #e3b171; +} + +.coal { + --bg: hsl(200, 7%, 8%); + --fg: #98a3ad; + + --sidebar-bg: #292c2f; + --sidebar-fg: #a1adb8; + --sidebar-non-existant: #505254; + --sidebar-active: #3473ad; + --sidebar-spacer: #393939; + + --scrollbar: var(--sidebar-fg); + + --icons: #43484d; + --icons-hover: #b3c0cc; + + --links: #2b79a2; + + --inline-code-color: #c5c8c6; + + --theme-popup-bg: #141617; + --theme-popup-border: #43484d; + --theme-hover: #1f2124; + + --quote-bg: hsl(234, 21%, 18%); + --quote-border: hsl(234, 21%, 23%); + + --table-border-color: hsl(200, 7%, 13%); + --table-header-bg: hsl(200, 7%, 28%); + --table-alternate-bg: hsl(200, 7%, 11%); + + --searchbar-border-color: #aaa; + --searchbar-bg: #b7b7b7; + --searchbar-fg: #000; + --searchbar-shadow-color: #aaa; + --searchresults-header-fg: #666; + --searchresults-border-color: #98a3ad; + --searchresults-li-bg: #2b2b2f; + --search-mark-bg: #355c7d; +} + +.light { + --bg: hsl(0, 0%, 100%); + --fg: hsl(0, 0%, 0%); + + --sidebar-bg: #fafafa; + --sidebar-fg: hsl(0, 0%, 0%); + --sidebar-non-existant: #aaaaaa; + --sidebar-active: #1f1fff; + --sidebar-spacer: #f4f4f4; + + --scrollbar: #8F8F8F; + + --icons: #747474; + --icons-hover: #000000; + + --links: #1f1fff; + + --inline-code-color: #F42C4C; + + --theme-popup-bg: #fafafa; + --theme-popup-border: #cccccc; + --theme-hover: #e6e6e6; + + --quote-bg: hsl(197, 37%, 96%); + --quote-border: hsl(197, 37%, 91%); + + --table-border-color: hsl(0, 0%, 95%); + --table-header-bg: hsl(0, 0%, 80%); + --table-alternate-bg: hsl(0, 0%, 97%); + + --searchbar-border-color: #aaa; + --searchbar-bg: #fafafa; + --searchbar-fg: #000; + --searchbar-shadow-color: #aaa; + --searchresults-header-fg: #666; + --searchresults-border-color: #888; + --searchresults-li-bg: #e4f2fe; + --search-mark-bg: #a2cff5; +} + +.navy { + --bg: hsl(226, 23%, 11%); + --fg: #bcbdd0; + + --sidebar-bg: #282d3f; + --sidebar-fg: #c8c9db; + --sidebar-non-existant: #505274; + --sidebar-active: #2b79a2; + --sidebar-spacer: #2d334f; + + --scrollbar: var(--sidebar-fg); + + --icons: #737480; + --icons-hover: #b7b9cc; + + --links: #2b79a2; + + --inline-code-color: #c5c8c6; + + --theme-popup-bg: #161923; + --theme-popup-border: #737480; + --theme-hover: #282e40; + + --quote-bg: hsl(226, 15%, 17%); + --quote-border: hsl(226, 15%, 22%); + + --table-border-color: hsl(226, 23%, 16%); + --table-header-bg: hsl(226, 23%, 31%); + --table-alternate-bg: hsl(226, 23%, 14%); + + --searchbar-border-color: #aaa; + --searchbar-bg: #aeaec6; + --searchbar-fg: #000; + --searchbar-shadow-color: #aaa; + --searchresults-header-fg: #5f5f71; + --searchresults-border-color: #5c5c68; + --searchresults-li-bg: #242430; + --search-mark-bg: #a2cff5; +} + +.rust { + --bg: hsl(60, 9%, 87%); + --fg: #262625; + + --sidebar-bg: #3b2e2a; + --sidebar-fg: #c8c9db; + --sidebar-non-existant: #505254; + --sidebar-active: #e69f67; + --sidebar-spacer: #45373a; + + --scrollbar: var(--sidebar-fg); + + --icons: #737480; + --icons-hover: #262625; + + --links: #2b79a2; + + --inline-code-color: #6e6b5e; + + --theme-popup-bg: #e1e1db; + --theme-popup-border: #b38f6b; + --theme-hover: #99908a; + + --quote-bg: hsl(60, 5%, 75%); + --quote-border: hsl(60, 5%, 70%); + + --table-border-color: hsl(60, 9%, 82%); + --table-header-bg: #b3a497; + --table-alternate-bg: hsl(60, 9%, 84%); + + --searchbar-border-color: #aaa; + --searchbar-bg: #fafafa; + --searchbar-fg: #000; + --searchbar-shadow-color: #aaa; + --searchresults-header-fg: #666; + --searchresults-border-color: #888; + --searchresults-li-bg: #dec2a2; + --search-mark-bg: #e69f67; +} + +@media (prefers-color-scheme: dark) { + .light.no-js { + --bg: hsl(200, 7%, 8%); + --fg: #98a3ad; + + --sidebar-bg: #292c2f; + --sidebar-fg: #a1adb8; + --sidebar-non-existant: #505254; + --sidebar-active: #3473ad; + --sidebar-spacer: #393939; + + --scrollbar: var(--sidebar-fg); + + --icons: #43484d; + --icons-hover: #b3c0cc; + + --links: #2b79a2; + + --inline-code-color: #c5c8c6; + + --theme-popup-bg: #141617; + --theme-popup-border: #43484d; + --theme-hover: #1f2124; + + --quote-bg: hsl(234, 21%, 18%); + --quote-border: hsl(234, 21%, 23%); + + --table-border-color: hsl(200, 7%, 13%); + --table-header-bg: hsl(200, 7%, 28%); + --table-alternate-bg: hsl(200, 7%, 11%); + + --searchbar-border-color: #aaa; + --searchbar-bg: #b7b7b7; + --searchbar-fg: #000; + --searchbar-shadow-color: #aaa; + --searchresults-header-fg: #666; + --searchresults-border-color: #98a3ad; + --searchresults-li-bg: #2b2b2f; + --search-mark-bg: #355c7d; + } +} diff --git a/documentation/operators/theme/index.hbs b/documentation/operators/theme/index.hbs new file mode 100644 index 0000000000..06a3731f33 --- /dev/null +++ b/documentation/operators/theme/index.hbs @@ -0,0 +1,1063 @@ + + + + + + {{ title }} + {{#if is_print }} + + {{/if}} + {{#if base_url}} + + {{/if}} + + + + {{> head}} + + + + + + {{#if favicon_svg}} + + {{/if}} + {{#if favicon_png}} + + {{/if}} + + + + {{#if print_enable}} + + {{/if}} + + + + {{#if copy_fonts}} + + {{/if}} + + + + + + + + {{#each additional_css}} + + {{/each}} + + {{#if mathjax_support}} + + + {{/if}} + + + + + + + + + + + + + + + + +
    + +
    + {{> header}} + + + + {{#if search_enabled}} + + {{/if}} + + + + +
    +
    + +
    + + +
    + + +
    + + +
    + + +
    + + +
    + + +
    + + +
    + + +
    + + +
    + + +
    + + +
    + + +
    + + +
    + + +
    + + +
    + + +
    + + +
    + + +
    + + +
    + + +
    + + +
    + + +
    + + +
    + + +
    + + +
    + + +
    + + +
    + + +
    + + +
    + + +
    + + +
    + + +
    + + +
    + + +
    + + +
    + + +
    + + +
    + + +
    + + +
    + + +
    + + +
    + + +
    + + +
    + + +
    + + +
    + + +
    + + +
    + + +
    + + +
    + + +
    + + +
    + + +
    + + +
    + + +
    + + +
    + + +
    + + +
    + + +
    + + +
    + + +
    + + +
    + + +
    + + +
    + + +
    + + +
    + + +
    + + +
    + + +
    + + +
    + + +
    + + +
    + + +
    + + +
    + + +
    + + +
    + + +
    + + +
    + + +
    + + +
    + + +
    + + +
    + + +
    + + +
    + + +
    + + +
    + + +
    + + +
    + + +
    + + +
    + + +
    + + +
    + + +
    + + +
    + + +
    + + +
    + + +
    + + +
    + + +
    + + +
    + + +
    + + +
    + + +
    + + +
    + + +
    + + +
    + + +
    + + +
    + + +
    + + +
    + + +
    + + +
    + + +
    + + +
    + + +
    + + +
    + + +
    + + +
    + + +
    + + +
    + + +
    + + +
    + + +
    + + +
    + + +
    + + +
    + + +
    + + +
    + + +
    + + +
    + + +
    + + +
    + + +
    + + +
    + + +
    + + +
    + + +
    + + +
    + + +
    + + +
    + + +
    + + +
    + + +
    + + +
    + + +
    + + +
    + + +
    + + +
    + + +
    + + +
    + + +
    + + +
    + + +
    + + +
    + + +
    + + +
    + + +
    + + +
    + + +
    + + +
    + + +
    + + +
    + + +
    + + +
    + + +
    + + +
    + + +
    + + +
    + + +
    + + +
    + + +
    + + +
    + + +
    + + +
    + + +
    + + +
    + + +
    + + +
    + + +
    + + +
    + + +
    + + +
    + + +
    + + +
    + + +
    + + +
    + + +
    + + +
    + + +
    + + +
    + + +
    + + +
    + + +
    + + +
    + + +
    + + +
    + + +
    + + +
    + + +
    + + +
    + + +
    + + +
    + + +
    + + +
    + + +
    + + +
    + + +
    + + +
    + + +
    + + +
    + + +
    + + +
    + + +
    + + +
    + + +
    + + +
    + + +
    + + +
    + + +
    + + +
    + + +
    + + +
    + + +
    + + +
    + + +
    + + +
    + + +
    + + +
    + + +
    + + +
    + + +
    + + +
    + + +
    + + +
    + + +
    + + +
    + + +
    + + +
    + + +
    + + +
    + + +
    + + +
    + + +
    + + +
    + + +
    + + +
    + + +
    + + +
    + + +
    + + +
    + + {{{ content }}} +
    + + +
    +
    + + + +
    + + {{#if live_reload_endpoint}} + + + {{/if}} + + {{#if google_analytics}} + + + {{/if}} + + {{#if playground_line_numbers}} + + {{/if}} + + {{#if playground_copyable}} + + {{/if}} + + {{#if playground_js}} + + + + + + {{/if}} + + {{#if search_js}} + + + + {{/if}} + + + + + + + {{#each additional_js}} + + {{/each}} + + {{#if is_print}} + {{#if mathjax_support}} + + {{else}} + + {{/if}} + {{/if}} + + + diff --git a/documentation/operators/theme/pagetoc.css b/documentation/operators/theme/pagetoc.css new file mode 100644 index 0000000000..a68e5b8e21 --- /dev/null +++ b/documentation/operators/theme/pagetoc.css @@ -0,0 +1,47 @@ +/* src: https://github.com/JorelAli/mdBook-pagetoc */ + +@media only screen and (max-width:1439px) { + .sidetoc { + display: none; + } +} + +@media only screen and (min-width:1440px) { + main { + position: relative; + } + .sidetoc { + margin-left: auto; + margin-right: auto; + /* left: calc(90% + (var(--content-min-width))/4 - 110px); */ + left: 101%; + position: absolute; + font-size: var(--pagetoc-fontsize); + } + .pagetoc { + position: fixed; + width: var(--pagetoc-width); + } + .pagetoc a { + border-left: 1px solid var(--sidebar-bg); + /* color: var(--fg); */ + /* color: var(--sidebar-fg); */ + color: var(--links); + display: block; + padding-bottom: 5px; + padding-top: 5px; + padding-left: 10px; + text-align: left; + text-decoration: none; + font-weight: normal; + background: var(--sidebar-bg); + } + .pagetoc a:hover, + .pagetoc a.active { + background: var(--sidebar-bg); + /* color: var(--sidebar-fg); */ + color: var(--sidebar-active); + font-weight: bold; + font-size: var(--pagetoc-fontsize); + } +} diff --git a/documentation/operators/theme/pagetoc.js b/documentation/operators/theme/pagetoc.js new file mode 100644 index 0000000000..b11052427e --- /dev/null +++ b/documentation/operators/theme/pagetoc.js @@ -0,0 +1,68 @@ +// src: https://github.com/JorelAli/mdBook-pagetoc + +// Un-active everything when you click it +Array.prototype.forEach.call(document.getElementsByClassName("pagetoc")[0].children, function(el, i) { + el.addEventHandler("click", function() { + Array.prototype.forEach.call(document.getElementsByClassName("pagetoc")[0].children, function(el, i) { + el.classList.remove("active"); + }); + el.classList.add("active"); + }); +}); + +var updateFunction = function() { + + var id; + var elements = document.getElementsByClassName("header"); + Array.prototype.forEach.call(elements, function(el, i) { + if (window.pageYOffset >= el.offsetTop) { + id = el; + } + }); + + Array.prototype.forEach.call(document.getElementsByClassName("pagetoc")[0].children, function(el, i) { + el.classList.remove("active"); + }); + + Array.prototype.forEach.call(document.getElementsByClassName("pagetoc")[0].children, function(el, i) { + if (id.href.localeCompare(el.href) == 0) { + el.classList.add("active"); + } + }); +}; + +// Populate sidebar on load +window.addEventListener('load', function() { + var pagetoc = document.getElementsByClassName("pagetoc")[0]; + var elements = document.getElementsByClassName("header"); + Array.prototype.forEach.call(elements, function(el, i) { + var link = document.createElement("a"); + + // Indent shows hierarchy + var indent = ""; + switch (el.parentElement.tagName) { + case "H2": + indent = "20px"; + break; + case "H3": + indent = "40px"; + break; + case "H4": + indent = "60px"; + break; + default: + break; + } + + link.appendChild(document.createTextNode(el.text)); + link.style.paddingLeft = indent; + link.href = el.href; + pagetoc.appendChild(link); + }); + updateFunction.call(); +}); + + + +// Handle active elements on scroll +window.addEventListener("scroll", updateFunction); From 99224310e9abbf9e8caed7eb949c680088ad8e29 Mon Sep 17 00:00:00 2001 From: serinko <97586125+serinko@users.noreply.github.com> Date: Wed, 2 Aug 2023 08:04:57 +0200 Subject: [PATCH 222/361] operators: plugins work, ready for final review --- documentation/operators/book.toml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/documentation/operators/book.toml b/documentation/operators/book.toml index 3a0a9f722b..28efe62c57 100644 --- a/documentation/operators/book.toml +++ b/documentation/operators/book.toml @@ -53,9 +53,9 @@ platform_release_version = "v1.1.22" wallet_release_version = "v1.2.5" -# [preprocessor.last-changed] -# command = "mdbook-last-changed" -# renderer = ["html"] +[preprocessor.last-changed] +command = "mdbook-last-changed" +renderer = ["html"] # used for grabbing output of binary commands for automation From 3ae803a3437be3d33a27e30f706774883b1f8e57 Mon Sep 17 00:00:00 2001 From: serinko <97586125+serinko@users.noreply.github.com> Date: Wed, 2 Aug 2023 10:40:26 +0200 Subject: [PATCH 223/361] #3674 feedback implemented --- .../docs/src/binaries/building-nym.html | 350 ------------------ .../docs/src/binaries/init-and-config.html | 290 --------------- .../docs/src/binaries/pre-built-binaries.html | 286 -------------- documentation/docs/src/introduction.md | 3 - documentation/operators/src/introduction.md | 15 +- .../operators/src/nodes/troubleshooting.md | 8 +- .../operators/src/preliminary-steps.md | 8 +- 7 files changed, 7 insertions(+), 953 deletions(-) delete mode 100644 documentation/docs/src/binaries/building-nym.html delete mode 100644 documentation/docs/src/binaries/init-and-config.html delete mode 100644 documentation/docs/src/binaries/pre-built-binaries.html diff --git a/documentation/docs/src/binaries/building-nym.html b/documentation/docs/src/binaries/building-nym.html deleted file mode 100644 index 2f73af4b4f..0000000000 --- a/documentation/docs/src/binaries/building-nym.html +++ /dev/null @@ -1,350 +0,0 @@ - - - - - - Building from Source - Nym Operators Guides - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    - - - - - - - - - - - - - - -
    - -
    - - - - - - - - -
    -
    - {comment} -
    - - {comment} -
    - - {comment} -
    - - {comment} -
    - - {comment} -
    - - {comment} -
    - - {comment} -
    - - {comment} -
    - - {comment} -
    - - {comment} -
    - - {comment} -
    - - {comment} -
    - - {comment} -
    - - {comment} -
    - - {comment} -
    - - {comment} -
    - - {comment} -
    - - {comment} -
    - - {comment} -
    - - {comment} -
    - - {comment} -
    - -

    Building from Source

    -
    -

    Nym runs on Mac OS X, Linux, and Windows. All nodes except the Desktop Wallet and NymConnect on Windows should be considered experimental - it works fine if you’re an app developer but isn’t recommended for running nodes.

    -
    -

    Building Nym

    -

    Nym has two main codebases:

    -
      -
    • the Nym platform, written in Rust. This contains all of our code except for the validators.
    • -
    • the Nym validators, written in Go.
    • -
    -
    -

    This page details how to build the main Nym platform code. If you want to build and run a validator, go here instead.

    -
    -

    Prerequisites

    -
      -
    • Debian/Ubuntu: pkg-config, build-essential, libssl-dev, curl, jq, git
    • -
    -
    apt install pkg-config build-essential libssl-dev curl jq git
    -
    -
      -
    • Arch/Manjaro: base-devel
    • -
    -
    pacman -S base-devel
    -
    -
      -
    • Mac OS X: pkg-config , brew, openss1, protobuf, curl, git -Running the following the script installs Homebrew and the above dependencies:
    • -
    -
    /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
    -
    -
      -
    • Rust & cargo >= 1.66
    • -
    -

    We recommend using the Rust shell script installer. Installing cargo from your package manager (e.g. apt) is not recommended as the packaged versions are usually too old.

    -

    If you really don’t want to use the shell script installer, the Rust installation docs contain instructions for many platforms.

    -

    Download and build Nym binaries

    -

    The following commands will compile binaries into the nym/target/release directory:

    -
    rustup update
    -git clone https://github.com/nymtech/nym.git
    -cd nym
    -
    -git reset --hard # in case you made any changes on your branch
    -git pull # in case you've checked it out before
    -
    -git checkout release/v1.1.22 # checkout to the latest release branch: `develop` will most likely be incompatible with deployed public networks
    -
    -cargo build --release # build your binaries with **mainnet** configuration
    -NETWORK=sandbox cargo build --release # build your binaries with **sandbox** configuration
    -
    -

    Quite a bit of stuff gets built. The key working parts are:

    - -

    The repository also contains Typescript applications which aren’t built in this process. These can be built by following the instructions on their respective docs pages.

    - -
    -

    You cannot build from GitHub’s .zip or .tar.gz archive files on the releases page - the Nym build scripts automatically include the current git commit hash in the built binary during compilation, so the build will fail if you use the archive code (which isn’t a Git repository). Check the code out from github using git clone instead.

    -
    - -
    - - -
    -
    - - - -
    - - - - - - - - - - - - - - - - - - - - - -
    - - diff --git a/documentation/docs/src/binaries/init-and-config.html b/documentation/docs/src/binaries/init-and-config.html deleted file mode 100644 index 147c89cc2d..0000000000 --- a/documentation/docs/src/binaries/init-and-config.html +++ /dev/null @@ -1,290 +0,0 @@ - - - - - - Binary Initialisation and Configuration - Nym Operators Guides - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    - - - - - - - - - - - - - - -
    - -
    - - - - - - - - -
    -
    - {comment} -
    - - {comment} -
    - - {comment} -
    - - {comment} -
    - - {comment} -
    - - {comment} -
    - - {comment} -
    - - {comment} -
    - - {comment} -
    - - {comment} -
    - - {comment} -
    - - {comment} -
    - - {comment} -
    - - {comment} -
    - - {comment} -
    - - {comment} -
    - - {comment} -
    - - {comment} -
    - - {comment} -
    - - {comment} -
    - - {comment} -
    - -

    Binary Initialisation and Configuration

    -

    All Nym binaries must first be initialised with init before being run.

    -

    The init command is usually where you pass flags specifying configuration arguments such as the gateway you wish to communicate with, the ports you wish your binary to listen on, etc.

    -

    The init command will also create the necessary keypairs and configuration files at ~/.nym/<BINARY_TYPE>/<BINARY_ID>/ if these files do not already exist. It will not overwrite existing keypairs if they are present.

    -

    You can reconfigure your binaries at any time by editing the config file located at ~/.nym/<BINARY_TYPE>/<BINARY_ID>/config/config.toml and restarting the binary process.

    -

    Once you have run init, you can start your binary with the run command, usually only accompanied by the id of the binary that you specified.

    -

    This id is never transmitted over the network, and is used to select which local config and key files to use for startup.

    - -
    - - -
    -
    - - - -
    - - - - - - - - - - - - - - - - - - - - - -
    - - diff --git a/documentation/docs/src/binaries/pre-built-binaries.html b/documentation/docs/src/binaries/pre-built-binaries.html deleted file mode 100644 index 3497c15d85..0000000000 --- a/documentation/docs/src/binaries/pre-built-binaries.html +++ /dev/null @@ -1,286 +0,0 @@ - - - - - - Pre-built Binaries - Nym Operators Guides - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    - - - - - - - - - - - - - - -
    - -
    - - - - - - - - -
    -
    - {comment} -
    - - {comment} -
    - - {comment} -
    - - {comment} -
    - - {comment} -
    - - {comment} -
    - - {comment} -
    - - {comment} -
    - - {comment} -
    - - {comment} -
    - - {comment} -
    - - {comment} -
    - - {comment} -
    - - {comment} -
    - - {comment} -
    - - {comment} -
    - - {comment} -
    - - {comment} -
    - - {comment} -
    - - {comment} -
    - - {comment} -
    - -

    Pre-built Binaries

    -

    The Github releases page has pre-built binaries which should work on Ubuntu 20.04 and other Debian-based systems, but at this stage cannot be guaranteed to work everywhere.

    -

    If the pre-built binaries don’t work or are unavailable for your system, you will need to build the platform yourself.

    - -
    - - -
    -
    - - - -
    - - - - - - - - - - - - - - - - - - - - - -
    - - diff --git a/documentation/docs/src/introduction.md b/documentation/docs/src/introduction.md index a6cf38aae7..9e245497f4 100644 --- a/documentation/docs/src/introduction.md +++ b/documentation/docs/src/introduction.md @@ -4,9 +4,6 @@ This is Nym's technical documentation, containing information and setup guides a If you are new to Nym and want to learn about the mixnet, how to integrate with the network, developer tutorials and quickstart guides, check out the [Developer Portal](https://nymtech.net/developers/). -If you are looking for information on grants and beta-release Nym apps, check out the [Shipyard](https://shipyard.nymtech.net) site. - - ## Popular pages **Network Architecture:** * [Network Overview](./architecture/network-overview.md) diff --git a/documentation/operators/src/introduction.md b/documentation/operators/src/introduction.md index 48b5984eef..acf29f9363 100644 --- a/documentation/operators/src/introduction.md +++ b/documentation/operators/src/introduction.md @@ -4,9 +4,7 @@ This is Nym's operators guide, containing information and setup tutorials about If you are new to Nym and want to learn about the mixnet, how to integrate with the network, developer tutorials and quickstart guides, check out the [Developer Portal](https://nymtech.net/developers/). -If you want to go deeper into Nym architecture and various software such as different, application clients, existing applications like the desktop wallet, mixnet explorer or SDKs, visit [Nym's technical documentation](https://nymtech.net/docs/). - -If you are looking for information on grants and beta-release Nym apps, check out the [Shipyard](https://shipyard.nymtech.net) site. +If you want to dive deeper into Nym architecture and such as different clients, existing applications like the desktop wallet, explorers or SDKs, visit [Nym's technical documentation](https://nymtech.net/docs/). ## Popular pages @@ -28,14 +26,3 @@ If you are looking for information on grants and beta-release Nym apps, check ou * [Troubleshooting](./nodes/troubleshooting.md) * [FAQ](./faq.md) - diff --git a/documentation/operators/src/nodes/troubleshooting.md b/documentation/operators/src/nodes/troubleshooting.md index e5333bcfe4..f063d985e0 100644 --- a/documentation/operators/src/nodes/troubleshooting.md +++ b/documentation/operators/src/nodes/troubleshooting.md @@ -1,9 +1,3 @@ - - # Troubleshooting ## Binary Build Problems @@ -73,6 +67,8 @@ There are 2 community explorers currently, which have been created by [Nodes Gur * [Mainnet](https://mixnet.explorers.guru/) * [Sandbox testnet](https://sandbox.mixnet.explorers.guru/) +[Here](https://github.com/cosmos/chain-registry/blob/master/nyx/chain.json#L158-L187) is a dictionary with Nyx chain registry entry regarding all explorers. + If you want more information, or if your node isn't showing up on the explorer of your choice and you want to double-check, here are some examples on how to check if the node is configured properly. #### Check from your VPS diff --git a/documentation/operators/src/preliminary-steps.md b/documentation/operators/src/preliminary-steps.md index 5cb76d8e19..53f5383838 100644 --- a/documentation/operators/src/preliminary-steps.md +++ b/documentation/operators/src/preliminary-steps.md @@ -1,12 +1,12 @@ # Preliminary Steps -> The Nym mixnode, gateway and network requester binaries were built in the [building nym](../binaries/building-nym.md) section. If you haven't yet built Nym and want to run the code, go there first. +> The Nym `mixnode`, `gateway` and `network-requester` binaries were built in the [building nym](../binaries/building-nym.md) section. If you haven't yet built Nym and want to run the code, go there first. ## Preliminary steps There are a couple of steps that need completing before starting to set up your mixnode, gateway or a network requester: -- preparing your [wallet](https://nymtech.net/docs/wallet/desktop-wallet.html) +- preparing your [desktop wallet](https://nymtech.net/docs/wallet/desktop-wallet.html) or [CLI wallet](https://nymtech.net/docs/wallet/cli-wallet.html). - requisitioning a VPS (Virtual Private Server) ### Wallet preparation @@ -32,12 +32,12 @@ For now, see the below rough specs: - Processors: 2 cores are fine. Get the fastest CPUs you can afford. -#### For Nym Mixnode +#### For Mixnode - RAM: Memory requirements are very low - typically a mix node may use only a few hundred MB of RAM. - Disks: The mixnodes require no disk space beyond a few bytes for the configuration files. -#### For Nym Gateway +#### For Gateway - RAM: Memory requirements depend on the amount of users your Gateway will be serving at any one time. If you're just going to be using it yourself, then minimal RAM is fine. **If you're running your Gateway as part of a Service Grant, get something with at least 4GB RAM.** - Disks: much like the amount of RAM your Gateway could use, the amount of disk space required will vary with the amount of users your Gateway is serving. **If you're running your Gateway as part of a Service Grant, get something with at least 40GB storage.** From fca384fba21cf11d4281300301f9546ae2d07eb4 Mon Sep 17 00:00:00 2001 From: mfahampshire Date: Thu, 3 Aug 2023 16:31:45 +0200 Subject: [PATCH 224/361] tweaked intro page --- documentation/operators/src/introduction.md | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/documentation/operators/src/introduction.md b/documentation/operators/src/introduction.md index acf29f9363..dc160825d6 100644 --- a/documentation/operators/src/introduction.md +++ b/documentation/operators/src/introduction.md @@ -1,19 +1,17 @@ # Introduction -This is Nym's operators guide, containing information and setup tutorials about the various pieces of Nym mixnet infrastructure nodes, such as mix node, gateway and network requester as well as Nyx chain validators. +This is Nym's Operators guide, containing information and setup guides for the various pieces of Nym mixnet infrastructure (mix node, gateway and network requester) and Nyx blockchain validators. -If you are new to Nym and want to learn about the mixnet, how to integrate with the network, developer tutorials and quickstart guides, check out the [Developer Portal](https://nymtech.net/developers/). +If you are new to Nym and want to learn about the mixnet, explore quickstart options and demos, learn how to integrate with the network, and follow developer tutorials check out the [Developer Portal](https://nymtech.net/developers/). -If you want to dive deeper into Nym architecture and such as different clients, existing applications like the desktop wallet, explorers or SDKs, visit [Nym's technical documentation](https://nymtech.net/docs/). +If you want to dive deeper into Nym's architecture, clients, nodes, and SDK examples visit the [technical docs](https://nymtech.net/docs/). ## Popular pages - - - -**Network Architecture:** -* [Network Overview](https://nymtech.net/docs/architecture/network-overview.html) -* [Mixnet Traffic Flow](https://nymtech.net/docs/architecture/traffic-flow.html) +**Binary Information** +* [Building Nym](./binaries/building-nym.md) +* [Pre-built Binaries](./binaries/pre-built-binaries.md) +* [Init & Configuration](./binaries/init-and-config.md) **Node setup and usage guides:** * [Mix nodes](./nodes/mix-node-setup.md) From 089d5e6dbb1558550762c425689785957ca2569b Mon Sep 17 00:00:00 2001 From: mfahampshire Date: Thu, 3 Aug 2023 16:43:58 +0200 Subject: [PATCH 225/361] * removed old `wallet-address` flag * 'mixnode' -> 'mix node' for consistency in non-code / non-binary references --- documentation/operators/src/faq.md | 6 +-- .../operators/src/nodes/maintenance.md | 32 ++++++------- .../operators/src/nodes/mix-node-setup.md | 18 ++++---- .../operators/src/nodes/troubleshooting.md | 46 +++++++++---------- .../operators/src/preliminary-steps.md | 12 ++--- 5 files changed, 57 insertions(+), 57 deletions(-) diff --git a/documentation/operators/src/faq.md b/documentation/operators/src/faq.md index bc3c210352..a42af22825 100644 --- a/documentation/operators/src/faq.md +++ b/documentation/operators/src/faq.md @@ -2,7 +2,7 @@ ## Mixnet nodes -### What determines the rewards when running a mixnode? +### What determines the rewards when running a mix node? The stake required for a mix node to achieve maximum rewards is called mix node saturation point. This is calculated from the staking supply (all circulating supply + part of unlocked tokens). The target level of staking is to have 50% of the staking supply locked in mix nodes. @@ -16,7 +16,7 @@ For more detailed calculation, read our blog post [Nym Token Economics update](h Consider in which jurisdiction you reside and where do you want to run a mix node. Do you want to pay by crypto or not and what are the other important particularities for your case? We always recommend operators to try to chose smaller and decentralized VPS providers over the most known ones controlling a majority of the internet. We recieve some good feedback on these: Hertzner, DigitalOcean, Linode, Ghandi and Exoscale. Do your own research and share with the community. - + ### What's the Sphinx packet size? @@ -32,7 +32,7 @@ Right now only mix nodes are rewarded. We're working on gateway and service paym ### Are mixnodes whitelisted? -Nope, anyone can run a mixnode. Purely reliant on the node's reputation (self stake + delegations) & routing score. +Nope, anyone can run a mix node. Purely reliant on the node's reputation (self stake + delegations) & routing score. ## Validators and tokens diff --git a/documentation/operators/src/nodes/maintenance.md b/documentation/operators/src/nodes/maintenance.md index 26d5375a7d..7ffe1d6d58 100644 --- a/documentation/operators/src/nodes/maintenance.md +++ b/documentation/operators/src/nodes/maintenance.md @@ -2,7 +2,7 @@ ``` -The `nym-mixnode` binary is currently one point version ahead of the rest of the platform binaries due to a patch applied between releases. +The `nym-mix node` binary is currently one point version ahead of the rest of the platform binaries due to a patch applied between releases. ## Preliminary steps -Make sure you do the preparation listed in the [preliminary steps page](../preliminary-steps.md) before setting up your mixnode. +Make sure you do the preparation listed in the [preliminary steps page](../preliminary-steps.md) before setting up your mix node. ## Mix node setup -Now that you have built the [codebase](../binaries/building-nym.md), set up your [wallet](https://nymtech.net/docs/wallet/desktop-wallet.html), and have a VPS with the `nym-mixnode` binary, you can set up your mix node with the instructions below. +Now that you have built the [codebase](../binaries/building-nym.md), set up your [wallet](https://nymtech.net/docs/wallet/desktop-wallet.html), and have a VPS with the `nym-mix node` binary, you can set up your mix node with the instructions below. -To begin, move to `/taget/release` directory from which you run the node commands: +To begin, move to `/target/release` directory from which you run the node commands: ``` cd target/release @@ -63,10 +63,10 @@ To check available configuration options for initializing your node use: ``` ~~~ -Initalise your mixnode with the following command, replacing the value of `--id` with the moniker you wish to give your mixnode, and the `--wallet-address` with the Nym address you created earlier. Your `--host` must be publicly routable on the internet in order to mix packets, and can be either an Ipv4 or IPv6 address. The `$(curl ifconfig.me)` command returns your IP automatically using an external service. If you enter your IP address manually, enter it **without** any port information. +Initalise your mix node with the following command, replacing the value of `--id` with the moniker you wish to give your mix node. Your `--host` must be publicly routable on the internet in order to mix packets, and can be either an Ipv4 or IPv6 address. The `$(curl ifconfig.me)` command returns your IP automatically using an external service. If you enter your IP address manually, enter it **without** any port information. ``` -./nym-mixnode init --id --host $(curl ifconfig.me) --wallet-address +./nym-mixnode init --id --host $(curl ifconfig.me) ``` @@ -105,7 +105,7 @@ During the `init` process you will have the option to change the `http_api`, `ve ### Bonding your mix node ```admonish caution -From `v1.1.3`, if you unbond your mixnode that means you are leaving the mixnet and you will lose all your delegations (permanently). You can join again with the same identity key, however, you will start with **no delegations**. +From `v1.1.3`, if you unbond your mix node that means you are leaving the mixnet and you will lose all your delegations (permanently). You can join again with the same identity key, however, you will start with **no delegations**. ``` #### Bond via the Desktop wallet (recommended) @@ -183,7 +183,7 @@ Have a look at the saved configuration files in `$HOME/.nym/mixnodes/` to see mo ## Node Description (optional) -In order to easily identify your node via human-readable information later on in the development of the testnet when delegated staking is implemented, you can `describe` your mixnode with the following command: +In order to easily identify your node via human-readable information later on in the development of the testnet when delegated staking is implemented, you can `describe` your mix node with the following command: ``` ./nym-mixnode describe --id @@ -311,5 +311,5 @@ For more details see [Troubleshooting FAQ](../nodes/troubleshooting.md) ## Maintenance -For mixnode upgrade, firewall setup, port configuration, API endpoints, VPS suggestions, automation and more, see the [maintenance page](./maintenance.md) +For mix node upgrade, firewall setup, port configuration, API endpoints, VPS suggestions, automation and more, see the [maintenance page](./maintenance.md) diff --git a/documentation/operators/src/nodes/troubleshooting.md b/documentation/operators/src/nodes/troubleshooting.md index f063d985e0..d8fb566b58 100644 --- a/documentation/operators/src/nodes/troubleshooting.md +++ b/documentation/operators/src/nodes/troubleshooting.md @@ -78,7 +78,7 @@ Additional details can be obtained via various methods after you connect to your ##### Socket statistics with `ss` ``` -sudo ss -s -t | grep 1789 # if you have specified a different port in your mixnode config, change accordingly +sudo ss -s -t | grep 1789 # if you have specified a different port in your mix node config, change accordingly ``` This command should return a lot of data containing `ESTAB`. This command should work on every unix based system. @@ -90,7 +90,7 @@ This command should return a lot of data containing `ESTAB`. This command should lsof -v # install if not installed sudo apt install lsof -# run against mixnode port +# run against mix node port sudo lsof -i TCP:1789 # if you have specified a different port in your mixnode config, change accordingly ``` @@ -110,7 +110,7 @@ nym-mixno 103349 root 57u IPv6 1333229976 0t0 TCP [2a03:b0c0:3:d0::ff3: sudo journalctl -u nym-mixnode -o cat | grep "Since startup mixed" ``` -If you have created `nym-mixnode.service` file (i.e. you are running your mixnode via `systemd`) then this command shows you how many packets have you mixed so far, and should return a list of messages like this: +If you have created `nym-mixnode.service` file (i.e. you are running your mix node via `systemd`) then this command shows you how many packets have you mixed so far, and should return a list of messages like this: ``` 2021-05-18T12:35:24.057Z INFO nym_mixnode::node::metrics > Since startup mixed 233639 packets! @@ -140,7 +140,7 @@ For example `./target/debug/nym-network-requester --no-banner build-info --outpu nmap -p 1789 -Pn ``` -If your mixnode is configured properly it should output something like this: +If your mix node is configured properly it should output something like this: ``` bob@desktop:~$ nmap -p 1789 95.296.134.220 -Pn @@ -164,7 +164,7 @@ You can query gateways by replacing `mixnodes` with `gateways` in the above comm #### Check with Network API -We currently have an API set up returning our metrics tests of the network. There are two endpoints to ping for information about your mixnode, `report` and `history`. Find more information about this in the [Mixnodes metrics documentation](./maintenance.md#metrics--api-endpoints). +We currently have an API set up returning our metrics tests of the network. There are two endpoints to ping for information about your mix node, `report` and `history`. Find more information about this in the [Mixnodes metrics documentation](./maintenance.md#metrics--api-endpoints). ### Why is my node not mixing any packets? @@ -172,24 +172,24 @@ If you are still unable to see your node on the dashboard, or your node is decla - The firewall on your host machine is not configured properly. Checkout the [instructions](./maintenance.md#configure-your-firewall). - You provided incorrect information when bonding your node. -- You are running your mixnode from a VPS without IPv6 support. -- You did not use the `--announce-host` flag while running the mixnode from your local machine behind NAT. -- You did not configure your router firewall while running the mixnode from your local machine behind NAT, or you are lacking IPv6 support. -- Your mixnode is not running at all, it has either exited / panicked or you closed the session without making the node persistent. Check out the [instructions](./maintenance.md#automating-your-node-with-tmux-and-systemd). +- You are running your mix node from a VPS without IPv6 support. +- You did not use the `--announce-host` flag while running the mix node from your local machine behind NAT. +- You did not configure your router firewall while running the mix node from your local machine behind NAT, or you are lacking IPv6 support. +- Your mix node is not running at all, it has either exited / panicked or you closed the session without making the node persistent. Check out the [instructions](./maintenance.md#automating-your-node-with-tmux-and-systemd). ```admonish caution -Your mixnode **must speak both IPv4 and IPv6** in order to cooperate with other nodes and route traffic. This is a common reason behind many errors we are seeing among node operators, so check with your provider that your VPS is able to do this! +Your mix node **must speak both IPv4 and IPv6** in order to cooperate with other nodes and route traffic. This is a common reason behind many errors we are seeing among node operators, so check with your provider that your VPS is able to do this! ``` #### Incorrect bonding information -Check that you have provided the correct information when bonding your mixnode in the web wallet [interface](https://sandbox-wallet.nymtech.net/). When in doubt, un-bond and then re-bond your node! +Check that you have provided the correct information when bonding your mix node in the web wallet [interface](https://sandbox-wallet.nymtech.net/). When in doubt, un-bond and then re-bond your node! -> All delegated stake will be lost when un-bonding! However the mixnode must be operational in the first place for the delegation to have any effect. +> All delegated stake will be lost when un-bonding! However the mix node must be operational in the first place for the delegation to have any effect. #### Missing `announce-host` flag -On certain cloud providers such as AWS and Google Cloud, you need to do some additional configuration of your firewall and use `--host` with your **local ip** and `--announce-host` with the **public ip** of your mixnode host. +On certain cloud providers such as AWS and Google Cloud, you need to do some additional configuration of your firewall and use `--host` with your **local ip** and `--announce-host` with the **public ip** of your mix node host. If the difference between the two is unclear, contact the help desk of your VPS provider. @@ -222,15 +222,15 @@ bob@nym:~$ hostname -I ### Running on a local machine behind NAT with no fixed IP address -Your ISP has to be IPv6 ready if you want to run a mixnode on your local machine. Sadly, in 2020, most of them are not and you won't get an IPv6 address by default from your ISP. Usually it is a extra paid service or they simply don't offer it. +Your ISP has to be IPv6 ready if you want to run a mix node on your local machine. Sadly, in 2020, most of them are not and you won't get an IPv6 address by default from your ISP. Usually it is a extra paid service or they simply don't offer it. Before you begin, check if you have IPv6 [here](https://test-ipv6.cz/) or by running command explained in the [section above](./troubleshooting.md#no-ipv6-connectivity). If not, then don't waste your time to run a node which won't ever be able to mix any packet due to this limitation. Call your ISP and ask for IPv6, there is a plenty of it for everyone! -If all goes well and you have IPv6 available, then you will need to `init` the mixnode with an extra flag, `--announce-host`. You will also need to edit your `config.toml` file each time your IPv4 address changes, that could be a few days or a few weeks. Check the your IPv4 in the [section above](./troubleshooting.md#no-ipv6-connectivity). +If all goes well and you have IPv6 available, then you will need to `init` the mix node with an extra flag, `--announce-host`. You will also need to edit your `config.toml` file each time your IPv4 address changes, that could be a few days or a few weeks. Check the your IPv4 in the [section above](./troubleshooting.md#no-ipv6-connectivity). Additional configuration on your router might also be needed to allow traffic in and out to port 1789 and IPv6 support. -Here is a sample of the `init` command example to create the mixnode config. +Here is a sample of the `init` command example to create the mix node config. ``` ./nym-mixnode init --id --host 0.0.0.0 --announce-host 85.160.12.13 @@ -244,7 +244,7 @@ Make sure you check if your node is really mixing. We are aiming to improve the ### Accidentally killing your node process on exiting session -When you close your current terminal session, you need to make sure you don't kill the mixnode process! There are multiple ways on how to make it persistent even after exiting your ssh session, the easiest solution is to use `tmux` or `nohup`, and the more elegant solution is to run the node with `systemd`. Read the automation manual [here](./maintenance.md#automating-your-node-with-tmux-and-systemd). +When you close your current terminal session, you need to make sure you don't kill the mix node process! There are multiple ways on how to make it persistent even after exiting your ssh session, the easiest solution is to use `tmux` or `nohup`, and the more elegant solution is to run the node with `systemd`. Read the automation manual [here](./maintenance.md#automating-your-node-with-tmux-and-systemd). ### Common errors and warnings @@ -273,24 +273,24 @@ Rocket runs on port `8000` by default. Although at this stage we need Rocket to #### `failed to receive reply to our echo packet within 1.5s. Stopping the test` -This relates to the VerLoc implementation that appeared in `0.10.1`, which has a particularly high log sensitivity. This warning means that the echo packet sent to the mixnode was received, but not sent back. _This will not affect the rate of rewards or performance metrics of your mixnode in the testnet at this point._ +This relates to the VerLoc implementation that appeared in `0.10.1`, which has a particularly high log sensitivity. This warning means that the echo packet sent to the mix node was received, but not sent back. _This will not affect the rate of rewards or performance metrics of your mix node in the testnet at this point._ #### `Connection to :1789 seems to be dead` -This warning is normal at the moment, and is _nothing to do with your mixnode!_ It is simply a warning that your node is unable to connect to other peoples' mixnodes for some reason, most likely because they are offline or poorly configured. +This warning is normal at the moment, and is _nothing to do with your mix node!_ It is simply a warning that your node is unable to connect to other peoples' mixnodes for some reason, most likely because they are offline or poorly configured. ---> ### Can I use a port other than 1789 ? Yes! Here is what you will need to do: -Assuming you would like to use port `1337` for your mixnode, you need to open the new port (and close the old one): +Assuming you would like to use port `1337` for your mix node, you need to open the new port (and close the old one): ``` sudo ufw allow 1337 sudo ufw deny 1789 ``` -And then edit the mixnode's config. +And then edit the mix node's config. > If you want to change the port for an already running node, you need to stop the process before editing your config file. @@ -304,9 +304,9 @@ nano ~/.nym/mixnodes/alice-node/config/config.toml You will need to edit two parts of the file. `announce_address` and `listening_address` in the config.toml file. Simply replace `:1789` (the default port) with `:1337` (your new port) after your IP address. -Finally, restart your node. You should see if the mixnode is using the port you have changed in the config.toml file right after you run the node. +Finally, restart your node. You should see if the mix node is using the port you have changed in the config.toml file right after you run the node. -### What is `verloc` and do I have to configure my mixnode to implement it? +### What is `verloc` and do I have to configure my mix node to implement it? `verloc` is short for _verifiable location_. Mixnodes and gateways now measure speed-of-light distances to each other, in an attempt to verify how far apart they are. In later releases, this will allow us to algorithmically verify node locations in a non-fakeable and trustworthy manner. diff --git a/documentation/operators/src/preliminary-steps.md b/documentation/operators/src/preliminary-steps.md index 53f5383838..d36ca76da4 100644 --- a/documentation/operators/src/preliminary-steps.md +++ b/documentation/operators/src/preliminary-steps.md @@ -1,23 +1,23 @@ # Preliminary Steps -> The Nym `mixnode`, `gateway` and `network-requester` binaries were built in the [building nym](../binaries/building-nym.md) section. If you haven't yet built Nym and want to run the code, go there first. +> The Nym `mix node`, `gateway` and `network-requester` binaries were built in the [building nym](../binaries/building-nym.md) section. If you haven't yet built Nym and want to run the code, go there first. ## Preliminary steps -There are a couple of steps that need completing before starting to set up your mixnode, gateway or a network requester: +There are a couple of steps that need completing before starting to set up your mix node, gateway or a network requester: - preparing your [desktop wallet](https://nymtech.net/docs/wallet/desktop-wallet.html) or [CLI wallet](https://nymtech.net/docs/wallet/cli-wallet.html). - requisitioning a VPS (Virtual Private Server) ### Wallet preparation #### Mainnet -Before you initialise and run your mixnode, head to our [website](https://nymtech.net/download/) and download the Nym wallet for your operating system. If pre-compiled binaries for your operating system aren't available, you can build the wallet yourself with instructions [here](https://nymtech.net/docs/wallet/desktop-wallet.html). +Before you initialise and run your mix node, head to our [website](https://nymtech.net/download/) and download the Nym wallet for your operating system. If pre-compiled binaries for your operating system aren't available, you can build the wallet yourself with instructions [here](https://nymtech.net/docs/wallet/desktop-wallet.html). -If you don't already have one, please create a Nym address using the wallet, and fund it with tokens. The minimum amount required to bond a mixnode is 100 `NYM`, but make sure you have a bit more to account for gas costs. +If you don't already have one, please create a Nym address using the wallet, and fund it with tokens. The minimum amount required to bond a mix node is 100 `NYM`, but make sure you have a bit more to account for gas costs. `NYM` can be purchased via Bity from the wallet itself with BTC or fiat, and is currently present on several [exchanges](https://www.coingecko.com/en/coins/nym#markets). -> Remember that you can **only** use Cosmos `NYM` tokens to bond your mixnode. You **cannot** use ERC20 representations of `NYM` to run a node. +> Remember that you can **only** use Cosmos `NYM` tokens to bond your mix node. You **cannot** use ERC20 representations of `NYM` to run a node. #### Sandbox testnet @@ -32,7 +32,7 @@ For now, see the below rough specs: - Processors: 2 cores are fine. Get the fastest CPUs you can afford. -#### For Mixnode +#### For mix node - RAM: Memory requirements are very low - typically a mix node may use only a few hundred MB of RAM. - Disks: The mixnodes require no disk space beyond a few bytes for the configuration files. From f17d2cec0dab9f4afdd3713cf734d69d1f044625 Mon Sep 17 00:00:00 2001 From: mfahampshire Date: Thu, 3 Aug 2023 16:46:05 +0200 Subject: [PATCH 226/361] typo fix --- documentation/operators/src/nodes/gateway-setup.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/documentation/operators/src/nodes/gateway-setup.md b/documentation/operators/src/nodes/gateway-setup.md index 026e7afde0..f1cb589756 100644 --- a/documentation/operators/src/nodes/gateway-setup.md +++ b/documentation/operators/src/nodes/gateway-setup.md @@ -17,7 +17,7 @@ Make sure you do the preparation listed in the [preliminary steps page](../preli ## Gateway setup Now that you have built the codebase, set up your wallet, and have a VPS with the `nym-gateway` binary, you can set up your gateway with the instructions below. -To begin, move to `/taget/release` directory from which you run the node commands: +To begin, move to `/target/release` directory from which you run the node commands: ``` cd target/release From 1f226682f475df40d1ee7e4fd7837e46c5c3f384 Mon Sep 17 00:00:00 2001 From: mfahampshire Date: Thu, 3 Aug 2023 19:28:13 +0200 Subject: [PATCH 227/361] added custom 404 + updated platform version --- documentation/docs/book.toml | 4 ++-- documentation/docs/src/not-found.md | 11 +++++++++++ 2 files changed, 13 insertions(+), 2 deletions(-) create mode 100644 documentation/docs/src/not-found.md diff --git a/documentation/docs/book.toml b/documentation/docs/book.toml index 996a7ec4e4..7dfd751b15 100644 --- a/documentation/docs/book.toml +++ b/documentation/docs/book.toml @@ -49,7 +49,7 @@ assets_version = "2.0.0" # do not edit: managed by `mdbook-admonish install` [preprocessor.variables.variables] minimum_rust_version = "1.66" # vars for links: TODO think on how to streamline updating -platform_release_version = "v1.1.25" +platform_release_version = "v1.1.26" wallet_release_version = "v1.2.7" [preprocessor.last-changed] @@ -92,7 +92,7 @@ git-repository-icon = "fa-github" # edit-url-template = "https://github.com/rust-lang/mdBook/edit/master/guide/{path}" # site-url = "/docs/" # cname = "nymtech.net" -# input-404 = "not-found.md" # todo make custom 404 page +input-404 = "not-found.md" [output.html.fold] enable = true # whether or not to enable section folding diff --git a/documentation/docs/src/not-found.md b/documentation/docs/src/not-found.md new file mode 100644 index 0000000000..3d203a9bb8 --- /dev/null +++ b/documentation/docs/src/not-found.md @@ -0,0 +1,11 @@ +# Page Not Found + +You seem to have followed a link to a page that no longer exists. Our documentation is changing and growing over time, so this page has most likely been moved somewhere else. + +For node setup guides, see the [Operators Guides book](https://nymtech.net/operators). + +For developer tutorials, app guides, and demo and community apps, see the [Developer Portal](https://nymtech.net/developers). + +If you are looking for information on network architecture traffic flow, clients, the SDKs, or interacting with the Nyx blockchain, check the sitemap on the left hand side of the screen. + +If you still can't find what you're looking for get in touch with us via [Matrix](TODO). \ No newline at end of file From 6d5ff1146bf6563fc881ae724f0c6a8076923cb8 Mon Sep 17 00:00:00 2001 From: mfahampshire Date: Thu, 3 Aug 2023 19:28:37 +0200 Subject: [PATCH 228/361] * deleted old setup guides * added new info on operator guides * adding stubs for in-depth node pages --- documentation/docs/src/SUMMARY.md | 13 +- documentation/docs/src/nodes/gateway.md | 1 + documentation/docs/src/nodes/mixnode.md | 17 + .../docs/src/nodes/network-requester.md | 1 + documentation/docs/src/nodes/overview.md | 9 + documentation/docs/src/nodes/setup-guides.md | 8 - .../docs/src/nodes/troubleshooting.md | 368 ---------- .../docs/src/nodes/validator-setup.md | 669 ------------------ documentation/docs/src/nodes/validator.md | 12 + 9 files changed, 46 insertions(+), 1052 deletions(-) create mode 100644 documentation/docs/src/nodes/gateway.md create mode 100644 documentation/docs/src/nodes/mixnode.md create mode 100644 documentation/docs/src/nodes/network-requester.md create mode 100644 documentation/docs/src/nodes/overview.md delete mode 100644 documentation/docs/src/nodes/setup-guides.md delete mode 100644 documentation/docs/src/nodes/troubleshooting.md delete mode 100644 documentation/docs/src/nodes/validator-setup.md create mode 100644 documentation/docs/src/nodes/validator.md diff --git a/documentation/docs/src/SUMMARY.md b/documentation/docs/src/SUMMARY.md index 8d9c174f5a..c9fc9c6d1d 100644 --- a/documentation/docs/src/SUMMARY.md +++ b/documentation/docs/src/SUMMARY.md @@ -14,12 +14,11 @@ # Nodes -- [Node Setup Guides](nodes/setup-guides.md) - - [Mix Node](nodes/mix-node-setup.md) - - [Gateway](nodes/gateway-setup.md) - - [Network Requester](nodes/network-requester-setup.md) - - [Validator](nodes/validator-setup.md) -- [Troubleshooting](nodes/troubleshooting.md) +- [Node Types (Previously Setup Guides)](nodes/overview.md) + - [Mix Node](nodes/mixnode.md) + - [Gateway](nodes/gateway.md) + - [Network Requester](nodes/network-requester.md) + - [Validator](nodes/validator.md) # Clients - [Clients Overview](clients/overview.md) @@ -40,7 +39,7 @@ - [Mixnet Explorer](explorers/mixnet-explorer.md) # Nyx Blockchain - +- [Interacting with Nyx Chain and Smart Contracts](nyx/interacting-with-chain.md) - [Smart Contracts](nyx/smart-contracts.md) - [Mixnet Contract](nyx/mixnet-contract.md) - [Vesting Contract](nyx/vesting-contract.md) diff --git a/documentation/docs/src/nodes/gateway.md b/documentation/docs/src/nodes/gateway.md new file mode 100644 index 0000000000..f4ee68e242 --- /dev/null +++ b/documentation/docs/src/nodes/gateway.md @@ -0,0 +1 @@ +# Gateway diff --git a/documentation/docs/src/nodes/mixnode.md b/documentation/docs/src/nodes/mixnode.md new file mode 100644 index 0000000000..159251ad3e --- /dev/null +++ b/documentation/docs/src/nodes/mixnode.md @@ -0,0 +1,17 @@ +# Mix Node + +> The mix node setup and maintenance guide has migrated to the [Operator Guides book](TO_DO). + +Mix nodes are the backbone of the mixnet. These are the nodes that perform 'mix mining', otherwise known simply as 'mixing' or performing the 'poisson mix'. + +Mix nodes perform one task: receiving packets, decrypting their outer 'layer', and holding them for a variable amount of time before passing them to their next destination - either another mix node, or a gateway. In doing so, they 'mix' packets by sending them to their next destination in a different order than they were recieved. + +The aim of this mixing is to protect against timing-based deanonymisation attempts by a global adversary able to monitor the entire network with a 'God's Eye View'. + +## (Coming soon) Mixing: a Step-by-Step Breakdown + +## Further reading +TODO +*
    +* +* diff --git a/documentation/docs/src/nodes/network-requester.md b/documentation/docs/src/nodes/network-requester.md new file mode 100644 index 0000000000..797dfd7795 --- /dev/null +++ b/documentation/docs/src/nodes/network-requester.md @@ -0,0 +1 @@ +# Network Requester diff --git a/documentation/docs/src/nodes/overview.md b/documentation/docs/src/nodes/overview.md new file mode 100644 index 0000000000..497a347365 --- /dev/null +++ b/documentation/docs/src/nodes/overview.md @@ -0,0 +1,9 @@ +# Node Types + +> This section was previously the node setup guides. These have been migrated to their own [**Operator Guides**](https://nymtech.net/operators) book. +> +> For setup and maintenance guides, go to the Operators book linked above. +> +> This section is a little spartan for the moment, but we will be adding detailed information about how exactly each node functions, as well as references to any literature and technical specs in the near future. + +This section contains information on the different node types of the mixnet and blockchain. \ No newline at end of file diff --git a/documentation/docs/src/nodes/setup-guides.md b/documentation/docs/src/nodes/setup-guides.md deleted file mode 100644 index 02a23ebff6..0000000000 --- a/documentation/docs/src/nodes/setup-guides.md +++ /dev/null @@ -1,8 +0,0 @@ -# Node Setup Guides - -This section contains setup guides for the following node types: -* [Mix node](./mix-node-setup.md) -* [Gateway](./gateway-setup.md) -* [Network Requester](./network-requester-setup.md) -* [Validator](./validator-setup.md) - diff --git a/documentation/docs/src/nodes/troubleshooting.md b/documentation/docs/src/nodes/troubleshooting.md deleted file mode 100644 index e832edbc39..0000000000 --- a/documentation/docs/src/nodes/troubleshooting.md +++ /dev/null @@ -1,368 +0,0 @@ -# Troubleshooting - - -## Binary Build Problems - -### I am trying to build from the GitHub archive files and the build fails - -GitHub automatically includes .zip and tar.gz files of the Nym repository in its release. You cannot extract these and build - you'll see something like this: - -``` - process didn't exit successfully: `/build/nym/src/nym-0.12.1/target/release/build/nym-socks5-client-c1d0f76a8c7d7e9a/build-script-build` (exit status: 101) - --- stderr - thread 'main' panicked at 'failed to extract build metadata: could not find repository from '/build/nym/src/nym-0.12.1/clients/socks5'; class=Repository (6); code=NotFound (-3)', clients/socks5/build.rs:7:31 - note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace -warning: build failed, waiting for other jobs to finish... -error: build failed -``` - -Why does this happen? - -We have scripts which automatically include the Git commit hash and Git tag in the binary for easier debugging later. If you download a .zip and try building from that, it's not a Git repository and build will fail as above. - -## General Node Config - -### Where can I find my private and public keys and config? - -All config and keys files are stored in a directory named after your `id` which you chose during the `init` process, and can be found at the following PATH: `$HOME/.nym//` where `$HOME` is a home directory of the user (your current user in this case) that launched the node or client. - -The directory structure for each node will be roughly as follows: - -``` -bob@nym:~$ tree /home/nym/.nym/mixnodes/ -/home/nym/.nym/mixnodes/ -|-- nym010 -| |-- config -| | `-- config.toml -| `-- data -| |-- private_identity.pem -| |-- private_sphinx.pem -| |-- public_identity.pem -| `-- public_sphinx.pem -``` - -> If you `cat` the `public_sphinx.pem key`, the output will be different from the public key you will see on Nym [dashboard](https://sandbox-explorer.nymtech.net/). The reason for this is that `.pem` files are encoded in **base64**, however on the web they are in **base58**. Don't be confused if your keys look different. They are the same keys, just with different encoding :) - - -## Mix Nodes - -### How can I tell my node is up and running and mixing traffic? - -First of all check the 'Mixnodes' section of either the Nym Network Explorers: -* [Mainnet](https://explorer.nymtech.net/overview) -* [Sandbox testnet](https://sandbox-explorer.nymtech.net/) - -Enter your **identity key** to find your node. Check the contents of the 'mixnode stats' and 'uptime story' sections. - -There are 2 community explorers currently, which have been created by [Nodes Guru](https://nodes.guru): -* [Mainnet](https://mixnet.explorers.guru/) -* [Sandbox testnet](https://sandbox.mixnet.explorers.guru/) - -If you want more information, or if your node isn't showing up on the explorer of your choice and you want to double-check, here are some examples on how to check if the node is configured properly. - -#### Check from your VPS - -Additional details can be obtained via various methods after you connect to your VPS: - -##### Socket statistics with `ss` - -``` -sudo ss -s -t | grep 1789 # if you have specified a different port in your mixnode config, change accordingly -``` - -This command should return a lot of data containing `ESTAB`. This command should work on every unix based system. - -##### List open files and reliant processes with `lsof` - -``` -# check if lsof is installed: -lsof -v -# install if not installed -sudo apt install lsof -# run against mixnode port -sudo lsof -i TCP:1789 # if you have specified a different port in your mixnode config, change accordingly -``` - -This command should return something like this: - -``` -nym-mixno 103349 root 53u IPv6 1333229972 0t0 TCP [2a03:b0c0:3:d0::ff3:f001]:57844->[2a01:4f9:c011:38ae::5]:1789 (ESTABLISHED) -nym-mixno 103349 root 54u IPv4 1333229973 0t0 TCP nym:57104->194.5.78.73:1789 (ESTABLISHED) -nym-mixno 103349 root 55u IPv4 1333229974 0t0 TCP nym:48130->static.236.109.119.168.clients.your-server.de:1789 (ESTABLISHED) -nym-mixno 103349 root 56u IPv4 1333229975 0t0 TCP nym:52548->vmi572614.contaboserver.net:1789 (ESTABLISHED) -nym-mixno 103349 root 57u IPv6 1333229976 0t0 TCP [2a03:b0c0:3:d0::ff3:f001]:43244->[2600:1f18:1031:2401:c04b:2f25:ca79:fef3]:1789 (ESTABLISHED) -``` - -##### Query `systemd` journal with `journalctl` - -``` -sudo journalctl -u nym-mixnode -o cat | grep "Since startup mixed" -``` - -If you have created `nym-mixnode.service` file (i.e. you are running your mixnode via `systemd`) then this command shows you how many packets have you mixed so far, and should return a list of messages like this: - -``` -2021-05-18T12:35:24.057Z INFO nym_mixnode::node::metrics > Since startup mixed 233639 packets! -2021-05-18T12:38:02.178Z INFO nym_mixnode::node::metrics > Since startup mixed 233739 packets! -2021-05-18T12:40:32.344Z INFO nym_mixnode::node::metrics > Since startup mixed 233837 packets! -2021-05-18T12:46:08.549Z INFO nym_mixnode::node::metrics > Since startup mixed 234081 packets! -2021-05-18T12:56:57.129Z INFO nym_mixnode::node::metrics > Since startup mixed 234491 packets! -``` - -You can add ` | tail` to the end of the command to watch for new entries in real time if needed. - -#### Check from your local machine - -##### Scan ports with `nmap`: - -``` -nmap -p 1789 -Pn -``` - -If your mixnode is configured properly it should output something like this: - -``` -bob@desktop:~$ nmap -p 1789 95.296.134.220 -Pn - -Host is up (0.053s latency). - -PORT STATE SERVICE -1789/tcp open hello -``` - -##### Query online nodes: - -``` -curl --location --request GET 'https://validator.nymtech.net/api/v1/mixnodes/' -``` - -Will return a list all nodes currently online. - -You can query gateways by replacing `mixnodes` with `gateways` in the above command, and can query for the mixnodes and gatways on the Sandbox testnet by replacing `validator` with `sandbox-validator`. - - -#### Check with Network API - -We currently have an API set up returning our metrics tests of the network. There are two endpoints to ping for information about your mixnode, `report` and `history`. Find more information about this in the [Mixnodes metrics documentation](../nodes/mix-node-setup.md#metrics--api-endpoints). - -### Why is my node not mixing any packets? - -If you are still unable to see your node on the dashboard, or your node is declaring it has not mixed any packets, there are several potential issues: - -- The firewall on your host machine is not configured properly. -- You provided incorrect information when bonding your node. -- You are running your mixnode from a VPS without IPv6 support. -- You did not use the `--announce-host` flag while running the mixnode from your local machine behind NAT. -- You did not configure your router firewall while running the mixnode from your local machine behind NAT, or you are lacking IPv6 support. -- Your mixnode is not running at all, it has either exited / panicked or you closed the session without making the node persistent. - -```admonish caution -Your mixnode **must speak both IPv4 and IPv6** in order to cooperate with other nodes and route traffic. This is a common reason behind many errors we are seeing among node operators, so check with your provider that your VPS is able to do this! -``` - -#### Incorrectly configured firewall - -The most common reason your mixnode might not be mixing packets is due to a poorly configured firewall. The following commands will allow you to set up a firewall using `ufw`. - -``` -# check if you have ufw installed -ufw version -# if it is not installed, install with -sudo apt install ufw -y -# enable ufw -sudo ufw enable -# check the status of the firewall -sudo ufw status -``` - -Finally open your mixnode's p2p port, as well as ports for ssh, http, and https connections, and ports `8000` and `1790` for verloc and measurement pings: - -``` -sudo ufw allow 1789,1790,8000,22,80,443/tcp -# check the status of the firewall -sudo ufw status -``` - -#### Incorrect bonding information - -Check that you have provided the correct information when bonding your mixnode in the web wallet [interface](https://sandbox-wallet.nymtech.net/). When in doubt, unbond and then rebond your node! - -#### Missing `announce-host` flag - -On certain cloud providers such as AWS and Google Cloud, you need to do some additional configuration of your firewall and use `--host` with your **local ip** and `--announce-host` with the **public ip** of your mixnode host. - -#### No IPv6 connectivity - -Make sure that your VPS has IPv6 connectivity available with whatever provider you are using. - -To get all ip addresses of your host, try following commands: - -``` -hostname -i -``` - -Will return your **local ip** address. - -``` -hostname -I -``` - -Will return all of the ip addresses of your host. This output should look something like this: - -``` -bob@nym:~$ hostname -I -88.36.11.23 172.18.0.1 2a01:28:ca:102::1:641 -``` - -- The first **ipv4** is the public ip you need to use for the `--announce-host` flag. -- The second **ipv4** is the local ip you need to use for the `--host` flag. -- The 3rd output should confirm if your machine has ipv6 available. - -### Running on a local machine behind NAT with no fixed IP address - -Your ISP has to be IPv6 ready if you want to run a mixnode on your local machine. Sadly, in 2020, most of them are not and you won't get an IPv6 address by default from your ISP. Usually it is a extra paid service or they simply don't offer it. - -Before you begin, check if you have IPv6 [here](https://test-ipv6.cz/). If not, then don't waste your time to run a node which won't ever be able to mix any packet due to this limitation. Call your ISP and ask for IPv6, there is a plenty of it for everyone! - -If all goes well and you have IPv6 available, then you will need to `init` the mixnode with an extra flag, `--announce-host`. You will also need to edit your `config.toml` file each time your IPv4 address changes, that could be a few days or a few weeks. - -Additional configuration on your router might also be needed to allow traffic in and out to port 1789 and IPv6 support. - -Here is a sample of the `init` command to create the mixnode config. - -``` -./target/release/nym-mixnode init --id nym-nat --host 0.0.0.0 --announce-host 85.160.12.13 --layer 3 -``` - -- `--host 0.0.0.0` should work everytime even if your local machine IPv4 address changes. For example on Monday your router gives your machine an address `192.168.0.13` and on Wednesday, the DHCP lease will end and you will be asigned `192.168.0.14`. Using `0.0.0.0` should avoid this without having to set any static ip in your router`s configuration. - -- you can get your current IPv4 address by either using `curl ipinfo.io` if you're on MacOS or Linux or visiting [whatsmyip site](https://www.whatsmyip.org/). Simply copy it and use it as `--anounce-host` address. - -Make sure you check if your node is really mixing. You will need a bit of luck to set this up from your home behind NAT. - -### Accidentally killing your node process on exiting session - -When you close your current terminal session, you need to make sure you don't kill the mixnode process! There are multiple ways on how to make it persistent even after exiting your ssh session, the easiest solution is to use `nohup`, and the more elegant solution is to run the node with `systemd`. - -### Running your mixnode as a background process with `nohup` - -`nohup` is a command with which your terminal is told to ignore the `HUP` or 'hangup' signal. This will stop the mixnode process ending if you kill your session. - -``` -nohup ./nym-mixnode run --id NYM # where `--id NYM` is the id you set during the `init` command. -``` - -### Running your mixnode as a background process with `systemd` - -The most reliable and elegant solution is to create a `systemd.service` file and run the nym-mixnode with `systemctl` command. - -Create a file with `nano` at `/etc/systemd/system/nym-mixnode.service` containing the following: - -```ini -[Unit] -Description=nym mixnode service -After=network.target - -[Service] -Type=simple -User=nym # change as appropriate -LimitNOFILE=65536 -ExecStart=/home/nym/nym-mixnode run --id nym # change as appropriate -KillSignal=SIGINT -Restart=on-failure -RestartSec=30 -Restart=on-abort -[Install] -WantedBy=multi-user.target -``` - -``` -# enable the service -sudo systemctl enable nym-mixnode -# start the service -sudo systemctl start nym-mixnode -# check if the service is running properly and mixnode is mixing -sudo systemctl status nym-mixnode -``` - -Now your node should be mixing all the time, and restart if you reboot your server! - -Anytime you change your `systemd` service file you need to `sudo systemctl daemon-reload` in order to restart the service. - -### Network configuration seems fine but log still claims `Since startup mixed 0 packets!` - -This behavior is most likely caused by a mismatch between your node configuration and the bonding information. Unbond and then rebond your node. - -Also make sure to enter all the information in the web wallet exactly as it appears in the log when you start the mixnode process. In particular, the `host` field must contain the _port_ on which your mixnode will listen: - -- correct host: `34.12.3.43:1789` -- incorrect host:`34.12.3.43` - -### Common errors and warnings - -Most of the `ERROR` and `WARN` messages in your node logs are benign - as long as your node outputs `since startup mixed X packets!` in your logs (and this number increases over time), your node is mixing packets. If you want to be sure, check the Nym [dashboard](https://sandbox-explorer.nymtech.net/) or see other ways on how to check if your node is mixing properly as outlined in the section **How can I tell my node is up and running and mixing traffic?** above. - -More specific errors and warnings are covered below. - -#### `tokio runtime worker` error - -If you are running into issues with an error including the following: - -``` -thread 'tokio-runtime-worker' panicked at 'Failed to create TCP listener: Os { code: 99, kind: AddrNotAvailable, message: "Cannot assign requested address" }' -``` - -Then you need to `--announce-host ` and ``--host ` on startup. This issue arises because of your use of a provider like AWS or Google Cloud, and the fact that your VPS' available bind address is not the same as the public IP address (see [Virtual IPs and hosting via Google and AWS](../nodes/mix-node-setup.md#virtual-ips-and-hosting-via-google--aws) for more information on this issue). - -#### `rocket::launch` warnings -These warnings are not an issue, please ignore them. Rocket is a web framework for rust which we are using to provide mixnodes with `/verloc` and `/description` http APIs. - -Find more information about this in the [Mixnodes metrics documentation](../nodes/mix-node-setup.md#metrics--api-endpoints). - -Rocket runs on port `8000` by default. Although at this stage of the testnet we need Rocket to be reachable via this port, in the future customization of the particular port it uses will be possible. - -#### `failed to receive reply to our echo packet within 1.5s. Stopping the test` - -This relates to the VerLoc implementation that appeared in `0.10.1`, which has a particularly high log sensitivity. This warning means that the echo packet sent to the mixnode was received, but not sent back. _This will not affect the rate of rewards or performance metrics of your mixnode in the testnet at this point._ - -#### `Connection to :1789 seems to be dead` - -This warning is normal at the moment, and is _nothing to do with your mixnode!_ It is simply a warning that your node is unable to connect to other peoples' mixnodes for some reason, most likely because they are offline or poorly configured. - -### Can I use a port other than 1789 ? - -Yes! Here is what you will need to do: - -Assuming you would like to use port `1337` for your mixnode, you need to open the new port (and close the old one): - -``` -sudo ufw allow 1337 -sudo ufw deny 1789 -``` - -And then edit the mixnode's config. - -> If you want to change the port for an already running node, you need to stop the process before editing your config file. - -Assuming your node name is `nym`, the config file is located at `~/.nym/mixnodes/nym/config/config.toml`. - -``` -nano ~/.nym/mixnodes/nym/config/config.toml -``` - -You will need to edit two parts of the file. `announce_address` and `listening_address` in the config.toml file. Simply replace `:1789` (the default port) with `:1337` (your new port) after your IP address. - -Finally, restart your node. You should see if the mixnode is using the port you have changed in the config.toml file right after you run the node. - -### What is `verloc` and do I have to configure my mixnode to implement it? - -`verloc` is short for _verifiable location_. Mixnodes and gateways now measure speed-of-light distances to each other, in an attempt to verify how far apart they are. In later releases, this will allow us to algorithmically verify node locations in a non-fakeable and trustworthy manner. - -You don't have to do any additional configuration for your node to implement this, it is a passive process that runs in the background of the mixnet from version `0.10.1` onwards. - - -### Where can I get more help? - -The fastest way to reach one of us or get a help from the community, visit our [Telegram help chat](https://t.me/nymchan_help_chat) or head to our [Discord](https://Discord.gg/nym) - -For more tech heavy questions join our Keybase channel. Get Keybase [here](https://keybase.io/), then click Teams -> Join a team. Type nymtech.friends into the team name and hit continue. For general chat, hang out in the #general channel. diff --git a/documentation/docs/src/nodes/validator-setup.md b/documentation/docs/src/nodes/validator-setup.md deleted file mode 100644 index 053a19253d..0000000000 --- a/documentation/docs/src/nodes/validator-setup.md +++ /dev/null @@ -1,669 +0,0 @@ -# Validators - -The validator is built using [Cosmos SDK](https://cosmos.network) and [Tendermint](https://tendermint.com), with a [CosmWasm](https://cosmwasm.com) smart contract controlling the directory service, node bonding, and delegated mixnet staking. - -## Building your validator -### Prerequisites -#### `git`, `gcc`, `jq` -* Debian-based systems: -``` -apt install git build-essential jq - -# optional additional manual pages can be installed with: -apt-get install manpages-dev -``` - -* Arch-based systems: -Install `git`, `gcc` and `jq` with the following: -``` -pacman -S git gcc jq -``` - -#### `Go` -`Go` can be installed via the following commands (taken from the [Agoric SDK docs](https://github.com/Agoric/agoric-sdk/wiki/Validator-Guide-for-Incentivized-Testnet#install-go)): - -``` -# First remove any existing old Go installation -sudo rm -rf /usr/local/go - -# Install correct Go version -curl https://dl.google.com/go/go1.20.4.linux-amd64.tar.gz | sudo tar -C/usr/local -zxvf - - -# Update environment variables to include go -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 -``` - -Verify `Go` is installed with: - -``` -go version -# Should return something like: -go version go1.20.4 linux/amd64 -``` - -### Download a precompiled validator binary -You can find pre-compiled binaries for Ubuntu `22.04` and `20.04` [here](https://github.com/nymtech/nyxd/releases). - -```admonish caution title="" -There are seperate releases for Mainnet and the Sandbox testnet - make sure to download the correct binary to avoid `bech32Prefix` mismatches. -``` - -### Manually compiling your validator binary -The codebase for the Nyx validators can be found [here](https://github.com/nymtech/nyxd). - -The validator binary can be compiled by running the following commands: -``` -git clone https://github.com/nymtech/nyxd.git -cd nyxd -git checkout release/ - -# Mainnet -make build - -# Sandbox testnet -BECH32_PREFIX=nymt make build -``` - -At this point, you will have a copy of the `nyxd` binary in your `build/` directory. Test that it's compiled properly by running: - -``` -./build/nyxd -``` - -You should see help text print out. - -### Linking `nyxd` to `libwasmvm.so` - -`libwasmvm.so` is the wasm virtual machine which is needed to execute smart contracts in `v0.26.1`. This file is renamed in `libwasmvm.x86_64.so` in `v0.31.1`. - -If you downloaded your `nyxd` binary from Github, you will have seen this file when un-`tar`-ing the `.tar.gz` file from the releases page. - -If you are seeing an error concerning this file when trying to run `nyxd`, then you need to move the `libwasmvm.so` file to correct location. - -Simply `cp` or `mv` that file to `/lib/x86_64-linux-gnu/` and re-run `nyxd`. - -### Adding `nyxd` to your `$PATH` -You'll need to set `LD_LIBRARY_PATH` in your user's `~/.bashrc` file, and add that to our path. Replace `/home/youruser/path/to/nym/binaries` in the command below to the locations of `nyxd` and `libwasmvm.so` and run it. If you have compiled these on the server, they will be in the `build/` folder: - -``` -NYX_BINARIES=/home/youruser/path/to/validator/binary - -# if you are using another shell like zsh replace '.bashrc' with the relevant config file -echo 'export LD_LIBRARY_PATH=${LD_LIBRARY_PATH}:'NYX_BINARIES >> ~/.bashrc -echo 'export PATH=$PATH:'${NYX_BINARIES} >> ~/.bashrc -source ~/.bashrc -``` - -Test everything worked: - -``` -nyxd -``` - -This should return the regular help text. - -## Initialising your validator -### Prerequisites: - -- FQDN Domain name -- IPv4 and IPv6 connectivity - -Choose a name for your validator and use it in place of `` in the following command: - -``` -# Mainnet -nyxd init --chain-id=nyx - -# Sandbox testnet -nyxd init --chain-id=sandbox -``` - -```admonish caution title="" -`init` generates `priv_validator_key.json` and `node_key.json`. - -If you have already set up a validator on a network, **make sure to back up the key located at** -`~/.nyxd/config/priv_validator_key.json`. - -If you don't save the validator key, then it can't sign blocks and will be jailed all the time, and -there is no way to deterministically (re)generate this key. -``` - -At this point, you have a new validator, with its own genesis file located at `$HOME/.nyxd/config/genesis.json`. You will need to replace the contents of that file that with either the Nyx Mainnet or Sandbox Testnet genesis file. - -You can use the following command to download them for the correct network: - -``` -# Mainnet -wget -O $HOME/.nyxd/config/genesis.json https://nymtech.net/genesis/genesis.json - -# Sandbox testnet -wget -O $HOME/.nyxd/config/genesis.json https://sandbox-validator1.nymtech.net/snapshots/genesis.json -``` - -### `config.toml` configuration - -Edit the following config options in `$HOME/.nyxd/config/config.toml` to match the information below for your network: - -``` -# Mainnet -persistent_peers = "ee03a6777fb76a2efd0106c3769daaa064a3fcb5@51.79.21.187:26656" -create_empty_blocks = false -laddr = "tcp://0.0.0.0:26656" -``` - -``` -# Sandbox testnet -cors_allowed_origins = ["*"] -persistent_peers = "8421c0a3d90d490e27e8061f2abcb1276c8358b6@sandbox-validator1.nymtech.net:26666" -create_empty_blocks = false -laddr = "tcp://0.0.0.0:26656" -``` - -These affect the following: -* `persistent_peers = "@.nymtech.net:26666"` allows your validator to start pulling blocks from other validators. **The main sandbox validator listens on `26666` instead of the default `26656` for debugging**. It is recommended you do not change your port from `26656`. -* `create_empty_blocks = false` will save space -* `laddr = "tcp://0.0.0.0:26656"` is in your p2p configuration options - -Optionally, if you want to enable [Prometheus](https://prometheus.io/) metrics then the following must also match in the `config.toml`: - -- `prometheus = true` -- `prometheus_listen_addr = ":26660"` - -> Remember to enable metrics in the 'Configuring Prometheus metrics' section below as well. - -And if you wish to add a human-readable moniker to your node: - -- `moniker = "yourname"` - -Finally, if you plan on using [Cockpit](https://cockpit-project.org/documentation.html) on your server, change the `grpc` port from `9090` as this is the port used by Cockpit. - -### `app.toml` configuration -In the file `$HOME/nyxd/config/app.toml`, set the following values: - -``` -# Mainnet -minimum-gas-prices = "0.025unym,0.025unyx" -enable = true in the `[api]` section to get the API server running -``` -``` -# Sandbox Testnet -minimum-gas-prices = "0.025unymt,0.025unyxt" -enable = true` in the `[api]` section to get the API server running -``` - -### Setting up your validator's admin user -You'll need an admin account to be in charge of your validator. Set that up with: - -``` -nyxd keys add nyxd-admin -``` - -This will add keys for your administrator account to your system's keychain and log your name, address, public key, and mnemonic. As the instructions say, remember to **write down your mnemonic**. - -You can get the admin account's address with: - -``` -nyxd keys show nyxd-admin -a -``` - -Type in your keychain **password**, not the mnemonic, when asked. - -## Starting your validator - -```admonish caution title="" -If you are running a Sandbox testnet validator, please skip the `validate-genesis` command: it will fail due to the size of the genesis file as this is a fork of an existing chain state. -``` - -Everything should now be ready to go. You've got the validator set up, all changes made in `config.toml` and `app.toml`, the Nym genesis file copied into place (replacing the initial auto-generated one). Now let's validate the whole setup: - -``` -nyxd validate-genesis -``` - -If this check passes, you should receive the following output: - -``` -File at /path/to/genesis.json is a valid genesis file -``` - -> If this test did not pass, check that you have replaced the contents of `/path/to/.nymd/config/genesis.json` with that of the correct genesis file. - -Before starting the validator, we will need to open the firewall ports: - -``` -# if ufw is not already installed: -sudo apt install ufw -sudo ufw enable -sudo ufw allow 1317,26656,26660,22,80,443/tcp -# to check everything worked -sudo ufw status -``` - -Ports `22`, `80`, and `443` are for ssh, http, and https connections respectively. The rest of the ports are documented [here](https://docs.cosmos.network/v0.42/core/grpc_rest.html). - -For more information about your validator's port configuration, check the [validator port reference table](#validator-port-reference) below. - -> If you are planning to use [Cockpit](https://cockpit-project.org/) on your validator server then you will have defined a different `grpc` port in your `config.toml` above: remember to open this port as well. - -Start the validator: - -``` -nyxd start -``` - -Once your validator starts, it will start requesting blocks from other validators. This may take several hours. Once it's up to date, you can issue a request to join the validator set with the command below. - -### Syncing from a snapshot -If you wish to sync from a snapshot on **mainnet** use Polkachu's [mainnet](https://polkachu.com/networks/nym) resources. - -If you wish to sync from a snapshot on **Sandbox testnet** use the below commands, which are a modified version of Polkachu's excellent resources. These commands assume you are running an OS with `apt` as the package manager: - -``` -# install lz4 if necessary -sudo apt install snapd -y -sudo snap install lz4 - -# download the snapshot -wget -O nyxd-sandbox-snapshot-data.tar.lz4 https://sandbox-validator1.nymtech.net/snapshots/nyxd-sandbox-snapshot-data.tar.lz4 - -# reset your validator state -nyxd tendermint unsafe-reset-all - -# unpack the snapshot -lz4 -c -d nyxd-sandbox-snapshot-data.tar.lz4 | tar -x -C $HOME/.nyxd -``` - -You can then restart `nyxd` - it should start syncing from a block > 2000000. - -### Joining Consensus -```admonish caution title="" -When joining consensus, make sure that you do not disrupt (or worse - halt) the network by coming in with a disproportionately large amount of staked tokens. - -Please initially stake a small amount of tokens compared to existing validators, then delegate to yourself in tranches over time. -``` - -Once your validator has synced and you have received tokens, you can join consensus and produce blocks. - -``` -# Mainnet -nyxd tx staking create-validator - --amount=10000000unyx - --fees=0unyx - --pubkey=$(/home/youruser/path/to/nyxd/binaries/nyxd tendermint show-validator) - --moniker="whatever you called your validator" - --chain-id=nyx - --commission-rate="0.10" - --commission-max-rate="0.20" - --commission-max-change-rate="0.01" - --min-self-delegation="1" - --gas="auto" - --gas-adjustment=1.15 - --from="KEYRING_NAME" - --node https://rpc-1.nyx.nodes.guru:443 -``` -``` -# Sandbox Testnet -nyxd tx staking create-validator - --amount=10000000unyxt - --fees=5000unyxt - --pubkey=$(/home/youruser/path/to/nym/binaries/nyxd tendermint show-validator) - --moniker="whatever you called your validator" - --chain-id=sandbox - --commission-rate="0.10" - --commission-max-rate="0.20" - --commission-max-change-rate="0.01" - --min-self-delegation="1" - --gas="auto" - --gas-adjustment=1.15 - --from="KEYRING_NAME" - --node https://sandbox-validator1.nymtech.net:443 -``` - -You'll need either `unyxt` tokens on Sandbox, or `unyx` tokens on mainnet to perform this command. - -> We are currently working towards building up a closed set of reputable validators. You can ask us for coins to get in, but please don't be offended if we say no - validators are part of our system's core security and we are starting out with people we already know or who have a solid reputation. - -If you want to edit some details for your node you will use a command like this: - -``` -# Mainnet -nyxd tx staking edit-validator - --chain-id=nyx - --moniker="whatever you called your validator" - --details="Nyx validator" - --security-contact="your email" - --identity="your identity" - --gas="auto" - --gas-adjustment=1.15 - --from="KEYRING_NAME" - --fees 2000unyx -``` -``` -# Sandbox testnet -nyxd tx staking edit-validator - --chain-id=sandbox - --moniker="whatever you called your validator" - --details="Sandbox testnet validator" - --security-contact="your email" - --identity="your identity" - --gas="auto" - --gas-adjustment=1.15 - --from="KEYRING_NAME" - --fees 2000unyxt -``` - -With above command you can specify the `gpg` key last numbers (as used in `keybase`) as well as validator details and your email for security contact. - -### Automating your validator with systemd -You will most likely want to automate your validator restarting if your server reboots. Below is a systemd unit file to place at `/etc/systemd/system/nymd.service`: - -```ini -[Unit] -Description=Nyxd -StartLimitInterval=350 -StartLimitBurst=10 - -[Service] -User=nyx # change to your user -Type=simple -Environment="LD_LIBRARY_PATH=/home/youruser/path/to/nyx/binaries" # change to correct path -ExecStart=/home/youruser/path/to/nyx/binaries/nymd start # change to correct path -Restart=on-failure -RestartSec=30 -LimitNOFILE=infinity - -[Install] -WantedBy=multi-user.target -``` - -Proceed to start it with: - -``` -systemctl daemon-reload # to pickup the new unit file -systemctl enable nymd # to enable the service -systemctl start nymd # to actually start the service -journalctl -f # to monitor system logs showing the service start -``` - -### Installing and configuring nginx for HTTPS -#### Setup -[Nginx](https://www.nginx.com/resources/glossary/nginx/#:~:text=NGINX%20is%20open%20source%20software,%2C%20media%20streaming%2C%20and%20more.&text=In%20addition%20to%20its%20HTTP,%2C%20TCP%2C%20and%20UDP%20servers.) is an open source software used for operating high-performance web servers. It allows us to set up reverse proxying on our validator server to improve performance and security. - -Install `nginx` and allow the 'Nginx Full' rule in your firewall: - -``` -sudo ufw allow 'Nginx Full' -``` - -Check nginx is running via systemctl: - -``` -systemctl status nginx -``` - -Which should return: - -``` -● nginx.service - A high performance web server and a reverse proxy server - Loaded: loaded (/lib/systemd/system/nginx.service; enabled; vendor preset: enabled) - Active: active (running) since Fri 2018-04-20 16:08:19 UTC; 3 days ago - Docs: man:nginx(8) - Main PID: 2369 (nginx) - Tasks: 2 (limit: 1153) - CGroup: /system.slice/nginx.service - ├─2369 nginx: master process /usr/sbin/nginx -g daemon on; master_process on; - └─2380 nginx: worker process -``` - -#### Configuration - -Proxying your validator's port `26657` to nginx port `80` can then be done by creating a file with the following at `/etc/nginx/conf.d/validator.conf`: - -``` -server { - listen 80; - listen [::]:80; - server_name "domain_name"; - - location / { - proxy_pass http://127.0.0.1:26657; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header Host $host; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - } -} -``` - -Followed by: - -``` -sudo apt install certbot nginx python3 -certbot --nginx -d nym-validator.yourdomain.com -m you@yourdomain.com --agree-tos --noninteractive --redirect -``` - -```admonish caution title="" -If using a VPS running Ubuntu 20: replace `certbot nginx python3` with `python3-certbot-nginx` -``` - -These commands will get you an https encrypted nginx proxy in front of the API. - -### Configuring Prometheus metrics (optional) - -Configure Prometheus with the following commands (adapted from NodesGuru's [Agoric setup guide](https://nodes.guru/agoric/setup-guide/en)): - -``` -echo 'export OTEL_EXPORTER_PROMETHEUS_PORT=9464' >> $HOME/.bashrc -source ~/.bashrc -sed -i '/\[telemetry\]/{:a;n;/enabled/s/false/true/;Ta}' $HOME/.nymd/config/app.toml -sed -i "s/prometheus-retention-time = 0/prometheus-retention-time = 60/g" $HOME/.nymd/config/app.toml -sudo ufw allow 9464 -echo 'Metrics URL: http://'$(curl -s ifconfig.me)':26660/metrics' -``` - -Your validator's metrics will be available to you at the returned 'Metrics URL'. - -~~~admonish example collapsible=true title="Console output" -``` -# HELP go_gc_duration_seconds A summary of the pause duration of garbage collection cycles. -# TYPE go_gc_duration_seconds summary -go_gc_duration_seconds{quantile="0"} 6.7969e-05 -go_gc_duration_seconds{quantile="0.25"} 7.864e-05 -go_gc_duration_seconds{quantile="0.5"} 8.4591e-05 -go_gc_duration_seconds{quantile="0.75"} 0.000115919 -go_gc_duration_seconds{quantile="1"} 0.001137591 -go_gc_duration_seconds_sum 0.356555301 -go_gc_duration_seconds_count 2448 -# HELP go_goroutines Number of goroutines that currently exist. -# TYPE go_goroutines gauge -go_goroutines 668 -# HELP go_info Information about the Go environment. -# TYPE go_info gauge -go_info{version="go1.15.7"} 1 -# HELP go_memstats_alloc_bytes Number of bytes allocated and still in use. -# TYPE go_memstats_alloc_bytes gauge -go_memstats_alloc_bytes 1.62622216e+08 -# HELP go_memstats_alloc_bytes_total Total number of bytes allocated, even if freed. -# TYPE go_memstats_alloc_bytes_total counter -go_memstats_alloc_bytes_total 2.09341707264e+11 -# HELP go_memstats_buck_hash_sys_bytes Number of bytes used by the profiling bucket hash table. -# TYPE go_memstats_buck_hash_sys_bytes gauge -go_memstats_buck_hash_sys_bytes 5.612319e+06 -# HELP go_memstats_frees_total Total number of frees. -# TYPE go_memstats_frees_total counter -go_memstats_frees_total 2.828263344e+09 -# HELP go_memstats_gc_cpu_fraction The fraction of this program's available CPU time used by the GC since the program started. -# TYPE go_memstats_gc_cpu_fraction gauge -go_memstats_gc_cpu_fraction 0.03357798610671518 -# HELP go_memstats_gc_sys_bytes Number of bytes used for garbage collection system metadata. -# TYPE go_memstats_gc_sys_bytes gauge -go_memstats_gc_sys_bytes 1.3884192e+07 -``` -~~~ - -### Setting the ulimit -Linux machines limit how many open files a user is allowed to have. This is called a `ulimit`. - -`ulimit` is 1024 by default on most systems. It needs to be set higher, because validators make and receive a lot of connections to other nodes. - -If you see errors such as: - -``` -Failed to accept incoming connection - Os { code: 24, kind: Other, message: "Too many open files" } -``` - -This means that the operating system is preventing network connections from being made. - -##### Set the ulimit via `systemd` service file -Query the `ulimit` of your validator with: - -``` -grep -i "open files" /proc/$(ps -A -o pid,cmd|grep nymd | grep -v grep |head -n 1 | awk '{print $1}')/limits -``` - -You'll get back the hard and soft limits, which looks something like this: - -``` -Max open files 65536 65536 files -``` - -If your output is **the same as above**, your node will not encounter any `ulimit` related issues. - -However if either value is `1024`, you must raise the limit via the systemd service file. Add the line: - -``` -LimitNOFILE=65536 -``` - -Reload the daemon: - -``` -systemctl daemon-reload -``` - -or execute this as root for system-wide setting of `ulimit`: - -``` -echo "DefaultLimitNOFILE=65535" >> /etc/systemd/system.conf -``` - -Reboot your machine and restart your node. When it comes back, use `cat /proc/$(pidof nym-validator)/limits | grep "Max open files"` to make sure the limit has changed to 65535. - -##### Set the ulimit on `non-systemd` based distributions -Edit `etc/security/conf` and add the following lines: - -``` -# Example hard limit for max opened files -username hard nofile 4096 -# Example soft limit for max opened files -username soft nofile 4096 -``` - -Then reboot your server and restart your validator. - -## Using your validator -### Unjailing your validator -If your validator gets jailed, you can fix it with the following command: - -``` -# Mainnet -nyxd tx slashing unjail - --broadcast-mode=block - --from="KEYRING_NAME" - --chain-id=nyx - --gas=auto - --gas-adjustment=1.4 - --fees=7000unyx -``` -``` -# Sandbox Testnet -nyxd tx slashing unjail - --broadcast-mode=block - --from="KEYRING_NAME" - --chain-id=sandbox - --gas=auto - --gas-adjustment=1.4 - --fees=7000unyxt -``` - -### Upgrading your validator - -Upgrading from `v0.31.1` -> `v0.32.0` process is fairly simple. Grab the `v0.32.0` release tarball from the [`nyxd` releases page](https://github.com/nymtech/nyxd/releases), and untar it. Inside are two files: - -- the new validator (`nyxd`) v0.32.0 -- the new wasmvm (it depends on your platform, but most common filename is `libwasmvm.x86_64.so`) - -Wait for the upgrade height to be reached and the chain to halt awaiting upgrade, then: - -* copy `libwasmvm.x86_64.so` to the default LD_LIBRARY_PATH on your system (on Ubuntu 20.04 this is `/lib/x86_64-linux-gnu/`) replacing your existing file with the same name. -* swap in your new `nyxd` binary and restart. - -You can also use something like [Cosmovisor](https://github.com/cosmos/cosmos-sdk/tree/main/tools/cosmovisor) - grab the relevant information from the current upgrade proposal [here](https://nym.explorers.guru/proposal/9). - -Note: Cosmovisor will swap the `nyxd` binary, but you'll need to already have the `libwasmvm.x86_64.so` in place. - -#### Common reasons for your validator being jailed - -The most common reason for your validator being jailed is that your validator is out of memory because of bloated syslogs. - -Running the command `df -H` will return the size of the various partitions of your VPS. - -If the `/dev/sda` partition is almost full, try pruning some of the `.gz` syslog archives and restart your validator process. - -### Day 2 operations with your validator - -You can check your current balances with: - -``` -nymd query bank balances ${ADDRESS} -``` - -For example, on the Sanbox testnet this would return: - -```yaml -balances: -- amount: "919376" -denom: unymt -pagination: -next_key: null -total: "0" -``` - -You can, of course, stake back the available balance to your validator with the following command. - -> Remember to save some tokens for gas costs! - -``` -# Mainnet -nyxd tx staking delegate VALOPERADDRESS AMOUNTunym - --from="KEYRING_NAME" - --keyring-backend=os - --chain-id=nyx - --gas="auto" - --gas-adjustment=1.15 - --fees 5000unyx -``` -``` -# Sandbox Testnet -nyxd tx staking delegate VALOPERADDRESS AMOUNTunymt - --from="KEYRING_NAME" - --keyring-backend=os - --chain-id=sandbox - --gas="auto" - --gas-adjustment=1.15 - --fees 5000unyxt -``` - -## Validator port reference -All validator-specific port configuration can be found in `$HOME/.nymd/config/config.toml`. If you do edit any port configs, remember to restart your validator. - -| Default port | Use | -|--------------|--------------------------------------| -| 1317 | REST API server endpoint | -| 26656 | Listen for incoming peer connections | -| 26660 | Listen for Prometheus connections | diff --git a/documentation/docs/src/nodes/validator.md b/documentation/docs/src/nodes/validator.md new file mode 100644 index 0000000000..6b7af1913a --- /dev/null +++ b/documentation/docs/src/nodes/validator.md @@ -0,0 +1,12 @@ +# Validator + +> The validator setup and maintenance guide has moved to the [Operator Guides book](TO_DO). + +Validators secure the Nyx blockchain via Proof of Stake consensus. The Nyx validators are run via the `nyxd` binary ([codebase](https://github.com/nymtech/nyxd)), maintaining a CosmWasm- and IBC-enabled blockchain. + +The blockchain plays a supporting but fundamental role in the mixnet: the `NYM` token used to incentivise node operators is one of two native tokens of the chain, and the chain is where the [Mixnet](../nyx/mixnet-contract.md) and [Vesting](../nyx/vesting-contract.md) smart contracts are deployed. + +## Further Reading +* TODO section of whitepaper +* TODO blogpost +* TODO gaiad link(s) \ No newline at end of file From e8506f6d53816bc2edcc05345a8c6b56d632205d Mon Sep 17 00:00:00 2001 From: mfahampshire Date: Thu, 3 Aug 2023 19:51:03 +0200 Subject: [PATCH 229/361] cont. --- documentation/docs/src/nodes/gateway.md | 13 ++++++++++++- documentation/docs/src/nodes/mixnode.md | 7 +++++-- documentation/docs/src/nodes/validator.md | 2 +- 3 files changed, 18 insertions(+), 4 deletions(-) diff --git a/documentation/docs/src/nodes/gateway.md b/documentation/docs/src/nodes/gateway.md index f4ee68e242..ad2da8e8ca 100644 --- a/documentation/docs/src/nodes/gateway.md +++ b/documentation/docs/src/nodes/gateway.md @@ -1 +1,12 @@ -# Gateway +# Gateways + +> The gateway setup and maintenance guide has migrated to the [Operator Guides book](TO_DO). + +Gateways are key to both the usability of the mixnet, and the operation of the mixnet's tokenomics. They serve two main functions: +* (When the mixnet is no longer in 'free' mode) to act literally as gateways; checking for zkNym credentials (previously referred to as [Coconut Credentials](../bandwidth-credentials.md)) that prove a user has paid to send traffic through the mixnet. A % of the worth of these credentials will be distributed to the operator of the gateway periodically as payment for providing their service. The more credentials user clients 'spend' with them (because of their quality of service) the higher the rewards. The rest of this value will be sent to the Mixmining Pool, a pool of tokens from which `NYM` rewards are distributed to mix node operators. +* Act as a mailbox for connected clients. Clients create a lasting relationship with a gateway on initialisation, binding themselves to always use a particular gateway as their egress point for mixnet traffic, and always receive mixnet traffic from it (see the [mixnet traffic flow page](../architecture/traffic-flow.md) for further details). If a client is offline and the Gateway can't deliver packets addressed to it, they will hold these packets until the client comes back online. + +## Further Reading +* TODO whitepaper section +* TODO loopix paper section +* TODO blogpost \ No newline at end of file diff --git a/documentation/docs/src/nodes/mixnode.md b/documentation/docs/src/nodes/mixnode.md index 159251ad3e..14e42a54b9 100644 --- a/documentation/docs/src/nodes/mixnode.md +++ b/documentation/docs/src/nodes/mixnode.md @@ -1,4 +1,4 @@ -# Mix Node +# Mix Nodes > The mix node setup and maintenance guide has migrated to the [Operator Guides book](TO_DO). @@ -8,10 +8,13 @@ Mix nodes perform one task: receiving packets, decrypting their outer 'layer', a The aim of this mixing is to protect against timing-based deanonymisation attempts by a global adversary able to monitor the entire network with a 'God's Eye View'. +Mix nodes are rewarded according to their quality of service, and their inclusion in the active set (i.e. the nodes that mix traffic for the next epoch) affected by this (as well as their delegation-based reputation - see [further reading](#further-reading) for more on this). + ## (Coming soon) Mixing: a Step-by-Step Breakdown ## Further reading TODO -*
    +* TODO SECTION OF WHITEPAPER +* TODO LOOPIX PAPER SECTION * * diff --git a/documentation/docs/src/nodes/validator.md b/documentation/docs/src/nodes/validator.md index 6b7af1913a..0ef86744a8 100644 --- a/documentation/docs/src/nodes/validator.md +++ b/documentation/docs/src/nodes/validator.md @@ -1,4 +1,4 @@ -# Validator +# Validators > The validator setup and maintenance guide has moved to the [Operator Guides book](TO_DO). From c85f7d2565d9e384a395fff31631c12fdcb4b040 Mon Sep 17 00:00:00 2001 From: mfahampshire Date: Thu, 3 Aug 2023 21:15:58 +0200 Subject: [PATCH 230/361] skeleton of NR page --- documentation/docs/src/nodes/network-requester.md | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/documentation/docs/src/nodes/network-requester.md b/documentation/docs/src/nodes/network-requester.md index 797dfd7795..747efbe62a 100644 --- a/documentation/docs/src/nodes/network-requester.md +++ b/documentation/docs/src/nodes/network-requester.md @@ -1 +1,9 @@ -# Network Requester +# Network Requesters + +> The network requester setup and maintenance guide has moved to the [Operator Guides book](TO_DO). + +Network requesters are the first instance of the catch-all term 'service', or 'service providers'. In essence, think of services as being the part of the mixnet infrastructure that let you _do_ something, such as access emails, messaging service backends, or blockchains via the mixnet. + +Network requesters, in essence, act as a form of proxy (somewhat analagous to a Tor exit node) + + From 2711e6f679e14f5b6df71ddb7512fb2b5462b2e8 Mon Sep 17 00:00:00 2001 From: serinko <97586125+serinko@users.noreply.github.com> Date: Fri, 4 Aug 2023 13:18:12 +0200 Subject: [PATCH 231/361] filled/docs/validators.md --- .../docs/src/images/nym-token-flow.png | Bin 0 -> 174671 bytes documentation/docs/src/nodes/gateway.md | 4 +-- documentation/docs/src/nodes/mixnode.md | 2 +- .../docs/src/nodes/network-requester.md | 2 +- documentation/docs/src/nodes/validator.md | 23 ++++++++++++++---- .../operators/src/nodes/maintenance.md | 3 ++- .../operators/src/preliminary-steps.md | 2 +- 7 files changed, 25 insertions(+), 11 deletions(-) create mode 100644 documentation/docs/src/images/nym-token-flow.png diff --git a/documentation/docs/src/images/nym-token-flow.png b/documentation/docs/src/images/nym-token-flow.png new file mode 100644 index 0000000000000000000000000000000000000000..eb026e23258efb6830022aef1b5af8566c8ecec1 GIT binary patch literal 174671 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

    rAqPF^d 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 U$$MSzw1{bxnWbsjCAZ)H1LKK-6#xJL literal 0 HcmV?d00001 diff --git a/documentation/docs/src/nodes/gateway.md b/documentation/docs/src/nodes/gateway.md index ad2da8e8ca..ebdae64574 100644 --- a/documentation/docs/src/nodes/gateway.md +++ b/documentation/docs/src/nodes/gateway.md @@ -1,6 +1,6 @@ # Gateways -> The gateway setup and maintenance guide has migrated to the [Operator Guides book](TO_DO). +> The gateway setup and maintenance guide has migrated to the [Operator Guides book](https://nymtech.net/operators/nodes/gateway-setup.html). Gateways are key to both the usability of the mixnet, and the operation of the mixnet's tokenomics. They serve two main functions: * (When the mixnet is no longer in 'free' mode) to act literally as gateways; checking for zkNym credentials (previously referred to as [Coconut Credentials](../bandwidth-credentials.md)) that prove a user has paid to send traffic through the mixnet. A % of the worth of these credentials will be distributed to the operator of the gateway periodically as payment for providing their service. The more credentials user clients 'spend' with them (because of their quality of service) the higher the rewards. The rest of this value will be sent to the Mixmining Pool, a pool of tokens from which `NYM` rewards are distributed to mix node operators. @@ -9,4 +9,4 @@ Gateways are key to both the usability of the mixnet, and the operation of the m ## Further Reading * TODO whitepaper section * TODO loopix paper section -* TODO blogpost \ No newline at end of file +* TODO blogpost diff --git a/documentation/docs/src/nodes/mixnode.md b/documentation/docs/src/nodes/mixnode.md index 14e42a54b9..2d1911a990 100644 --- a/documentation/docs/src/nodes/mixnode.md +++ b/documentation/docs/src/nodes/mixnode.md @@ -1,6 +1,6 @@ # Mix Nodes -> The mix node setup and maintenance guide has migrated to the [Operator Guides book](TO_DO). +> The mix node setup and maintenance guide has migrated to the [Operator Guides book](https://nymtech.net/developers/nodes/mix-node-setup.html). Mix nodes are the backbone of the mixnet. These are the nodes that perform 'mix mining', otherwise known simply as 'mixing' or performing the 'poisson mix'. diff --git a/documentation/docs/src/nodes/network-requester.md b/documentation/docs/src/nodes/network-requester.md index 747efbe62a..83778e18a4 100644 --- a/documentation/docs/src/nodes/network-requester.md +++ b/documentation/docs/src/nodes/network-requester.md @@ -1,6 +1,6 @@ # Network Requesters -> The network requester setup and maintenance guide has moved to the [Operator Guides book](TO_DO). +> The network requester setup and maintenance guide has moved to the [Operator Guides book](https://nymtech.net/developers/nodes/network-requester-setup.html). Network requesters are the first instance of the catch-all term 'service', or 'service providers'. In essence, think of services as being the part of the mixnet infrastructure that let you _do_ something, such as access emails, messaging service backends, or blockchains via the mixnet. diff --git a/documentation/docs/src/nodes/validator.md b/documentation/docs/src/nodes/validator.md index 0ef86744a8..ac3d896bca 100644 --- a/documentation/docs/src/nodes/validator.md +++ b/documentation/docs/src/nodes/validator.md @@ -1,12 +1,25 @@ +- [x] TODO section of whitepaper +- [x] TODO blogpost +- [ ] TODO gaias link(s) + # Validators -> The validator setup and maintenance guide has moved to the [Operator Guides book](TO_DO). +> The validator setup and maintenance guide has moved to the [Operator Guides book](https://nymtech.net/operators/nodes/validator-setup.html). -Validators secure the Nyx blockchain via Proof of Stake consensus. The Nyx validators are run via the `nyxd` binary ([codebase](https://github.com/nymtech/nyxd)), maintaining a CosmWasm- and IBC-enabled blockchain. +> We are currently working towards building up a closed set of reputable validators. You can ask us for coins to get in, but please don't be offended if we say no - validators are part of our system's core security and we are starting out with people we already know or who have a solid reputation. + + +Validators secure the Nyx blockchain via Proof of Stake consensus. The Nyx blockchain records the ledger of NYM transactions and executes the smart contracts for distributing NYM rewards. The Nyx validators are run via the `nyxd` binary ([codebase](https://github.com/nymtech/nyxd)), maintaining a CosmWasm- and IBC-enabled blockchain. The blockchain plays a supporting but fundamental role in the mixnet: the `NYM` token used to incentivise node operators is one of two native tokens of the chain, and the chain is where the [Mixnet](../nyx/mixnet-contract.md) and [Vesting](../nyx/vesting-contract.md) smart contracts are deployed. +![Nym Token Flow](../images/nym-token-flow.png) + ## Further Reading -* TODO section of whitepaper -* TODO blogpost -* TODO gaiad link(s) \ No newline at end of file + +* Detailed info on Nyx Validators and token flow can be found in [Nym Reward Sharing for Mixnets document](https://nymtech.net/nym-cryptoecon-paper.pdf) in section 2.3 and 2.4. +* For more detailed calculation, read our blog post [Nym Token Economics update](https://blog.nymtech.net/nym-token-economics-update-fedff0ed5267). +* More info on staking can be found [here](https://blog.nymtech.net/staking-in-nym-introducing-mainnet-mixmining-f9bb1cbc7c36). +* [Here](https://blog.nymtech.net/want-to-stake-in-nym-here-is-how-to-choose-a-mix-node-to-delegate-nym-to-c3b862add165) is more info on how to choose a mix node for delegation. +* Our [quarterly update](https://blog.nymtech.net/quarterly-token-economic-parameter-update-b2862948710f) on token economics from July 2023. + diff --git a/documentation/operators/src/nodes/maintenance.md b/documentation/operators/src/nodes/maintenance.md index 7ffe1d6d58..05723461f2 100644 --- a/documentation/operators/src/nodes/maintenance.md +++ b/documentation/operators/src/nodes/maintenance.md @@ -7,7 +7,8 @@ TODO - copy that dir over to the new vps - re-run init - remember that init doesn't overwrite existing keys - to generate a config with the new listening address etc - change the node info via the wallet - - Since otherwise their keys will point to the old IP address in the smart contract, and their node will (a) not be able to be connected to, and (b) fail uptime checks + - Since otherwise their keys will point to the old IP address in the smart contract, and their node will (a) not be able to be connected to, and (b) fail uptime checks +- [ ] Test the scp -3 command in the moving a node section ---> ## Useful commands diff --git a/documentation/operators/src/preliminary-steps.md b/documentation/operators/src/preliminary-steps.md index d36ca76da4..ec13e7ea59 100644 --- a/documentation/operators/src/preliminary-steps.md +++ b/documentation/operators/src/preliminary-steps.md @@ -1,6 +1,6 @@ # Preliminary Steps -> The Nym `mix node`, `gateway` and `network-requester` binaries were built in the [building nym](../binaries/building-nym.md) section. If you haven't yet built Nym and want to run the code, go there first. +> The Nym `mix node`, `gateway` and `network-requester` binaries were built in the [building nym](./binaries/building-nym.md) section. If you haven't yet built Nym and want to run the code, go there first. ## Preliminary steps From c43344d489948976edded8d422bc62e6a19e8f17 Mon Sep 17 00:00:00 2001 From: serinko <97586125+serinko@users.noreply.github.com> Date: Fri, 4 Aug 2023 13:19:29 +0200 Subject: [PATCH 232/361] remove reduntant nym/docs/ --- docs/src/binaries/building-nym.html | 242 ---------------------- docs/src/binaries/building-nym.md | 1 - docs/src/binaries/init-and-config.html | 242 ---------------------- docs/src/binaries/init-and-config.md | 1 - docs/src/binaries/pre-built-binaries.html | 242 ---------------------- docs/src/binaries/pre-built-binaries.md | 1 - 6 files changed, 729 deletions(-) delete mode 100644 docs/src/binaries/building-nym.html delete mode 100644 docs/src/binaries/building-nym.md delete mode 100644 docs/src/binaries/init-and-config.html delete mode 100644 docs/src/binaries/init-and-config.md delete mode 100644 docs/src/binaries/pre-built-binaries.html delete mode 100644 docs/src/binaries/pre-built-binaries.md diff --git a/docs/src/binaries/building-nym.html b/docs/src/binaries/building-nym.html deleted file mode 100644 index 90e9c937cc..0000000000 --- a/docs/src/binaries/building-nym.html +++ /dev/null @@ -1,242 +0,0 @@ - - - - - - Building from Source - Nym Operators Guides - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

    - - - - - - - - - - - - - - -
    - -
    - - - - - - - - -
    -
    - {comment} -
    - - {comment} -
    - - {comment} -
    - - {comment} -
    - - {comment} -
    - - {comment} -
    - - {comment} -
    - -

    Building from Source

    - -
    - - -
    -
    - - - -
    - - - - - - - - - - - - - - - - - - - - - -
    - - diff --git a/docs/src/binaries/building-nym.md b/docs/src/binaries/building-nym.md deleted file mode 100644 index f195be32f9..0000000000 --- a/docs/src/binaries/building-nym.md +++ /dev/null @@ -1 +0,0 @@ -# Building from Source diff --git a/docs/src/binaries/init-and-config.html b/docs/src/binaries/init-and-config.html deleted file mode 100644 index 2a9390a316..0000000000 --- a/docs/src/binaries/init-and-config.html +++ /dev/null @@ -1,242 +0,0 @@ - - - - - - Binary Initialisation and Configuration - Nym Operators Guides - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    - - - - - - - - - - - - - - -
    - -
    - - - - - - - - -
    -
    - {comment} -
    - - {comment} -
    - - {comment} -
    - - {comment} -
    - - {comment} -
    - - {comment} -
    - - {comment} -
    - -

    Binary Initialisation and Configuration

    - -
    - - -
    -
    - - - -
    - - - - - - - - - - - - - - - - - - - - - -
    - - diff --git a/docs/src/binaries/init-and-config.md b/docs/src/binaries/init-and-config.md deleted file mode 100644 index 9087d5cf00..0000000000 --- a/docs/src/binaries/init-and-config.md +++ /dev/null @@ -1 +0,0 @@ -# Binary Initialisation and Configuration diff --git a/docs/src/binaries/pre-built-binaries.html b/docs/src/binaries/pre-built-binaries.html deleted file mode 100644 index a02f51d2bd..0000000000 --- a/docs/src/binaries/pre-built-binaries.html +++ /dev/null @@ -1,242 +0,0 @@ - - - - - - Pre-built Binaries - Nym Operators Guides - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    - - - - - - - - - - - - - - -
    - -
    - - - - - - - - -
    -
    - {comment} -
    - - {comment} -
    - - {comment} -
    - - {comment} -
    - - {comment} -
    - - {comment} -
    - - {comment} -
    - -

    Pre-built Binaries

    - -
    - - -
    -
    - - - -
    - - - - - - - - - - - - - - - - - - - - - -
    - - diff --git a/docs/src/binaries/pre-built-binaries.md b/docs/src/binaries/pre-built-binaries.md deleted file mode 100644 index 8c52595ce9..0000000000 --- a/docs/src/binaries/pre-built-binaries.md +++ /dev/null @@ -1 +0,0 @@ -# Pre-built Binaries From 1e2de5067a6802d578b15fdeea791b67d0a83dfa Mon Sep 17 00:00:00 2001 From: serinko <97586125+serinko@users.noreply.github.com> Date: Mon, 7 Aug 2023 12:08:18 +0200 Subject: [PATCH 233/361] correct go installation - guide finished --- .../operators/src/nodes/validator-setup.md | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/documentation/operators/src/nodes/validator-setup.md b/documentation/operators/src/nodes/validator-setup.md index 9e233808d7..4726b4c11c 100644 --- a/documentation/operators/src/nodes/validator-setup.md +++ b/documentation/operators/src/nodes/validator-setup.md @@ -41,19 +41,6 @@ rm -rf /usr/local/go && tar -C /usr/local -xzf go1.20.6.linux-amd64.tar.gz export PATH=$PATH:/usr/local/go/bin source $HOME/.profile ``` - Verify `Go` is installed with: From 77f8eadd0eb527531eab041f0b66f3e5bdaae003 Mon Sep 17 00:00:00 2001 From: serinko <97586125+serinko@users.noreply.github.com> Date: Mon, 7 Aug 2023 12:19:34 +0200 Subject: [PATCH 234/361] scp automation tested - doc finished --- documentation/operators/src/nodes/maintenance.md | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/documentation/operators/src/nodes/maintenance.md b/documentation/operators/src/nodes/maintenance.md index 05723461f2..a9a90ccaa6 100644 --- a/documentation/operators/src/nodes/maintenance.md +++ b/documentation/operators/src/nodes/maintenance.md @@ -1,15 +1,5 @@ # Maintenance - ## Useful commands > Adding `--no-banner` startup flag will prevent Nym banner being printed even if run in tty environment. From d13116bda0f2e7a14ae9a241d79c1dd7f2563e10 Mon Sep 17 00:00:00 2001 From: serinko <97586125+serinko@users.noreply.github.com> Date: Mon, 7 Aug 2023 12:36:19 +0200 Subject: [PATCH 235/361] old typos edits --- documentation/docs/src/sdk/rust.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/documentation/docs/src/sdk/rust.md b/documentation/docs/src/sdk/rust.md index 035d8b37b5..70015751e4 100644 --- a/documentation/docs/src/sdk/rust.md +++ b/documentation/docs/src/sdk/rust.md @@ -77,12 +77,12 @@ In order to reply to an incoming message using SURBs, you can construct a `recip ``` ### Importing and using a custom network topology -If you want to send traffic through a sub-set of nodes (for instance, ones you control, or a small test setup) when developing, debugging, or peforming research, you will need to import these nodes as a custom network topology, instead of grabbing it from the [`Mainnet Nym-API`](https://validator.nymtech.net/api/swagger/index.html) (`examples/custom_topology_provider.rs`). +If you want to send traffic through a sub-set of nodes (for instance, ones you control, or a small test setup) when developing, debugging, or performing research, you will need to import these nodes as a custom network topology, instead of grabbing it from the [`Mainnet Nym-API`](https://validator.nymtech.net/api/swagger/index.html) (`examples/custom_topology_provider.rs`). There are two ways to do this: #### Import a custom Nym API endpoint -If you are also running a Validator and Nym API for your network, you can specify that endpoint as such and interact with it as clients ususally do (under the hood): +If you are also running a Validator and Nym API for your network, you can specify that endpoint as such and interact with it as clients usually do (under the hood): ```rust,noplayground {{#include ../../../../sdk/rust/nym-sdk/examples/custom_topology_provider.rs}} From 9c0317ab289ae91fb6b0a1bf3bd966a9da89e465 Mon Sep 17 00:00:00 2001 From: serinko <97586125+serinko@users.noreply.github.com> Date: Mon, 7 Aug 2023 12:38:10 +0200 Subject: [PATCH 236/361] correcting links, typos - doc finished --- documentation/operators/src/nodes/troubleshooting.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/documentation/operators/src/nodes/troubleshooting.md b/documentation/operators/src/nodes/troubleshooting.md index d8fb566b58..50f9406ff3 100644 --- a/documentation/operators/src/nodes/troubleshooting.md +++ b/documentation/operators/src/nodes/troubleshooting.md @@ -183,7 +183,7 @@ Your mix node **must speak both IPv4 and IPv6** in order to cooperate with other #### Incorrect bonding information -Check that you have provided the correct information when bonding your mix node in the web wallet [interface](https://sandbox-wallet.nymtech.net/). When in doubt, un-bond and then re-bond your node! +Check that you have provided the correct information when bonding your mix node in the web wallet interface. When in doubt, un-bond and then re-bond your node! > All delegated stake will be lost when un-bonding! However the mix node must be operational in the first place for the delegation to have any effect. @@ -222,7 +222,7 @@ bob@nym:~$ hostname -I ### Running on a local machine behind NAT with no fixed IP address -Your ISP has to be IPv6 ready if you want to run a mix node on your local machine. Sadly, in 2020, most of them are not and you won't get an IPv6 address by default from your ISP. Usually it is a extra paid service or they simply don't offer it. +Your ISP has to be IPv6 ready if you want to run a mix node on your local machine. Sadly, in 2020, most of them are not and you won't get an IPv6 address by default from your ISP. Usually it is an extra paid service or they simply don't offer it. Before you begin, check if you have IPv6 [here](https://test-ipv6.cz/) or by running command explained in the [section above](./troubleshooting.md#no-ipv6-connectivity). If not, then don't waste your time to run a node which won't ever be able to mix any packet due to this limitation. Call your ISP and ask for IPv6, there is a plenty of it for everyone! From 9652060c57074cb35db45f95208d719ef0a64dec Mon Sep 17 00:00:00 2001 From: serinko <97586125+serinko@users.noreply.github.com> Date: Mon, 7 Aug 2023 12:40:28 +0200 Subject: [PATCH 237/361] correction of typos --- documentation/docs/src/tools/nym-cli.md | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/documentation/docs/src/tools/nym-cli.md b/documentation/docs/src/tools/nym-cli.md index f516190002..b6ed36bfa7 100644 --- a/documentation/docs/src/tools/nym-cli.md +++ b/documentation/docs/src/tools/nym-cli.md @@ -4,14 +4,14 @@ This is a CLI tool for interacting with: * the Nyx blockchain (account management, querying the chain state, etc) -* the smart contracts deployed on Nyx (bonding and unbonding mixnodes, collecting rewards, etc) +* the smart contracts deployed on Nyx (bonding and un-bonding mixnodes, collecting rewards, etc) It provides a convenient wrapper around the `nymd` client, and has similar functionality to the `nyxd` binary for querying the chain or executing smart contract methods. ## Building The `nym-cli` binary can be built by running `cargo build --release` in the `nym/tools/nym-cli` directory. -### Useage +### Usage You can see all available commands with: ``` @@ -225,9 +225,9 @@ Bonding a mix node is a process that takes a few steps due to the need to sign a ./nym-cli --mnemonic mixnet operators gateway bond --amount 100000000 --mix-port 1789 --version "1.1.13" --host "85.163.111.99" --identity-key "B6pWscxYb8sPAdKTci8zPy5AgMzn5Zx8KpWwQNCyUSU7" --location "nym-town" --sphinx-key "o6MmKHzRewpNzVwaV37ZX9G3BfK4AmfYvsQfyoyAFRk" --signature "2TujBZfer8r5QM639Yb8coD9xH6f5eXzjAT5dD7wMom9fH8D1u36d7UpPdVaaZrWsCynmYpobwMWqiMKr5kM6CprD" ``` -### Unbond a node +### Un-bond a node -Unbond a mix node or gateway. +Un-bond a mix node or gateway. ``` ./nym-cli mixnet operators gateway unbound --mnemonic ``` @@ -270,7 +270,7 @@ Delegate to a mix node. ./nym-cli mixnet delegators delegate --amount –mix-id --mnemonic ``` -### Undelegate Stake +### Un-delegate Stake Remove stake from a mix node. ``` @@ -321,10 +321,10 @@ Query for vesting schedule in the [vesting smart contract](../nyx/vesting-contra ## Staking on someone's behalf (for custodians) -There is a limitation the the staking address can only perform the following actions (and are visible via the Nym Wallet: +There is a limitation the staking address can only perform the following actions (and are visible via the Nym Wallet: -- Bond on the gaƒteway's or mix node's behalf. -- Delegate or Undelegate (to a mix node in order to begin receiving rewards) +- Bond on the gateway's or mix node's behalf. +- Delegate or Un-delegate (to a mix node in order to begin receiving rewards) - Claiming the rewards on the account ```admonish note title="" @@ -333,7 +333,7 @@ The staking address has no ability to withdraw any coins from the parent's accou The staking address must maintain the same level of security as the parent mnemonic; while the parent mnemonic's delegations and bonding events will be visible to the parent owner, the staking address will be the only account capable of undoing the bonding and delegating from the mix nodes or gateway. -Query for staking on behlaf of someone else +Query for staking on behalf of someone else ``` ./nym-cli --mnemonic mixnet delegators delegate --mix-id --identity-key --amount ``` From ce81f0c841946e4757a848384ad66825f6fd9e3d Mon Sep 17 00:00:00 2001 From: serinko <97586125+serinko@users.noreply.github.com> Date: Mon, 7 Aug 2023 12:47:29 +0200 Subject: [PATCH 238/361] operators-guides final review of links and typos --- documentation/operators/src/faq.md | 2 +- documentation/operators/src/introduction.md | 2 +- documentation/operators/src/nodes/mix-node-setup.md | 8 +------- .../operators/src/nodes/network-requester-setup.md | 4 ++-- documentation/operators/src/nodes/troubleshooting.md | 2 +- documentation/operators/src/nodes/validator-setup.md | 4 ++-- 6 files changed, 8 insertions(+), 14 deletions(-) diff --git a/documentation/operators/src/faq.md b/documentation/operators/src/faq.md index a42af22825..9bb2c6dec9 100644 --- a/documentation/operators/src/faq.md +++ b/documentation/operators/src/faq.md @@ -14,7 +14,7 @@ For more detailed calculation, read our blog post [Nym Token Economics update](h ### Which VPS providers would you recommend? -Consider in which jurisdiction you reside and where do you want to run a mix node. Do you want to pay by crypto or not and what are the other important particularities for your case? We always recommend operators to try to chose smaller and decentralized VPS providers over the most known ones controlling a majority of the internet. We recieve some good feedback on these: Hertzner, DigitalOcean, Linode, Ghandi and Exoscale. Do your own research and share with the community. +Consider in which jurisdiction you reside and where do you want to run a mix node. Do you want to pay by crypto or not and what are the other important particularities for your case? We always recommend operators to try to chose smaller and decentralized VPS providers over the most known ones controlling a majority of the internet. We receive some good feedback on these: Hertzner, DigitalOcean, Linode, Ghandi and Exoscale. Do your own research and share with the community. diff --git a/documentation/operators/src/introduction.md b/documentation/operators/src/introduction.md index dc160825d6..4050fe141c 100644 --- a/documentation/operators/src/introduction.md +++ b/documentation/operators/src/introduction.md @@ -2,7 +2,7 @@ This is Nym's Operators guide, containing information and setup guides for the various pieces of Nym mixnet infrastructure (mix node, gateway and network requester) and Nyx blockchain validators. -If you are new to Nym and want to learn about the mixnet, explore quickstart options and demos, learn how to integrate with the network, and follow developer tutorials check out the [Developer Portal](https://nymtech.net/developers/). +If you are new to Nym and want to learn about the mixnet, explore kickstart options and demos, learn how to integrate with the network, and follow developer tutorials check out the [Developer Portal](https://nymtech.net/developers/). If you want to dive deeper into Nym's architecture, clients, nodes, and SDK examples visit the [technical docs](https://nymtech.net/docs/). diff --git a/documentation/operators/src/nodes/mix-node-setup.md b/documentation/operators/src/nodes/mix-node-setup.md index 2dccc52258..1c70383a61 100644 --- a/documentation/operators/src/nodes/mix-node-setup.md +++ b/documentation/operators/src/nodes/mix-node-setup.md @@ -173,12 +173,6 @@ If everything worked, you'll see your node running on the either the [Sandbox te Note that your node's public identity key is displayed during startup, you can use it to identify your node in the list. - - Have a look at the saved configuration files in `$HOME/.nym/mixnodes/` to see more configuration options. ## Node Description (optional) @@ -298,7 +292,7 @@ Once you've started your mix node and it connects to the validator, your node wi - [Mainnet](https://explorer.nymtech.net/overview) - [Sandbox testnet](https://sandbox-explorer.nymtech.net/) -Enter your **identity key** to find your node. There are numerous statistics about your node on that page that are useful for checking your uptime history, packets mixed, and any delegations your node may have. +Enter your **identity key** to find your node. There are numerous statistics about your node on that page that are useful for checking your up-time history, packets mixed, and any delegations your node may have. There are also 2 community explorers which have been created by [Nodes Guru](https://nodes.guru): diff --git a/documentation/operators/src/nodes/network-requester-setup.md b/documentation/operators/src/nodes/network-requester-setup.md index 049b139de6..15cacd3266 100644 --- a/documentation/operators/src/nodes/network-requester-setup.md +++ b/documentation/operators/src/nodes/network-requester-setup.md @@ -22,7 +22,7 @@ Any request to a URL which is not on this list will be blocked. On startup, if this file is not present, the requester will grab the default whitelist from [Nym's default list](https://nymtech.net/.wellknown/network-requester/standard-allowed-list.txt) automatically. -This default whitelist is useful for knowing that the majority of network requesters are able to support certain apps 'out of the box'. +This default whitelist is useful for knowing that the majority of Network requesters are able to support certain apps 'out of the box'. **Operators of a network requester are of course free to edit this file and add the URLs of services they wish to support to it!** You can find instructions below on adding your own URLs or IPs to this list. @@ -77,7 +77,7 @@ matrix.org ``` ## Network Requester Directory -You can find a list of Network Requesters running the default whitelist in the [explorer](https://explorer.nymtech.net/network-components/service-providers). This list comprises of the NRs running as infrastructure for NymConnect. +You can find a list of Network requesters running the default whitelist in the [explorer](https://explorer.nymtech.net/network-components/service-providers). This list comprises of the NRs running as infrastructure for NymConnect. > We are currently working on a smart-contract based solution more in line with how Mix nodes and Gateways announce themselves to the network. diff --git a/documentation/operators/src/nodes/troubleshooting.md b/documentation/operators/src/nodes/troubleshooting.md index 50f9406ff3..20b98970df 100644 --- a/documentation/operators/src/nodes/troubleshooting.md +++ b/documentation/operators/src/nodes/troubleshooting.md @@ -236,7 +236,7 @@ Here is a sample of the `init` command example to create the mix node config. ./nym-mixnode init --id --host 0.0.0.0 --announce-host 85.160.12.13 ``` -- `--host 0.0.0.0` should work every time even if your local machine IPv4 address changes. For example on Monday your router gives your machine an address `192.168.0.13` and on Wednesday, the DHCP lease will end and you will be asigned `192.168.0.14`. Using `0.0.0.0` should avoid this without having to set any static ip in your router`s configuration. +- `--host 0.0.0.0` should work every time even if your local machine IPv4 address changes. For example on Monday your router gives your machine an address `192.168.0.13` and on Wednesday, the DHCP lease will end and you will be assigned `192.168.0.14`. Using `0.0.0.0` should avoid this without having to set any static IP in your router`s configuration. - you can get your current IPv4 address by either using `curl ipinfo.io` if you're on MacOS or Linux or visiting [whatsmyip site](https://www.whatsmyip.org/). Simply copy it and use it as `--anounce-host` address. diff --git a/documentation/operators/src/nodes/validator-setup.md b/documentation/operators/src/nodes/validator-setup.md index 4726b4c11c..ca35c0b2b9 100644 --- a/documentation/operators/src/nodes/validator-setup.md +++ b/documentation/operators/src/nodes/validator-setup.md @@ -455,7 +455,7 @@ If you want to set up a reverse proxying on the validator server to improve secu Linux machines limit how many open files a user is allowed to have. This is called a `ulimit`. We need to set it to a higher value than the default 1024. Follow the instructions in the [maintenance page](./maintenance.md#Setting-the-ulimit) to change the `ulimit` value for validators. ## Using your validator -### Unjailing your validator +### Un-jailing your validator If your validator gets jailed, you can fix it with the following command: ``` @@ -499,7 +499,7 @@ You can check your current balances with: nymd query bank balances ${ADDRESS} ``` -For example, on the Sanbox testnet this would return: +For example, on the Sandbox testnet this would return: ```yaml balances: From 567d3c81733294a78e1361a10d8fd04afed12d8d Mon Sep 17 00:00:00 2001 From: serinko <97586125+serinko@users.noreply.github.com> Date: Mon, 7 Aug 2023 13:13:11 +0200 Subject: [PATCH 239/361] socks5: new commands from #3699 --- documentation/docs/src/clients/socks5-client.md | 12 +++++++++++- documentation/operators/src/nodes/maintenance.md | 2 +- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/documentation/docs/src/clients/socks5-client.md b/documentation/docs/src/clients/socks5-client.md index e7163347cb..e8bce0e948 100644 --- a/documentation/docs/src/clients/socks5-client.md +++ b/documentation/docs/src/clients/socks5-client.md @@ -82,7 +82,7 @@ You can check that your binaries are properly compiled with: You can check the necessary parameters for the available commands by running: ``` -./nym-client --help +./nym-client --help ``` ### Initialising a new client instance @@ -169,3 +169,13 @@ Here is an example of setting the proxy connecting in Blockstream Green: Most wallets and other applications will work basically the same way: find the network proxy settings, enter the proxy url (host: **localhost**, port: **1080**). In some other applications, this might be written as **localhost:1080** if there's only one proxy entry field. + +## Useful Commands + +**no-banner** + +Adding `--no-banner` startup flag will prevent Nym banner being printed even if run in tty environment. + +**build-info** + +A `build-info` command prints the build information like commit hash, rust version, binary version just like what command `--version` does. However, you can also specify an `--output=json` flag that will format the whole output as a json, making it an order of magnitude easier to parse. diff --git a/documentation/operators/src/nodes/maintenance.md b/documentation/operators/src/nodes/maintenance.md index a9a90ccaa6..911a6825af 100644 --- a/documentation/operators/src/nodes/maintenance.md +++ b/documentation/operators/src/nodes/maintenance.md @@ -661,7 +661,7 @@ All ``-specific port configuration can be found in `$HOME/.nym// Date: Mon, 7 Aug 2023 15:52:48 +0200 Subject: [PATCH 240/361] added links --- documentation/docs/src/nodes/gateway.md | 9 ++++----- documentation/docs/src/nodes/mixnode.md | 15 ++++++--------- documentation/docs/src/nodes/validator.md | 6 +----- 3 files changed, 11 insertions(+), 19 deletions(-) diff --git a/documentation/docs/src/nodes/gateway.md b/documentation/docs/src/nodes/gateway.md index ebdae64574..814c550d4c 100644 --- a/documentation/docs/src/nodes/gateway.md +++ b/documentation/docs/src/nodes/gateway.md @@ -3,10 +3,9 @@ > The gateway setup and maintenance guide has migrated to the [Operator Guides book](https://nymtech.net/operators/nodes/gateway-setup.html). Gateways are key to both the usability of the mixnet, and the operation of the mixnet's tokenomics. They serve two main functions: -* (When the mixnet is no longer in 'free' mode) to act literally as gateways; checking for zkNym credentials (previously referred to as [Coconut Credentials](../bandwidth-credentials.md)) that prove a user has paid to send traffic through the mixnet. A % of the worth of these credentials will be distributed to the operator of the gateway periodically as payment for providing their service. The more credentials user clients 'spend' with them (because of their quality of service) the higher the rewards. The rest of this value will be sent to the Mixmining Pool, a pool of tokens from which `NYM` rewards are distributed to mix node operators. -* Act as a mailbox for connected clients. Clients create a lasting relationship with a gateway on initialisation, binding themselves to always use a particular gateway as their egress point for mixnet traffic, and always receive mixnet traffic from it (see the [mixnet traffic flow page](../architecture/traffic-flow.md) for further details). If a client is offline and the Gateway can't deliver packets addressed to it, they will hold these packets until the client comes back online. +* In the future (when the mixnet is no longer running in 'free to use' mode), to check for zkNym credentials (previously referred to as [Coconut Credentials](../bandwidth-credentials.md)) with which users can anonymously prove they have paid to send traffic through the mixnet. A % of the worth of these credentials will be distributed to the operator of the gateway periodically as payment for providing their service. The more credentials user clients 'spend' with them the higher the rewards for the gateway operator and their delegators. The rest of this value will be sent to the Mixmining Pool, a pool of tokens from which `NYM` rewards are distributed to mix node operators. +* Act as a mailbox for connected clients. Clients create a lasting relationship with a gateway on initialisation, binding themselves to always use a particular gateway as their egress point for mixnet traffic, and always receive mixnet traffic from it (see the [mixnet traffic flow page](../architecture/traffic-flow.md) and the [addressing scheme](../clients/addressing-system.md) for further details). If a client is offline and the Gateway can't deliver packets addressed to it, they will hold these packets until the client comes back online. ## Further Reading -* TODO whitepaper section -* TODO loopix paper section -* TODO blogpost +* [Nym Whitepaper](https://nymtech.net/nym-whitepaper.pdf) section 4.2 +* [Nym Blog: Gateways to Privacy](https://blog.nymtech.net/gateways-to-privacy-51196005bf5) diff --git a/documentation/docs/src/nodes/mixnode.md b/documentation/docs/src/nodes/mixnode.md index 2d1911a990..e849b141b1 100644 --- a/documentation/docs/src/nodes/mixnode.md +++ b/documentation/docs/src/nodes/mixnode.md @@ -2,19 +2,16 @@ > The mix node setup and maintenance guide has migrated to the [Operator Guides book](https://nymtech.net/developers/nodes/mix-node-setup.html). -Mix nodes are the backbone of the mixnet. These are the nodes that perform 'mix mining', otherwise known simply as 'mixing' or performing the 'poisson mix'. +Mix nodes are the backbone of the mixnet. These are the nodes that perform 'mix mining', otherwise known simply as 'mixing'. -Mix nodes perform one task: receiving packets, decrypting their outer 'layer', and holding them for a variable amount of time before passing them to their next destination - either another mix node, or a gateway. In doing so, they 'mix' packets by sending them to their next destination in a different order than they were recieved. - -The aim of this mixing is to protect against timing-based deanonymisation attempts by a global adversary able to monitor the entire network with a 'God's Eye View'. +Mix nodes, after receiving a packet, decrypt its outer 'layer', and hold them for a variable amount of time before passing them to their next destination - either another mix node, or a gateway. In doing so, they 'mix' packets by sending them to their next destination in a different order than they were received. Mix nodes are rewarded according to their quality of service, and their inclusion in the active set (i.e. the nodes that mix traffic for the next epoch) affected by this (as well as their delegation-based reputation - see [further reading](#further-reading) for more on this). ## (Coming soon) Mixing: a Step-by-Step Breakdown ## Further reading -TODO -* TODO SECTION OF WHITEPAPER -* TODO LOOPIX PAPER SECTION -* -* +* [Nym Whitepaper](https://nymtech.net/nym-whitepaper.pdf) section 4 +* [Nym Blog: Mix node deepdive](https://blog.nymtech.net/nym-mixnodes-deep-dive-d2b91917f097) +* [Mixnet Traffic Flow overview](../architecture/traffic-flow.md) +* [Reward Sharing for Mixnets](https://nymtech.net/nym-cryptoecon-paper.pdf) \ No newline at end of file diff --git a/documentation/docs/src/nodes/validator.md b/documentation/docs/src/nodes/validator.md index ac3d896bca..ceb0abdf8a 100644 --- a/documentation/docs/src/nodes/validator.md +++ b/documentation/docs/src/nodes/validator.md @@ -1,6 +1,4 @@ - [x] TODO section of whitepaper -- [x] TODO blogpost -- [ ] TODO gaias link(s) # Validators @@ -8,7 +6,6 @@ > We are currently working towards building up a closed set of reputable validators. You can ask us for coins to get in, but please don't be offended if we say no - validators are part of our system's core security and we are starting out with people we already know or who have a solid reputation. - Validators secure the Nyx blockchain via Proof of Stake consensus. The Nyx blockchain records the ledger of NYM transactions and executes the smart contracts for distributing NYM rewards. The Nyx validators are run via the `nyxd` binary ([codebase](https://github.com/nymtech/nyxd)), maintaining a CosmWasm- and IBC-enabled blockchain. The blockchain plays a supporting but fundamental role in the mixnet: the `NYM` token used to incentivise node operators is one of two native tokens of the chain, and the chain is where the [Mixnet](../nyx/mixnet-contract.md) and [Vesting](../nyx/vesting-contract.md) smart contracts are deployed. @@ -16,10 +13,9 @@ The blockchain plays a supporting but fundamental role in the mixnet: the `NYM` ![Nym Token Flow](../images/nym-token-flow.png) ## Further Reading - * Detailed info on Nyx Validators and token flow can be found in [Nym Reward Sharing for Mixnets document](https://nymtech.net/nym-cryptoecon-paper.pdf) in section 2.3 and 2.4. * For more detailed calculation, read our blog post [Nym Token Economics update](https://blog.nymtech.net/nym-token-economics-update-fedff0ed5267). * More info on staking can be found [here](https://blog.nymtech.net/staking-in-nym-introducing-mainnet-mixmining-f9bb1cbc7c36). * [Here](https://blog.nymtech.net/want-to-stake-in-nym-here-is-how-to-choose-a-mix-node-to-delegate-nym-to-c3b862add165) is more info on how to choose a mix node for delegation. * Our [quarterly update](https://blog.nymtech.net/quarterly-token-economic-parameter-update-b2862948710f) on token economics from July 2023. - +* [Nym Whitepaper](https://nymtech.net/nym-whitepaper.pdf) section 3.1 \ No newline at end of file From 8fe549daa595dff1b4c2fc68e0aae562745f0cf9 Mon Sep 17 00:00:00 2001 From: mfahampshire Date: Mon, 7 Aug 2023 16:06:39 +0200 Subject: [PATCH 241/361] more links, fleshed out intro --- documentation/docs/src/introduction.md | 23 +++++++++++++-------- documentation/docs/src/wallet/cli-wallet.md | 2 +- 2 files changed, 15 insertions(+), 10 deletions(-) diff --git a/documentation/docs/src/introduction.md b/documentation/docs/src/introduction.md index 9e245497f4..9964ec5b75 100644 --- a/documentation/docs/src/introduction.md +++ b/documentation/docs/src/introduction.md @@ -2,24 +2,29 @@ This is Nym's technical documentation, containing information and setup guides about the various pieces of Nym software such as different mixnet infrastructure nodes, application clients, and existing applications like the desktop wallet and mixnet explorer. -If you are new to Nym and want to learn about the mixnet, how to integrate with the network, developer tutorials and quickstart guides, check out the [Developer Portal](https://nymtech.net/developers/). +If you are looking for information and setup guides for the various pieces of Nym mixnet infrastructure (mix nodes, gateways and network requesters) and Nyx blockchain validators see the **new [Operators Guides](https://nymtech.net/operators)** book. + +If you are new to Nym and want to learn about the mixnet, explore kickstart options and demos, learn how to integrate with the network, and follow developer tutorials check out the [Developer Portal](https://nymtech.net/developers/). ## Popular pages **Network Architecture:** * [Network Overview](./architecture/network-overview.md) * [Mixnet Traffic Flow](./architecture/traffic-flow.md) -**Node setup and usage guides:** -* [Mix nodes](./nodes/mix-node-setup.md) -* [Gateways](./nodes/gateway-setup.md) -* [Network requesters](./nodes/network-requester-setup.md) -* [Validators](./nodes/validator-setup.md) +**SDK examples:** +* [Typescript SDK](./sdk/typescript.md) +* [Rust SDK](./sdk/rust.md) + +**Nyx** +* [Interacting with the Nyx chain](./nyx/interacting-with-chain.md) +* [Ledger Live setup](./nyx/ledger-live.md) +* [RPC Node](./nyx/rpc-node.md) **Client setup and usage guides:** * [Websocket client](./clients/websocket-client.md) * [Socks5 client](./clients/socks5-client.md) * [Webassembly client](./clients/webassembly-client.md) -**SDK guides:** -* [Typescript SDK](./sdk/typescript.md) -* [Rust SDK](./sdk/rust.md) +**Wallets** +* [Desktop](./wallet/desktop-wallet.md) +* [CLI](./wallet/cli-wallet.md) \ No newline at end of file diff --git a/documentation/docs/src/wallet/cli-wallet.md b/documentation/docs/src/wallet/cli-wallet.md index e4d2468068..3f713b1937 100644 --- a/documentation/docs/src/wallet/cli-wallet.md +++ b/documentation/docs/src/wallet/cli-wallet.md @@ -5,4 +5,4 @@ If you have already read our validator setup and maintenance [documentation](../ ### Using `nyxd` binary as a CLI wallet You can use the `nyxd` as a minimal CLI wallet if you want to set up an account (or multiple accounts). Just compile the binary as per the documentation, **stopping after** the [building your validator](../nodes/validator-setup.md#building-your-validator) step is complete. You can then run `nyxd keys --help` to see how you can set up and store different keypairs with which to interact with the Nyx blockchain. - +For more on interacting with the chain, see the [Interacting with Nyx Chain and Smart Contracts](../nyx/interacting-with-chain.md) page. From d3d5346cfe6bd346e3adf93ee294958e34d3909d Mon Sep 17 00:00:00 2001 From: mfahampshire Date: Mon, 7 Aug 2023 16:38:02 +0200 Subject: [PATCH 242/361] typo fix --- documentation/operators/src/nodes/network-requester-setup.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/documentation/operators/src/nodes/network-requester-setup.md b/documentation/operators/src/nodes/network-requester-setup.md index 15cacd3266..e840bb357a 100644 --- a/documentation/operators/src/nodes/network-requester-setup.md +++ b/documentation/operators/src/nodes/network-requester-setup.md @@ -83,7 +83,7 @@ You can find a list of Network requesters running the default whitelist in the [ ## Viewing command help -To begin, move to `/taget/release` directory from which you run the node commands: +To begin, move to `/target/release` directory from which you run the node commands: ``` cd target/release From 7a606e11f4fe45f4ac3fe3b01971f967ca17b347 Mon Sep 17 00:00:00 2001 From: mfahampshire Date: Mon, 7 Aug 2023 16:38:13 +0200 Subject: [PATCH 243/361] NR page --- .../docs/src/nodes/network-requester.md | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/documentation/docs/src/nodes/network-requester.md b/documentation/docs/src/nodes/network-requester.md index 83778e18a4..ac083a3143 100644 --- a/documentation/docs/src/nodes/network-requester.md +++ b/documentation/docs/src/nodes/network-requester.md @@ -1,9 +1,22 @@ -# Network Requesters +# Network Requester > The network requester setup and maintenance guide has moved to the [Operator Guides book](https://nymtech.net/developers/nodes/network-requester-setup.html). Network requesters are the first instance of the catch-all term 'service', or 'service providers'. In essence, think of services as being the part of the mixnet infrastructure that let you _do_ something, such as access emails, messaging service backends, or blockchains via the mixnet. -Network requesters, in essence, act as a form of proxy (somewhat analagous to a Tor exit node) +## Domain filtering +Network requesters, in essence, act as a form of proxy (somewhat analagous to a Tor exit node). If you have access to a server, you can run the network requester, which allows Nym users to send outbound requests from their local machine through the mixnet to a server, which then makes the request on their behalf, shielding them (and their metadata) from clearnet, untrusted and unknown infrastructure, such as email or message client servers. +By default the network requester is **not** an open proxy (although it can be used as one). It uses a whitelist for outbound requests. +Any request to a URL which is not on this local list (modified by the node operator) or [Nym's default list](https://nymtech.net/.wellknown/network-requester/standard-allowed-list.txt) will be blocked. + +This default whitelist is useful for knowing that the majority of Network requesters are able to support certain apps 'out of the box', and the local whitelist allows operators to include their own whitelisted domains. + +> Substantial changes are on the horizon concerning how Network Requesters manage incoming requests - if you are an operator and have experience running software such as Tor exit nodes or p2p nodes get in touch via our [Matrix server](https://matrix.to/#/#dev:nymtech.chat). + +## (Coming soon) Consuming credentials for anonymous service payment + +## Further reading +* [Nym Blog: Network Requester deepdive](https://blog.nymtech.net/tech-deepdive-network-requesters-e5359a6cc31c) +* [Nym Blog: Choose Your Character](https://blog.nymtech.net/choose-your-character-an-overview-of-nym-network-actors-19e6a9808540) \ No newline at end of file From 32bfceacb5fef9dc2098b536625f1fbfa26c82a1 Mon Sep 17 00:00:00 2001 From: mfahampshire Date: Tue, 8 Aug 2023 13:59:53 +0200 Subject: [PATCH 244/361] added custom 404 page for all books --- documentation/dev-portal/book.toml | 2 +- documentation/dev-portal/src/not-found.md | 11 +++++++++++ documentation/docs/src/not-found.md | 4 ++-- documentation/operators/book.toml | 4 ++-- documentation/operators/src/not-found.md | 11 +++++++++++ 5 files changed, 27 insertions(+), 5 deletions(-) create mode 100644 documentation/dev-portal/src/not-found.md create mode 100644 documentation/operators/src/not-found.md diff --git a/documentation/dev-portal/book.toml b/documentation/dev-portal/book.toml index cf434a31f5..d8ede22a4a 100644 --- a/documentation/dev-portal/book.toml +++ b/documentation/dev-portal/book.toml @@ -84,7 +84,7 @@ git-repository-icon = "fa-github" # edit-url-template = "https://github.com/rust-lang/mdBook/edit/master/guide/{path}" site-url = "/developers/" # cname = "nymtech.net" -# input-404 = "not-found.md" # todo make custom 404 page +input-404 = "not-found.md" [output.html.fold] enable = true # whether or not to enable section folding diff --git a/documentation/dev-portal/src/not-found.md b/documentation/dev-portal/src/not-found.md new file mode 100644 index 0000000000..704da5ec19 --- /dev/null +++ b/documentation/dev-portal/src/not-found.md @@ -0,0 +1,11 @@ +# Page Not Found + +You seem to have followed a link to a page that no longer exists. Our documentation is changing and growing over time, so this page has most likely been moved somewhere else. + +For node setup guides, see the [Operator Guides book](https://nymtech.net/operators). + +For information on network architecture traffic flow, clients, the SDKs, or interacting with the Nyx blockchain see the [Technical Documentation](https://nymtech.net/docs). + +If you are looking for information on developer tutorials, app guides, and demo and community apps, check the sitemap on the left hand side of the screen. + +If you still can't find what you're looking for get in touch with us via [Matrix](https://matrix.to/#/#nym-community:nymtech.chat). \ No newline at end of file diff --git a/documentation/docs/src/not-found.md b/documentation/docs/src/not-found.md index 3d203a9bb8..af87d6dd59 100644 --- a/documentation/docs/src/not-found.md +++ b/documentation/docs/src/not-found.md @@ -2,10 +2,10 @@ You seem to have followed a link to a page that no longer exists. Our documentation is changing and growing over time, so this page has most likely been moved somewhere else. -For node setup guides, see the [Operators Guides book](https://nymtech.net/operators). +For node setup guides, see the [Operator Guides book](https://nymtech.net/operators). For developer tutorials, app guides, and demo and community apps, see the [Developer Portal](https://nymtech.net/developers). If you are looking for information on network architecture traffic flow, clients, the SDKs, or interacting with the Nyx blockchain, check the sitemap on the left hand side of the screen. -If you still can't find what you're looking for get in touch with us via [Matrix](TODO). \ No newline at end of file +If you still can't find what you're looking for get in touch with us via [Matrix](https://matrix.to/#/#nym-community:nymtech.chat). \ No newline at end of file diff --git a/documentation/operators/book.toml b/documentation/operators/book.toml index 28efe62c57..2ce49e37bb 100644 --- a/documentation/operators/book.toml +++ b/documentation/operators/book.toml @@ -92,9 +92,9 @@ additional-js = ["theme/pagetoc.js"] git-repository-url = "https://github.com/nymtech/nym" git-repository-icon = "fa-github" # edit-url-template = "https://github.com/rust-lang/mdBook/edit/master/guide/{path}" -# site-url = "/docs/" +site-url = "/operators/" # cname = "nymtech.net" -# input-404 = "not-found.md" # todo make custom 404 page +input-404 = "not-found.md" [output.html.fold] enable = true # whether or not to enable section folding diff --git a/documentation/operators/src/not-found.md b/documentation/operators/src/not-found.md new file mode 100644 index 0000000000..aafb70cba5 --- /dev/null +++ b/documentation/operators/src/not-found.md @@ -0,0 +1,11 @@ +# Page Not Found + +You seem to have followed a link to a page that no longer exists. Our documentation is changing and growing over time, so this page has most likely been moved somewhere else. + +For node setup guides, see the [Operator Guides book](https://nymtech.net/operators). + +For developer tutorials, app guides, and demo and community apps, see the [Developer Portal](https://nymtech.net/developers). + +If you are looking for information on setting up and maintaining infrastructure nodes, check the sitemap on the left hand side of the screen. + +If you still can't find what you're looking for get in touch with us via [Matrix](https://matrix.to/#/#nym-community:nymtech.chat). \ No newline at end of file From 3da3f66ea38c6f41cd49210c88cef342acb24071 Mon Sep 17 00:00:00 2001 From: mfahampshire Date: Tue, 8 Aug 2023 14:10:44 +0200 Subject: [PATCH 245/361] * tweaked mixnode doc * removed token diagram from nyx validator doc --- documentation/docs/src/nodes/mixnode.md | 2 +- documentation/docs/src/nodes/validator.md | 8 +------- 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/documentation/docs/src/nodes/mixnode.md b/documentation/docs/src/nodes/mixnode.md index e849b141b1..44dc924d86 100644 --- a/documentation/docs/src/nodes/mixnode.md +++ b/documentation/docs/src/nodes/mixnode.md @@ -4,7 +4,7 @@ Mix nodes are the backbone of the mixnet. These are the nodes that perform 'mix mining', otherwise known simply as 'mixing'. -Mix nodes, after receiving a packet, decrypt its outer 'layer', and hold them for a variable amount of time before passing them to their next destination - either another mix node, or a gateway. In doing so, they 'mix' packets by sending them to their next destination in a different order than they were received. +Mix nodes, after receiving a packet, decrypt its outer 'layer' and hold it for a variable amount of time before passing it to its next destination - either another mix node, or a gateway. In doing so, they 'mix' packets by sending them to their next destination in a different order than they were received. Mix nodes are rewarded according to their quality of service, and their inclusion in the active set (i.e. the nodes that mix traffic for the next epoch) affected by this (as well as their delegation-based reputation - see [further reading](#further-reading) for more on this). diff --git a/documentation/docs/src/nodes/validator.md b/documentation/docs/src/nodes/validator.md index ceb0abdf8a..eba1666351 100644 --- a/documentation/docs/src/nodes/validator.md +++ b/documentation/docs/src/nodes/validator.md @@ -1,17 +1,11 @@ -- [x] TODO section of whitepaper - # Validators > The validator setup and maintenance guide has moved to the [Operator Guides book](https://nymtech.net/operators/nodes/validator-setup.html). -> We are currently working towards building up a closed set of reputable validators. You can ask us for coins to get in, but please don't be offended if we say no - validators are part of our system's core security and we are starting out with people we already know or who have a solid reputation. - -Validators secure the Nyx blockchain via Proof of Stake consensus. The Nyx blockchain records the ledger of NYM transactions and executes the smart contracts for distributing NYM rewards. The Nyx validators are run via the `nyxd` binary ([codebase](https://github.com/nymtech/nyxd)), maintaining a CosmWasm- and IBC-enabled blockchain. +Validators secure the Nyx blockchain via Proof of Stake consensus. The Nyx blockchain records the ledger of `NYM` transactions and executes the smart contracts for distributing `NYM` rewards. The Nyx validators are run via the `nyxd` binary ([codebase](https://github.com/nymtech/nyxd)), maintaining a CosmWasm- and IBC-enabled blockchain. The blockchain plays a supporting but fundamental role in the mixnet: the `NYM` token used to incentivise node operators is one of two native tokens of the chain, and the chain is where the [Mixnet](../nyx/mixnet-contract.md) and [Vesting](../nyx/vesting-contract.md) smart contracts are deployed. -![Nym Token Flow](../images/nym-token-flow.png) - ## Further Reading * Detailed info on Nyx Validators and token flow can be found in [Nym Reward Sharing for Mixnets document](https://nymtech.net/nym-cryptoecon-paper.pdf) in section 2.3 and 2.4. * For more detailed calculation, read our blog post [Nym Token Economics update](https://blog.nymtech.net/nym-token-economics-update-fedff0ed5267). From 534c8f49d1f006cf5d400f7d0efc91fc9ebcdbbe Mon Sep 17 00:00:00 2001 From: mfahampshire Date: Tue, 8 Aug 2023 14:16:48 +0200 Subject: [PATCH 246/361] added notes to ws and socks client subsections re: rust sdk --- documentation/docs/src/clients/overview.md | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/documentation/docs/src/clients/overview.md b/documentation/docs/src/clients/overview.md index 4bfc125b5c..c8d2db1503 100644 --- a/documentation/docs/src/clients/overview.md +++ b/documentation/docs/src/clients/overview.md @@ -23,19 +23,23 @@ At present, there are three Nym clients: You need to choose which one you want incorporate into your app. Which one you use will depend largely on your preferred programming style and the purpose of your app. ### The websocket client -Your first option is the native websocket client (`nym-client`). This is a compiled program that can run on Linux, Mac OS X, and Windows machines. It runs as a persistent process on a desktop or server machine. You can connect to it with **any language that supports websockets**. +Your first option is the native websocket client (`nym-client`). This is a compiled program that can run on Linux, Mac OS X, and Windows machines. It can be run as a persistent process on a desktop or server machine. You can connect to it with **any language that supports websockets**. + +_Rust developers can import websocket client functionality into their code via the [Rust SDK](../sdk/rust.md)_. ### The webassembly client If you're working in JavaScript or Typescript in the browser, or building an [edge computing](https://en.wikipedia.org/wiki/Edge_computing) app, you'll likely want to choose the webassembly client. It's packaged and [available on the npm registry](https://www.npmjs.com/package/@nymproject/nym-client-wasm), so you can `npm install` it into your JavaScript or TypeScript application. -The webassembly client is most easily used via the [sdk](../sdk/typescript.md). +_The webassembly client is most easily used via the [Typescript SDK](../sdk/typescript.md)_. ### The SOCKS5 client -This client (`nym-socks5-client`) is useful for allowing existing applications to use the Nym mixnet without any code changes. All that's necessary is that they can use one of the SOCKS5, SOCKS4a, or SOCKS4 proxy protocols (which many applications can - crypto wallets, browsers, chat applications etc). +The `nym-socks5-client` is useful for allowing existing applications to use the Nym mixnet without any code changes. All that's necessary is that they can use one of the SOCKS5, SOCKS4a, or SOCKS4 proxy protocols (which many applications can - crypto wallets, browsers, chat applications etc). -It's less flexible as a way of writing custom applications than the other clients, but able to be used to proxy application traffic through the mixnet without having to make any code changes. +When used as a standalone client, it's less flexible as a way of writing custom applications than the other clients, but able to be used to proxy application traffic through the mixnet without having to make any code changes. + +_Rust developers can import socks client functionality into their code via the [Rust SDK](../sdk/rust.md)_. ## Commonalities between clients All Nym client packages present basically the same capabilities to the privacy application developer. They need to run as a persistent process in order to stay connected and ready to receive any incoming messages from their gateway nodes. They register and authenticate to gateways, and encrypt Sphinx packets. From a93de8b4c59fadb702a3cf5ddcd4023cf682ff77 Mon Sep 17 00:00:00 2001 From: mfahampshire Date: Tue, 8 Aug 2023 14:19:29 +0200 Subject: [PATCH 247/361] updated vars in all books for next release version --- documentation/dev-portal/book.toml | 2 +- documentation/docs/book.toml | 2 +- documentation/operators/book.toml | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/documentation/dev-portal/book.toml b/documentation/dev-portal/book.toml index d8ede22a4a..8f44c978f4 100644 --- a/documentation/dev-portal/book.toml +++ b/documentation/dev-portal/book.toml @@ -49,8 +49,8 @@ assets_version = "2.0.0" # do not edit: managed by `mdbook-admonish install` [preprocessor.variables.variables] minimum_rust_version = "1.66" # vars for links: TODO think on how to streamline updating -platform_release_version = "v1.1.25" wallet_release_version = "v1.2.7" +platform_release_version = "v1.1.27" [preprocessor.last-changed] command = "mdbook-last-changed" diff --git a/documentation/docs/book.toml b/documentation/docs/book.toml index 7dfd751b15..cda7e3db07 100644 --- a/documentation/docs/book.toml +++ b/documentation/docs/book.toml @@ -49,7 +49,7 @@ assets_version = "2.0.0" # do not edit: managed by `mdbook-admonish install` [preprocessor.variables.variables] minimum_rust_version = "1.66" # vars for links: TODO think on how to streamline updating -platform_release_version = "v1.1.26" +platform_release_version = "v1.1.27" wallet_release_version = "v1.2.7" [preprocessor.last-changed] diff --git a/documentation/operators/book.toml b/documentation/operators/book.toml index 2ce49e37bb..c39af18a65 100644 --- a/documentation/operators/book.toml +++ b/documentation/operators/book.toml @@ -49,8 +49,8 @@ assets_version = "2.0.0" # do not edit: managed by `mdbook-admonish install` [preprocessor.variables.variables] minimum_rust_version = "1.66" # vars for links: TODO think on how to streamline updating -platform_release_version = "v1.1.22" -wallet_release_version = "v1.2.5" +platform_release_version = "v1.1.27" +wallet_release_version = "v1.2.7" [preprocessor.last-changed] From ee14dfc75e7f79b20bd79572a3b8ba98ec29cd5f Mon Sep 17 00:00:00 2001 From: mfahampshire Date: Wed, 9 Aug 2023 11:29:34 +0200 Subject: [PATCH 248/361] added coc and license --- documentation/operators/src/SUMMARY.md | 5 +++++ documentation/operators/src/coc.md | 17 +++++++++++++++++ documentation/operators/src/licensing.md | 5 +++++ 3 files changed, 27 insertions(+) create mode 100644 documentation/operators/src/coc.md create mode 100644 documentation/operators/src/licensing.md diff --git a/documentation/operators/src/SUMMARY.md b/documentation/operators/src/SUMMARY.md index a3f55376c8..82345c6f49 100644 --- a/documentation/operators/src/SUMMARY.md +++ b/documentation/operators/src/SUMMARY.md @@ -20,3 +20,8 @@ - [Troubleshooting](./nodes/troubleshooting.md) - [FAQ](./faq.md) +--- +# Misc. +- [Code of Conduct](coc.md) +- [Licensing](licensing.md) +--- diff --git a/documentation/operators/src/coc.md b/documentation/operators/src/coc.md new file mode 100644 index 0000000000..f8771a73bf --- /dev/null +++ b/documentation/operators/src/coc.md @@ -0,0 +1,17 @@ +# Code of Conduct + +We are committed to providing a friendly, safe and welcoming environment for all, regardless of level of experience, gender identity and expression, sexual orientation, disability, personal appearance, body size, race, ethnicity, age, religion, nationality, or other similar characteristic. + +Please avoid using overtly sexual aliases or other nicknames that might detract from a friendly, safe and welcoming environment for all. + +Please be kind and courteous. There’s no need to be mean or rude. + +Respect that people have differences of opinion and that every design or implementation choice carries a trade-off and numerous costs. There is seldom a right answer. + +Please keep unstructured critique to a minimum. If you have solid ideas you want to experiment with, make a fork and see how it works. + +We will exclude you from interaction if you insult, demean or harass anyone. That is not welcome behaviour. We interpret the term “harassment” as including the definition in the Citizen Code of Conduct; if you have any lack of clarity about what might be included in that concept, please read their definition. In particular, we don’t tolerate behaviour that excludes people in socially marginalized groups. + +Private harassment is also unacceptable. No matter who you are, if you feel you have been or are being harassed or made uncomfortable by a community member, please contact one of the channel ops or any of the Rust moderation team immediately. Whether you’re a regular contributor or a newcomer, we care about making this community a safe place for you and we’ve got your back. + +Likewise any spamming, trolling, flaming, baiting or other attention-stealing behaviour is not welcome. diff --git a/documentation/operators/src/licensing.md b/documentation/operators/src/licensing.md new file mode 100644 index 0000000000..2e8612d4d4 --- /dev/null +++ b/documentation/operators/src/licensing.md @@ -0,0 +1,5 @@ +# Licensing + +Nym is Free Software released under the Apache License V2. + +All of the contributions of the Nym core developers are © Nym Technologies SA. If you are interested in talking to us about other licenses, please get in touch. From 7065f870ebc6be75cf26151bad67211c64a28515 Mon Sep 17 00:00:00 2001 From: mfahampshire Date: Wed, 9 Aug 2023 11:29:57 +0200 Subject: [PATCH 249/361] removed glossary file: needs rework --- documentation/dev-portal/src/SUMMARY.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/documentation/dev-portal/src/SUMMARY.md b/documentation/dev-portal/src/SUMMARY.md index 0e0c02bc58..8184038624 100644 --- a/documentation/dev-portal/src/SUMMARY.md +++ b/documentation/dev-portal/src/SUMMARY.md @@ -45,10 +45,9 @@ - [Community Applications and Guides](community-resources/community-applications-and-guides.md) - [Change Service Grantee Information](info-request.md) - [Rewards FAQ](community-resources/rewards-faq.md) ---- +--- # Misc. -- [Glossary](glossary.md) - [Code of Conduct](coc.md) - [Licensing](licensing.md) --- From 905318ad27cf63ec611fe23b74bd558d47d7c672 Mon Sep 17 00:00:00 2001 From: mfahampshire Date: Wed, 9 Aug 2023 11:30:12 +0200 Subject: [PATCH 250/361] final tweaks --- documentation/docs/src/nodes/gateway.md | 4 ++-- documentation/docs/src/nodes/mixnode.md | 2 +- documentation/docs/src/nodes/validator.md | 3 --- 3 files changed, 3 insertions(+), 6 deletions(-) diff --git a/documentation/docs/src/nodes/gateway.md b/documentation/docs/src/nodes/gateway.md index 814c550d4c..3634c36652 100644 --- a/documentation/docs/src/nodes/gateway.md +++ b/documentation/docs/src/nodes/gateway.md @@ -3,8 +3,8 @@ > The gateway setup and maintenance guide has migrated to the [Operator Guides book](https://nymtech.net/operators/nodes/gateway-setup.html). Gateways are key to both the usability of the mixnet, and the operation of the mixnet's tokenomics. They serve two main functions: -* In the future (when the mixnet is no longer running in 'free to use' mode), to check for zkNym credentials (previously referred to as [Coconut Credentials](../bandwidth-credentials.md)) with which users can anonymously prove they have paid to send traffic through the mixnet. A % of the worth of these credentials will be distributed to the operator of the gateway periodically as payment for providing their service. The more credentials user clients 'spend' with them the higher the rewards for the gateway operator and their delegators. The rest of this value will be sent to the Mixmining Pool, a pool of tokens from which `NYM` rewards are distributed to mix node operators. -* Act as a mailbox for connected clients. Clients create a lasting relationship with a gateway on initialisation, binding themselves to always use a particular gateway as their egress point for mixnet traffic, and always receive mixnet traffic from it (see the [mixnet traffic flow page](../architecture/traffic-flow.md) and the [addressing scheme](../clients/addressing-system.md) for further details). If a client is offline and the Gateway can't deliver packets addressed to it, they will hold these packets until the client comes back online. +* In the future (when the mixnet is no longer running in 'free to use' mode), to check for zkNym credentials (previously referred to as [Coconut Credentials](../bandwidth-credentials.md)) with which users can anonymously prove they have paid to send traffic through the mixnet. A % of the worth of these credentials will be distributed to the operator of the gateway periodically as payment for providing their service. The more credentials user clients 'spend' with them, the higher the rewards for the gateway operator and their delegators will be. The rest of this value will be sent to the Mixmining Pool, a pool of tokens from which `NYM` rewards are distributed to mix node operators. +* Act as a mailbox for connected clients. Clients create a lasting relationship with a gateway on initialisation, binding themselves to always use a particular gateway as their ingress point for mixnet traffic (this also means that this gateway is the egress point for any traffic sent _to_ this client - see the [mixnet traffic flow page](../architecture/traffic-flow.md) and the [addressing scheme](../clients/addressing-system.md) for further details). If a client is offline and the Gateway can't deliver packets addressed to it, they will hold these packets until the client comes back online. ## Further Reading * [Nym Whitepaper](https://nymtech.net/nym-whitepaper.pdf) section 4.2 diff --git a/documentation/docs/src/nodes/mixnode.md b/documentation/docs/src/nodes/mixnode.md index 44dc924d86..b093c6f0b4 100644 --- a/documentation/docs/src/nodes/mixnode.md +++ b/documentation/docs/src/nodes/mixnode.md @@ -6,7 +6,7 @@ Mix nodes are the backbone of the mixnet. These are the nodes that perform 'mix Mix nodes, after receiving a packet, decrypt its outer 'layer' and hold it for a variable amount of time before passing it to its next destination - either another mix node, or a gateway. In doing so, they 'mix' packets by sending them to their next destination in a different order than they were received. -Mix nodes are rewarded according to their quality of service, and their inclusion in the active set (i.e. the nodes that mix traffic for the next epoch) affected by this (as well as their delegation-based reputation - see [further reading](#further-reading) for more on this). +Mix nodes are rewarded according to their quality of service, and the probability of their inclusion in the active set (i.e. the nodes that mix traffic for the next epoch) is also affected by this (as well as their delegation-based reputation - see the [Mix node deepdive](#further-reading) below for more on this). ## (Coming soon) Mixing: a Step-by-Step Breakdown diff --git a/documentation/docs/src/nodes/validator.md b/documentation/docs/src/nodes/validator.md index eba1666351..855cf21700 100644 --- a/documentation/docs/src/nodes/validator.md +++ b/documentation/docs/src/nodes/validator.md @@ -8,8 +8,5 @@ The blockchain plays a supporting but fundamental role in the mixnet: the `NYM` ## Further Reading * Detailed info on Nyx Validators and token flow can be found in [Nym Reward Sharing for Mixnets document](https://nymtech.net/nym-cryptoecon-paper.pdf) in section 2.3 and 2.4. -* For more detailed calculation, read our blog post [Nym Token Economics update](https://blog.nymtech.net/nym-token-economics-update-fedff0ed5267). -* More info on staking can be found [here](https://blog.nymtech.net/staking-in-nym-introducing-mainnet-mixmining-f9bb1cbc7c36). -* [Here](https://blog.nymtech.net/want-to-stake-in-nym-here-is-how-to-choose-a-mix-node-to-delegate-nym-to-c3b862add165) is more info on how to choose a mix node for delegation. * Our [quarterly update](https://blog.nymtech.net/quarterly-token-economic-parameter-update-b2862948710f) on token economics from July 2023. * [Nym Whitepaper](https://nymtech.net/nym-whitepaper.pdf) section 3.1 \ No newline at end of file From b1c3264f69894685bbb24f004481c9a41cff4e4f Mon Sep 17 00:00:00 2001 From: mfahampshire Date: Wed, 9 Aug 2023 11:47:11 +0200 Subject: [PATCH 251/361] removed doubled header --- documentation/operators/src/preliminary-steps.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/documentation/operators/src/preliminary-steps.md b/documentation/operators/src/preliminary-steps.md index ec13e7ea59..2f7cb2008b 100644 --- a/documentation/operators/src/preliminary-steps.md +++ b/documentation/operators/src/preliminary-steps.md @@ -1,8 +1,6 @@ # Preliminary Steps -> The Nym `mix node`, `gateway` and `network-requester` binaries were built in the [building nym](./binaries/building-nym.md) section. If you haven't yet built Nym and want to run the code, go there first. - -## Preliminary steps +> The Nym `mixnode`, `gateway` and `network-requester` binaries were built in the [building nym](./binaries/building-nym.md) section. If you haven't yet built Nym and want to run the code, go there first. There are a couple of steps that need completing before starting to set up your mix node, gateway or a network requester: From 074e27291b75e14b2639ba683817add8e0fdf935 Mon Sep 17 00:00:00 2001 From: mfahampshire Date: Wed, 9 Aug 2023 12:51:34 +0200 Subject: [PATCH 252/361] updated links in operator binaries compilation list --- documentation/operators/src/binaries/building-nym.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/documentation/operators/src/binaries/building-nym.md b/documentation/operators/src/binaries/building-nym.md index 8610fcb63a..20c157ad21 100644 --- a/documentation/operators/src/binaries/building-nym.md +++ b/documentation/operators/src/binaries/building-nym.md @@ -55,12 +55,12 @@ NETWORK=sandbox cargo build --release # build your binaries with **sandbox** con Quite a bit of stuff gets built. The key working parts are: -* [mix node](../nodes/mix-node-setup.md): `nym-mixnode` -* [gateway node](../nodes/gateway-setup.md): `nym-gateway` +* [mix node](../nodes/mix-node.md): `nym-mixnode` +* [gateway node](../nodes/gateway.md): `nym-gateway` * [websocket client](https://nymtech.net/docs/clients/websocket-client.html): `nym-client` * [socks5 client](https://nymtech.net/docs/clients/socks5-client.html): `nym-socks5-client` * [webassembly client](https://nymtech.net/docs/clients/webassembly-client.html): `webassembly-client` -* [network requester](../nodes/network-requester-setup.md): `nym-network-requester` +* [network requester](../nodes/network-requester.md): `nym-network-requester` * [nym-cli tool](https://nymtech.net/docs/tools/nym-cli.html): `nym-cli` The repository also contains Typescript applications which aren't built in this process. These can be built by following the instructions on their respective docs pages. From 740846befcfbb8567bb3fb282a95816e032b09d4 Mon Sep 17 00:00:00 2001 From: mfahampshire Date: Wed, 9 Aug 2023 13:25:37 +0200 Subject: [PATCH 253/361] fixed links --- documentation/operators/src/binaries/building-nym.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/documentation/operators/src/binaries/building-nym.md b/documentation/operators/src/binaries/building-nym.md index 20c157ad21..8610fcb63a 100644 --- a/documentation/operators/src/binaries/building-nym.md +++ b/documentation/operators/src/binaries/building-nym.md @@ -55,12 +55,12 @@ NETWORK=sandbox cargo build --release # build your binaries with **sandbox** con Quite a bit of stuff gets built. The key working parts are: -* [mix node](../nodes/mix-node.md): `nym-mixnode` -* [gateway node](../nodes/gateway.md): `nym-gateway` +* [mix node](../nodes/mix-node-setup.md): `nym-mixnode` +* [gateway node](../nodes/gateway-setup.md): `nym-gateway` * [websocket client](https://nymtech.net/docs/clients/websocket-client.html): `nym-client` * [socks5 client](https://nymtech.net/docs/clients/socks5-client.html): `nym-socks5-client` * [webassembly client](https://nymtech.net/docs/clients/webassembly-client.html): `webassembly-client` -* [network requester](../nodes/network-requester.md): `nym-network-requester` +* [network requester](../nodes/network-requester-setup.md): `nym-network-requester` * [nym-cli tool](https://nymtech.net/docs/tools/nym-cli.html): `nym-cli` The repository also contains Typescript applications which aren't built in this process. These can be built by following the instructions on their respective docs pages. From df9aec15546a1929b2c1723109fbee85314c0bac Mon Sep 17 00:00:00 2001 From: mfahampshire Date: Wed, 9 Aug 2023 14:11:06 +0200 Subject: [PATCH 254/361] updated grantee info update link --- documentation/dev-portal/src/info-request.md | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/documentation/dev-portal/src/info-request.md b/documentation/dev-portal/src/info-request.md index 551da83bf3..e3edbdc3de 100644 --- a/documentation/dev-portal/src/info-request.md +++ b/documentation/dev-portal/src/info-request.md @@ -1,6 +1,6 @@ # Change Service Grantee Information -The Nym Service Grantees User Information Update Form allows Service Grantees to update their information with the Nym team. The form allows grantees to update their current: +If you wish to update any of the following information: - `Email Address` - `NYM address` - `Network Requester` @@ -9,10 +9,6 @@ The Nym Service Grantees User Information Update Form allows Service Grantees to - `Service` - `Payment Address` -Complete and submit your form so the Nym team to keep track of the service grantees and your current information, ensuring that deliver payments and handle other logistics smooth and accurately. - -> ⚠️ Service Grantee User Information will be updated by our team by 5pm UTC between Mon - Fri. - - +Please reach out either in the `#service-grantees` channel on Discord or on [Matrix](https://matrix.to/#/#nym-community:nymtech.chat). From 035535058e228079cd22dfd820532d7f70a9246b Mon Sep 17 00:00:00 2001 From: mfahampshire Date: Wed, 9 Aug 2023 14:24:57 +0200 Subject: [PATCH 255/361] fixed incorrect import path for chat app --- documentation/docs/src/sdk/typescript.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/documentation/docs/src/sdk/typescript.md b/documentation/docs/src/sdk/typescript.md index eed1167692..5ce939e858 100644 --- a/documentation/docs/src/sdk/typescript.md +++ b/documentation/docs/src/sdk/typescript.md @@ -47,7 +47,7 @@ There are multiple example projects in [`nym/sdk/typescript/examples/`](https:// The best place to start if you just want to quickly get a basic frontend up and running with which to experiment is `examples/plain-html`: ```typescript -{{#include ../../../../sdk/typescript/examples/plain-html/src/index.ts}} +{{#include ../../../../sdk/typescript/examples/chat-app/plain-html/src/index.ts}} ``` As you can see, all that is required to create an ephemeral keypair and connect to the mixnet is creating a client and then subscribing to the mixnet events coming down the websocket, and adding logic to deal with them. From 230d8aa170ad2121557033edaa42a26b9d226d53 Mon Sep 17 00:00:00 2001 From: mfahampshire Date: Wed, 9 Aug 2023 14:25:29 +0200 Subject: [PATCH 256/361] changed out of date var 'mix_node_release_version' to 'platform_release_version --- documentation/operators/src/nodes/maintenance.md | 2 +- documentation/operators/src/nodes/mix-node-setup.md | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/documentation/operators/src/nodes/maintenance.md b/documentation/operators/src/nodes/maintenance.md index 911a6825af..bb7a0f0ea7 100644 --- a/documentation/operators/src/nodes/maintenance.md +++ b/documentation/operators/src/nodes/maintenance.md @@ -230,7 +230,7 @@ Here's a systemd service file to do that: ```ini [Unit] -Description=Nym Mixnode ({{mix_node_release_version}}) +Description=Nym Mixnode ({{platform_release_version}}) StartLimitInterval=350 StartLimitBurst=10 diff --git a/documentation/operators/src/nodes/mix-node-setup.md b/documentation/operators/src/nodes/mix-node-setup.md index 1c70383a61..a50c5cac13 100644 --- a/documentation/operators/src/nodes/mix-node-setup.md +++ b/documentation/operators/src/nodes/mix-node-setup.md @@ -87,13 +87,13 @@ Mixnode configuration completed. |_| |_|\__, |_| |_| |_| |___/ - (nym-mixnode - version {{mix_node_release_version}}) + (nym-mixnode - version {{platform_release_version}}) -Identity Key: DhmUYedPZvhP9MMwXdNpPaqCxxTQgjAg78s2nqtTTiNF","version":"{{mix_node_release_version}}"},"cost_params +Identity Key: DhmUYedPZvhP9MMwXdNpPaqCxxTQgjAg78s2nqtTTiNF","version":"{{platform_release_version}}"},"cost_params Sphinx Key: CfZSy1jRfrfiVi9JYexjFWPqWkKoY72t7NdpWaq37K8Z Host: 62.240.134.189 (bind address: 62.240.134.189) -Version: {{mix_node_release_version}} +Version: {{platform_release_version}} Mix Port: 1789, Verloc port: 1790, Http Port: 8000 ``` ~~~ @@ -134,7 +134,7 @@ It will look something like this: |_| |_|\__, |_| |_| |_| |___/ - (nym-mixnode - version {{mix_node_release_version}}) + (nym-mixnode - version {{platform_release_version}}) >>> attempting to sign 22Z9wt4PyiBCbMiErxj5bBa4VCCFsjNawZ1KnLyMeV9pMUQGyksRVANbXHjWndMUaXNRnAuEVJW6UCxpRJwZe788hDt4sicsrv7iAXRajEq19cWPVybbUqgeo76wbXbCbRdg1FvVKgYZGZZp8D72p5zWhKSBRD44qgCrqzfV1SkiFEhsvcLUvZATdLRocAUL75KmWivyRiQjCE1XYEWyRH9yvRYn4TymWwrKVDtEB63zhHjATN4QEi2E5qSrSbBcmmqatXsKakbgSbQoLsYygcHx7tkwbQ2HDYzeiKP1t16Rhcjn6Ftc2FuXUNnTcibk2LQ1hiqu3FAq31bHUbzn2wiaPfm4RgqTwGM4eqnjBofwR3251wQSxbYwKUYwGsrkweRcoPuEaovApR9R19oJ7GVG5BrKmFwZWX3XFVuECe8vt1x9MY7DbQ3xhAapsHhThUmzN6JPPU4qbQ3PdMt3YVWy6oRhap97ma2dPMBaidebfgLJizpRU3Yu7mtb6E8vgi5Xnehrgtd35gitoJqJUY5sB1p6TDPd6vk3MVU1zqusrke7Lvrud4xKfCLqp672Bj9eGb2wPwow643CpHuMkhigfSWsv9jDq13d75EGTEiprC2UmWTzCJWHrDH7ka68DZJ5XXAW67DBewu7KUm1jrJkNs55vS83SWwm5RjzQLVhscdtCH1Bamec6uZoFBNVzjs21o7ax2WHDghJpGMxFi6dmdMCZpqn618t4 From a2445183bf3d90c146655cee90ed103c342f0abb Mon Sep 17 00:00:00 2001 From: Gala Date: Wed, 9 Aug 2023 17:56:36 +0200 Subject: [PATCH 257/361] theme change --- documentation/dev-portal/book.toml | 6 ++-- documentation/dev-portal/custom.css | 38 +++++++++++++++++++++++ documentation/dev-portal/last-changed.css | 6 ---- 3 files changed, 41 insertions(+), 9 deletions(-) create mode 100644 documentation/dev-portal/custom.css delete mode 100644 documentation/dev-portal/last-changed.css diff --git a/documentation/dev-portal/book.toml b/documentation/dev-portal/book.toml index 8f44c978f4..172868cbe8 100644 --- a/documentation/dev-portal/book.toml +++ b/documentation/dev-portal/book.toml @@ -17,7 +17,7 @@ src = "src" # - run `mdbook build`: this will rebuild the `./theme` directory # - change value of `turn-off` back to `true` [preprocessor.theme] -pagetoc = true +pagetoc = true # some variables related (defined in theme/css/variables.css) # `content-max-width` + `pagetoc-width` = 95% seems the best pagetoc-width = "13%" @@ -37,7 +37,7 @@ nav-chapters-min-width = "auto" chapter-line-height = "2em" section-line-height = "1.5em" -# # if true, never read and touch the files in theme dir +# if true, never read and touch the files in theme dir turn-off = false [preprocessor.admonish] @@ -77,7 +77,7 @@ curly-quotes = true # mathjax-support = false # useful if we want to pull equations in ? copy-fonts = true no-section-label = false -additional-css = ["theme/pagetoc.css", "././mdbook-admonish.css", "last-changed.css"] +additional-css = ["theme/pagetoc.css", "././mdbook-admonish.css", "custom.css"] additional-js = ["theme/pagetoc.js"] git-repository-url = "https://github.com/nymtech/nym" git-repository-icon = "fa-github" diff --git a/documentation/dev-portal/custom.css b/documentation/dev-portal/custom.css new file mode 100644 index 0000000000..0174c7796c --- /dev/null +++ b/documentation/dev-portal/custom.css @@ -0,0 +1,38 @@ +:root { + --mono-font: "Open Sans", Consolas, "Ubuntu Mono", Menlo, "DejaVu Sans Mono", + monospace, monospace; +} +.coal { + --bg: #121726; + --fg: #f2f2f2; + + --sidebar-bg: #121726; + --sidebar-fg: #f2f2f2; + --sidebar-active: #fb6e4e; + + --icons: #f2f2f2; + --icons-hover: #fb6e4e; + + --links: #fb6e4e; +} + +.light { + --bg: #f4f6f8; + --fg: #121726; + + --sidebar-bg: #f4f6f8; + --sidebar-fg: #121726; + --sidebar-active: #fb6e4e; + + --icons: #121726; + --icons-hover: #fb6e4e; + + --links: #fb6e4e; +} + +footer { + font-size: 0.8em; + text-align: center; + border-top: 1px solid black; + padding: 5px 0; +} \ No newline at end of file diff --git a/documentation/dev-portal/last-changed.css b/documentation/dev-portal/last-changed.css deleted file mode 100644 index 0c4228de32..0000000000 --- a/documentation/dev-portal/last-changed.css +++ /dev/null @@ -1,6 +0,0 @@ -footer { - font-size: 0.8em; - text-align: center; - border-top: 1px solid black; - padding: 5px 0; - } \ No newline at end of file From 8b65e44767eea8948a4e0f9d937cf3c0653a519d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C4=99drzej=20Stuczy=C5=84ski?= Date: Thu, 10 Aug 2023 14:40:15 +0100 Subject: [PATCH 258/361] using stricter version requirements for mdbook and mdbook-variables --- .github/workflows/ci-dev.yml | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci-dev.yml b/.github/workflows/ci-dev.yml index 80688c2ccc..90470e8c3e 100644 --- a/.github/workflows/ci-dev.yml +++ b/.github/workflows/ci-dev.yml @@ -23,10 +23,13 @@ jobs: with: toolchain: stable - name: Install mdbook - run: (test -x $HOME/.cargo/bin/mdbook || cargo install --vers "^0.4" mdbook) + # to make sure we don't run into toml dependency issue, make sure to pull fixed mdbook that depends on old 0.5 + # and OLD mdbook-variables that uses the same dependency + # (mdbook-variables 0.2.3 updated to 0.7 which was used by mdbook incorrectly and extremely briefly) + run: (test -x $HOME/.cargo/bin/mdbook || cargo install --vers "^0.4.33" mdbook) - name: Install mdbook plugins run: | - cargo install --vers "^0.2.0" mdbook-variables && cargo install \ + cargo install --vers "=0.2.2" mdbook-variables && cargo install \ --vers "^1.8.0" mdbook-admonish && cargo install --vers \ "^0.1.2" mdbook-last-changed && cargo install --vers "^0.1.2" mdbook-theme \ && cargo install --vers "^0.7.7" mdbook-linkcheck From b0d5bacafd04de56c354396a844122b53e9a8d1c Mon Sep 17 00:00:00 2001 From: serinko <97586125+serinko@users.noreply.github.com> Date: Thu, 10 Aug 2023 18:31:56 +0200 Subject: [PATCH 259/361] picked theme changes:dev-portal -> operators; theme finished --- documentation/operators/book.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/documentation/operators/book.toml b/documentation/operators/book.toml index c39af18a65..52d1379fa8 100644 --- a/documentation/operators/book.toml +++ b/documentation/operators/book.toml @@ -18,7 +18,7 @@ src = "src" # - run `mdbook build`: this will rebuild the `./theme` directory # - change value of `turn-off` back to `true` [preprocessor.theme] -pagetoc = true +pagetoc = true # some variables related (defined in theme/css/variables.css) pagetoc-width = "13%" content-max-width = "70%" # 82 @@ -87,7 +87,7 @@ curly-quotes = true # mathjax-support = false # useful if we want to pull equations in copy-fonts = true no-section-label = false -additional-css = ["theme/pagetoc.css", "././mdbook-admonish.css","./last-changed.css"] +additional-css = ["theme/pagetoc.css", "././mdbook-admonish.css","./custom.css"] additional-js = ["theme/pagetoc.js"] git-repository-url = "https://github.com/nymtech/nym" git-repository-icon = "fa-github" From 1f2a2c37ab29de430bde0ab81d33689f6c8385f0 Mon Sep 17 00:00:00 2001 From: serinko <97586125+serinko@users.noreply.github.com> Date: Thu, 10 Aug 2023 18:32:13 +0200 Subject: [PATCH 260/361] picked theme changes:dev-portal -> operators; theme finished --- documentation/operators/custom.css | 38 ++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 documentation/operators/custom.css diff --git a/documentation/operators/custom.css b/documentation/operators/custom.css new file mode 100644 index 0000000000..170e91dd26 --- /dev/null +++ b/documentation/operators/custom.css @@ -0,0 +1,38 @@ +:root { + --mono-font: "Open Sans", Consolas, "Ubuntu Mono", Menlo, "DejaVu Sans Mono", + monospace, monospace; +} +.coal { + --bg: #121726; + --fg: #f2f2f2; + + --sidebar-bg: #121726; + --sidebar-fg: #f2f2f2; + --sidebar-active: #fb6e4e; + + --icons: #f2f2f2; + --icons-hover: #fb6e4e; + + --links: #fb6e4e; +} + +.light { + --bg: #f4f6f8; + --fg: #121726; + + --sidebar-bg: #f4f6f8; + --sidebar-fg: #121726; + --sidebar-active: #fb6e4e; + + --icons: #121726; + --icons-hover: #fb6e4e; + + --links: #fb6e4e; +} + +footer { + font-size: 0.8em; + text-align: center; + border-top: 1px solid black; + padding: 5px 0; +} From 54ff3ee987bac964f106299a82821b524c2d1bcd Mon Sep 17 00:00:00 2001 From: serinko <97586125+serinko@users.noreply.github.com> Date: Thu, 10 Aug 2023 18:39:04 +0200 Subject: [PATCH 261/361] applied theme changes:dev-portal -> docs --- documentation/docs/book.toml | 2 +- documentation/docs/custom.css | 38 +++++++++++++++++++++++++++++++++++ 2 files changed, 39 insertions(+), 1 deletion(-) create mode 100644 documentation/docs/custom.css diff --git a/documentation/docs/book.toml b/documentation/docs/book.toml index cda7e3db07..ce666bddf2 100644 --- a/documentation/docs/book.toml +++ b/documentation/docs/book.toml @@ -85,7 +85,7 @@ curly-quotes = true # mathjax-support = false # useful if we want to pull equations in copy-fonts = true no-section-label = false -additional-css = ["theme/pagetoc.css", "././mdbook-admonish.css", "last-changed.css"] +additional-css = ["theme/pagetoc.css", "././mdbook-admonish.css", "./custom.css"] additional-js = ["theme/pagetoc.js"] git-repository-url = "https://github.com/nymtech/nym" git-repository-icon = "fa-github" diff --git a/documentation/docs/custom.css b/documentation/docs/custom.css new file mode 100644 index 0000000000..170e91dd26 --- /dev/null +++ b/documentation/docs/custom.css @@ -0,0 +1,38 @@ +:root { + --mono-font: "Open Sans", Consolas, "Ubuntu Mono", Menlo, "DejaVu Sans Mono", + monospace, monospace; +} +.coal { + --bg: #121726; + --fg: #f2f2f2; + + --sidebar-bg: #121726; + --sidebar-fg: #f2f2f2; + --sidebar-active: #fb6e4e; + + --icons: #f2f2f2; + --icons-hover: #fb6e4e; + + --links: #fb6e4e; +} + +.light { + --bg: #f4f6f8; + --fg: #121726; + + --sidebar-bg: #f4f6f8; + --sidebar-fg: #121726; + --sidebar-active: #fb6e4e; + + --icons: #121726; + --icons-hover: #fb6e4e; + + --links: #fb6e4e; +} + +footer { + font-size: 0.8em; + text-align: center; + border-top: 1px solid black; + padding: 5px 0; +} From f569ba1c1725136b5ad9893e0a9c69eab06287b7 Mon Sep 17 00:00:00 2001 From: mfahampshire Date: Fri, 11 Aug 2023 10:16:37 +0200 Subject: [PATCH 262/361] pull fix from ci-dev.yml to ci-docs.yml --- .github/workflows/ci-docs.yml | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/.github/workflows/ci-docs.yml b/.github/workflows/ci-docs.yml index 2523013204..5d7874833b 100644 --- a/.github/workflows/ci-docs.yml +++ b/.github/workflows/ci-docs.yml @@ -27,14 +27,13 @@ jobs: command: build args: --workspace --release --all - name: Install mdbook - run: (test -x $HOME/.cargo/bin/mdbook || cargo install --vers "^0.4" mdbook) + run: (test -x $HOME/.cargo/bin/mdbook || cargo install --vers "^0.4.33" mdbook) - name: Install mdbook plugins run: | - cargo install --vers "^0.2.0" mdbook-variables && cargo install \ + cargo install --vers "=0.2.2" mdbook-variables && cargo install \ --vers "^1.8.0" mdbook-admonish && cargo install --vers \ - "^0.1.2" mdbook-last-changed && cargo install --vers "^0.1.2" \ - mdbook-theme && cargo install --vers "^0.7.7" mdbook-linkcheck && \ - cargo install --vers "^0.5.0" mdbook-cmdrun + "^0.1.2" mdbook-last-changed && cargo install --vers "^0.1.2" mdbook-theme \ + && cargo install --vers "^0.7.7" mdbook-linkcheck - name: Clean website run: cd documentation/docs && mdbook clean - name: Build website From 84ad777c4209ce46d0d100b856bb2670c50df290 Mon Sep 17 00:00:00 2001 From: mfahampshire Date: Fri, 11 Aug 2023 10:53:34 +0200 Subject: [PATCH 263/361] added workflow_dispatch to ci for dev-portal and docs --- .github/workflows/cd-dev.yml | 1 + .github/workflows/cd-docs.yml | 1 + 2 files changed, 2 insertions(+) diff --git a/.github/workflows/cd-dev.yml b/.github/workflows/cd-dev.yml index 0ca463345f..664f3cd79e 100644 --- a/.github/workflows/cd-dev.yml +++ b/.github/workflows/cd-dev.yml @@ -1,6 +1,7 @@ name: CD dev-portal on: + workflow_dispatch: push: branches: master paths: diff --git a/.github/workflows/cd-docs.yml b/.github/workflows/cd-docs.yml index 782a2c196d..b3f15ab265 100644 --- a/.github/workflows/cd-docs.yml +++ b/.github/workflows/cd-docs.yml @@ -1,6 +1,7 @@ name: CD docs on: + workflow_dispatch: push: branches: master paths: From 96e2712039df26af696239c0e88acb2ee5c8af3b Mon Sep 17 00:00:00 2001 From: mfahampshire Date: Fri, 11 Aug 2023 12:29:20 +0200 Subject: [PATCH 264/361] reintroduced linkchecker + fixed broken links --- documentation/docs/book.toml | 4 ++-- documentation/docs/src/architecture/network-overview.md | 2 +- documentation/docs/src/binaries/building-nym.md | 2 +- documentation/docs/src/clients/socks5-client.md | 6 +++--- documentation/docs/src/nyx/ledger-live.md | 2 +- documentation/docs/src/nyx/rpc-node.md | 2 +- documentation/docs/src/wallet/cli-wallet.md | 4 ++-- documentation/operators/book.toml | 4 ++-- documentation/operators/src/nodes/maintenance.md | 2 +- 9 files changed, 14 insertions(+), 14 deletions(-) diff --git a/documentation/docs/book.toml b/documentation/docs/book.toml index ce666bddf2..6d22440ee3 100644 --- a/documentation/docs/book.toml +++ b/documentation/docs/book.toml @@ -119,5 +119,5 @@ expand = true # partial words will match longer terms heading-split-level = 3 # link results to heading levels copy-js = true # include Javascript code for search -# [output.linkcheck] -# warning-policy = "warn" +[output.linkcheck] +warning-policy = "warn" diff --git a/documentation/docs/src/architecture/network-overview.md b/documentation/docs/src/architecture/network-overview.md index fab4655e09..3a69f78be7 100644 --- a/documentation/docs/src/architecture/network-overview.md +++ b/documentation/docs/src/architecture/network-overview.md @@ -26,7 +26,7 @@ The mixnet - the different pieces of software that your traffic will pass throug * **Gateways** act as message storage for clients which may go offline and come back online again, and defend against denial of service attacks. The default gateway implementation included in the Nym platform code holds packets for later retrieval. For many applications (such as simple chat), this is usable out of the box, as it provides a place that potentially offline clients can retrieve packets from. The access token allows clients to pull messages from the gateway node. -* **Services** are applications that communicate with nym clients, listening and sending traffic to the mixnet. This is an umbrella term for a variety of different pieces of code, such as the [network requester](../nodes/network-requester-setup.md) binary. +* **Services** are applications that communicate with nym clients, listening and sending traffic to the mixnet. This is an umbrella term for a variety of different pieces of code, such as the [network requester](../nodes/network-requester.md) binary. * **Nyx Blockchain Validators** secure the network with proof-of-stake Sybil defenses, determine which nodes are included within the network, and work together to create Coconut threshold credentials which provide anonymous access to data and resources. They also produce blocks and secure the Nyx Blockchain. Initially, this chain was used only to house the CosmWasm smart contracts keeping track of Nym's network topology, token vesting contracts, and the `NYM` token itself. In recent months, we've decided to expand the role of Nyx and instead expand its role by making it an open smart contract platform for anyone to upload CosmWasm smart contracts to. Validators also provide privacy-enhanced credentials based on the testimony of a set of decentralized, blockchain-based issuing authorities. Nym validators use the [Coconut](https://arxiv.org/abs/1802.07344) [signature scheme](https://en.wikipedia.org/wiki/Digital_signature) to issue credentials. This allows privacy apps to generate anonymous resource claims through decentralised authorities, then use them with Service Providers. diff --git a/documentation/docs/src/binaries/building-nym.md b/documentation/docs/src/binaries/building-nym.md index 06edd54a2f..e2c970bd00 100644 --- a/documentation/docs/src/binaries/building-nym.md +++ b/documentation/docs/src/binaries/building-nym.md @@ -8,7 +8,7 @@ Nym has two main codebases: - the [Nym platform](https://github.com/nymtech/nym), written in Rust. This contains all of our code _except_ for the validators. - the [Nym validators](https://github.com/nymtech/nyxd), written in Go. -> This page details how to build the main Nym platform code. **If you want to build and run a validator, [go here](../nodes/validator-setup.md) instead.** +> This page details how to build the main Nym platform code. **If you want to build and run a validator, [go here](../nodes/validator.md) instead.** ## Prerequisites - Debian/Ubuntu: `pkg-config`, `build-essential`, `libssl-dev`, `curl`, `jq`, `git` diff --git a/documentation/docs/src/clients/socks5-client.md b/documentation/docs/src/clients/socks5-client.md index e8bce0e948..e3a5759685 100644 --- a/documentation/docs/src/clients/socks5-client.md +++ b/documentation/docs/src/clients/socks5-client.md @@ -60,7 +60,7 @@ There are 2 pieces of software that work together to send SOCKS traffic through The `nym-socks5-client` allows you to do the following from your local machine: * Take a TCP data stream from a application that can send traffic via SOCKS5. * Chop up the TCP stream into multiple Sphinx packets, assigning sequence numbers to them, while leaving the TCP connection open for more data -* Send the Sphinx packets through the mixnet to a [network requester](../nodes/network-requester-setup.md). Packets are shuffled and mixed as they transit the mixnet. +* Send the Sphinx packets through the mixnet to a [network requester](../nodes/network-requester.md). Packets are shuffled and mixed as they transit the mixnet. The `nym-network-requester` then reassembles the original TCP stream using the packets' sequence numbers, and make the intended request. It will then chop up the response into Sphinx packets and send them back through the mixnet to your `nym-socks5-client`. The application will then receive its data, without even noticing that it wasn't talking to a "normal" SOCKS5 proxy! @@ -100,9 +100,9 @@ Before you can use the client, you need to initalise a new instance of it, which The `--id` in the example above is a local identifier so that you can name your clients and keep track of them on your local system; it is **never** transmitted over the network. -The `--provider` field needs to be filled with the Nym address of a Network Requester that can make network requests on your behalf. If you don't want to [run your own](../nodes/network-requester-setup.md) you can select one from the [mixnet explorer](https://explorer.nymtech.net/network-components/service-providers) by copying its `Client ID` and using this as the value of the `--provider` flag. Alternatively, you could use [this list](https://harbourmaster.nymtech.net/). +The `--provider` field needs to be filled with the Nym address of a Network Requester that can make network requests on your behalf. If you don't want to [run your own](../nodes/network-requester.md) you can select one from the [mixnet explorer](https://explorer.nymtech.net/network-components/service-providers) by copying its `Client ID` and using this as the value of the `--provider` flag. Alternatively, you could use [this list](https://harbourmaster.nymtech.net/). -Since the nodes on this list are the infrastructure for [Nymconnect](https://nymtech.net/developers/quickstart/nymconnect-gui.html) they will support all apps on the [default whitelist](../nodes/network-requester-setup.md#network-requester-whitelist): Keybase, Telegram, Electrum, Blockstream Green, and Helios. +Since the nodes on this list are the infrastructure for [Nymconnect](https://nymtech.net/developers/quickstart/nymconnect-gui.html) they will support all apps on the [default whitelist](../nodes/network-requester.md#network-requester-whitelist): Keybase, Telegram, Electrum, Blockstream Green, and Helios. #### Choosing a Gateway By default - as in the example above - your client will choose a random gateway to connect to. diff --git a/documentation/docs/src/nyx/ledger-live.md b/documentation/docs/src/nyx/ledger-live.md index f7559b6fd2..031834fa24 100644 --- a/documentation/docs/src/nyx/ledger-live.md +++ b/documentation/docs/src/nyx/ledger-live.md @@ -4,7 +4,7 @@ Use the following instructions to interact with the Nyx blockchain - either with ## Prerequisites * Download and install [Ledger Live](https://www.ledger.com/ledger-live). -* Compile the `nyxd` binary as per the instructions [here](../nodes/validator-setup.md#building-the-nym-validator). Stop after you can successfully run `nyxd` and get the helptext in your console output. +* Compile the `nyxd` binary as per the instructions [here](https://nymtech.net/operators/nodes/validator-setup.md#building-the-nym-validator). Stop after you can successfully run `nyxd` and get the helptext in your console output. ## Prepare your Ledger App * Plug in your Ledger device diff --git a/documentation/docs/src/nyx/rpc-node.md b/documentation/docs/src/nyx/rpc-node.md index af76f1f927..dc48d68409 100644 --- a/documentation/docs/src/nyx/rpc-node.md +++ b/documentation/docs/src/nyx/rpc-node.md @@ -4,6 +4,6 @@ RPC Nodes (which might otherwise be referred to as 'Lite Nodes' or just 'Full No You may want to set up an RPC Node for querying the blockchain, or in order to have an endpoint that your app can use to send transactions. -In order to set up an RPC Node, simply follow the instructions to set up a [Validator](../nodes/validator-setup.md), but **exclude the `nyxd tx staking create-validator` command**. +In order to set up an RPC Node, simply follow the instructions to set up a [Validator](https://nymtech.net/operators/nodes/validator-setup.md), but **exclude the `nyxd tx staking create-validator` command**. If you want to fast-sync your node, check out the Polkachu snapshot and their other [resources](https://polkachu.com/seeds/nym). \ No newline at end of file diff --git a/documentation/docs/src/wallet/cli-wallet.md b/documentation/docs/src/wallet/cli-wallet.md index 3f713b1937..36670190a3 100644 --- a/documentation/docs/src/wallet/cli-wallet.md +++ b/documentation/docs/src/wallet/cli-wallet.md @@ -1,8 +1,8 @@ # CLI Wallet -If you have already read our validator setup and maintenance [documentation](../nodes/validator-setup.md) you will have seen that we compile and use the `nyxd` binary primarily for our validators. This binary can however be used for many other tasks, such as creating and using keypairs for wallets, or automated setups that require the signing and broadcasting of transactions. +If you have already read our validator setup and maintenance [documentation](../nodes/validator.md) you will have seen that we compile and use the `nyxd` binary primarily for our validators. This binary can however be used for many other tasks, such as creating and using keypairs for wallets, or automated setups that require the signing and broadcasting of transactions. ### Using `nyxd` binary as a CLI wallet -You can use the `nyxd` as a minimal CLI wallet if you want to set up an account (or multiple accounts). Just compile the binary as per the documentation, **stopping after** the [building your validator](../nodes/validator-setup.md#building-your-validator) step is complete. You can then run `nyxd keys --help` to see how you can set up and store different keypairs with which to interact with the Nyx blockchain. +You can use the `nyxd` as a minimal CLI wallet if you want to set up an account (or multiple accounts). Just compile the binary as per the documentation, **stopping after** the [building your validator](../nodes/validator.md) step is complete. You can then run `nyxd keys --help` to see how you can set up and store different keypairs with which to interact with the Nyx blockchain. For more on interacting with the chain, see the [Interacting with Nyx Chain and Smart Contracts](../nyx/interacting-with-chain.md) page. diff --git a/documentation/operators/book.toml b/documentation/operators/book.toml index 52d1379fa8..be7233c069 100644 --- a/documentation/operators/book.toml +++ b/documentation/operators/book.toml @@ -121,5 +121,5 @@ expand = true # partial words will match longer terms heading-split-level = 3 # link results to heading levels copy-js = true # include Javascript code for search -# [output.linkcheck] -# warning-policy = "warn" +[output.linkcheck] +warning-policy = "warn" diff --git a/documentation/operators/src/nodes/maintenance.md b/documentation/operators/src/nodes/maintenance.md index bb7a0f0ea7..c955975118 100644 --- a/documentation/operators/src/nodes/maintenance.md +++ b/documentation/operators/src/nodes/maintenance.md @@ -46,7 +46,7 @@ You can either do this graphically via the Desktop Wallet, or the CLI. ![Node Settings Page](../images/wallet-screenshots/node_settings.png) ### Updating node information via the CLI -If you want to bond your `` via the CLI, then check out the [relevant section in the Nym CLI](../../documentation/docs/src/tools/nym-cli.md#upgrade-a-mix-node) docs. +If you want to bond your `` via the CLI, then check out the [relevant section in the Nym CLI](https://nymtech.net/docs/tools/nym-cli.md#upgrade-a-mix-node) docs. ### Upgrading Network Requester to >= v1.1.10 from Date: Fri, 11 Aug 2023 12:29:30 +0200 Subject: [PATCH 265/361] rough staging sh script --- documentation/staging.sh | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100755 documentation/staging.sh diff --git a/documentation/staging.sh b/documentation/staging.sh new file mode 100755 index 0000000000..07ca2de1d2 --- /dev/null +++ b/documentation/staging.sh @@ -0,0 +1,35 @@ +#!/bin/bash + +# commands assume you run script from `nym/documentation/` + +# array of project dirs +declare -a projects=("docs" "dev-portal" "operators") + +## now loop through the above array +for i in "${projects[@]}" +do + # cd to project dir + cd "./$i" && + # little sanity checks + echo $(pwd) && echo $(mdbook --version) && + # clean old book + rm -rf ./book/ + # build book + mdbook build + # check for destination, if ! then mkdir & check again else echo thumbs up + if [ ! -d ../../dist/docs/$i ]; then + echo "dest doesn't exist: creating dir" + mkdir -p ../../dist/docs/$i + fi + if [ -d ../../dist/docs/$i ]; then + echo "cp destination exists, all good" + fi + # clean old dist/$i + rm -rf ../../dist/docs/$i + # move newly rendered book/ to dist + cp -r ./book/ ../../dist/docs/$i + # sanity check + ls -a ../../dist/docs/$i/html + # cd back to ../documentation/ + cd ../ +done \ No newline at end of file From 89ea3039d5d93fbf2cc6da61be4f791328ade1ef Mon Sep 17 00:00:00 2001 From: mfahampshire Date: Fri, 11 Aug 2023 12:30:41 +0200 Subject: [PATCH 266/361] removed unnecessary linkcheck rendering output from dist/ --- documentation/staging.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/documentation/staging.sh b/documentation/staging.sh index 07ca2de1d2..64fec07e47 100755 --- a/documentation/staging.sh +++ b/documentation/staging.sh @@ -27,9 +27,9 @@ do # clean old dist/$i rm -rf ../../dist/docs/$i # move newly rendered book/ to dist - cp -r ./book/ ../../dist/docs/$i + cp -r ./book/html/ ../../dist/docs/$i # sanity check - ls -a ../../dist/docs/$i/html + ls -a ../../dist/docs/$i/ # cd back to ../documentation/ cd ../ done \ No newline at end of file From a1a1970dc1fb37457b0178facc7ddd1957ea8a42 Mon Sep 17 00:00:00 2001 From: mfahampshire Date: Fri, 11 Aug 2023 13:08:19 +0200 Subject: [PATCH 267/361] updated readme with soon to be operator doc URL --- documentation/README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/documentation/README.md b/documentation/README.md index 3330b3e969..e074f7cdfe 100644 --- a/documentation/README.md +++ b/documentation/README.md @@ -2,5 +2,6 @@ * `docs` contains technical documentation hosted at [https://nymtech.net/docs](https://nymtech.net/docs) * `dev-portal` contains developer documentation hosted at [https://nymtech.net/developers](https://nymtech.net/developers) +* `operators` contains node setup and maintenance guides hosted at [https://nymtech.net/operators](https://nymtech.net/operators) Each directory contains a readme with more information about running and contributing to the projects. Each is built with [`mdbook`](https://rust-lang.github.io/mdBook/index.html) - use `mdbook serve` to build and serve them (defaults to `localhost:3000`). From 0f43521497f076e3427cb7ae6e7ccdde3e415350 Mon Sep 17 00:00:00 2001 From: mfahampshire Date: Fri, 18 Aug 2023 10:10:36 +0200 Subject: [PATCH 268/361] tweak --- documentation/staging.sh | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/documentation/staging.sh b/documentation/staging.sh index 64fec07e47..87fe4d5b58 100755 --- a/documentation/staging.sh +++ b/documentation/staging.sh @@ -13,6 +13,7 @@ do # little sanity checks echo $(pwd) && echo $(mdbook --version) && # clean old book + echo "cleaning old book" rm -rf ./book/ # build book mdbook build @@ -27,9 +28,9 @@ do # clean old dist/$i rm -rf ../../dist/docs/$i # move newly rendered book/ to dist - cp -r ./book/html/ ../../dist/docs/$i + rsync -r ./book/html/ ../../dist/docs/$i # sanity check - ls -a ../../dist/docs/$i/ + ls -laF ../../dist/docs/ # cd back to ../documentation/ cd ../ -done \ No newline at end of file +done From ee5b55fab682e0de719d9b7b06237ea59bec6781 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bogdan-=C8=98tefan=20Neac=C5=9Fu?= Date: Fri, 18 Aug 2023 14:14:13 +0300 Subject: [PATCH 269/361] Feature/ephemera (#3731) * Feature/ephemera compile (#3437) * Include ephemera node code in repo * Upgrade deps * Bump minor version of cosmwasm-std * Include ephemera in nym-api dep and downgrade rusqlite * Fix clippy and ephemera docs code * More clippy on ephemera --------- Co-authored-by: Andrus Salumets * Start ephemera components in nym-api (#3475) * Start ephemera components in nym-api * Pass nyxd client and use common metric structures * Swap url endpoint with contract for sending rewarding messages * Fix build after rebase * Perform ephemera rewards computation before normal nym-api ones * Remove contract mock from ephemera * Take raw rewards from network monitor * Remove ephemera old reward version * Use nym shutdown procedure in ephemera * Temporary fix for some warnings * Umock contract membership of ephemera (#3574) * Pass nyxd client to members provider * Basic ephemera contract * Add register peer tx * Add query all peers * Nyxd ephemera client * Add registration of ephemera peer * Replace epoch http api with actual contract * Merge ephemera config into nym-api config * Load cluster from contract * Guard nym-outfox out of cosmwasm builds (#3650) * Feature/fixes while testing (#3668) * Commit local peer before querying contract * Default to anyonline * Remove string from template * Fix avg computing * Use updated qa env * Fix clippy * Add unit tests for ephemera contract * Upload ephemera contract in CI * Add group check for peer signup * Peer registration unit test * Start ephemera only on monitoring * Remove old MixnodeToReward struct * Move all ephemera config to its file * Skip with serde ephemera config * Fix default value in args * Feature/add ephemera flag (#3727) * Replace unwrap with error handling * Add ephemera enable flag * Fix template * Add json schema to ephemera contract (#3735) * Update lock files * Update changelog --------- Co-authored-by: Andrus Salumets --- .../build-and-upload-binaries-ci.yml | 1 + CHANGELOG.md | 4 + Cargo.lock | 2844 ++++++++++++++++- Cargo.toml | 1 + clients/webassembly/Cargo.lock | 11 + .../client-libs/validator-client/Cargo.toml | 1 + .../contract_traits/ephemera_query_client.rs | 79 + .../ephemera_signing_client.rs | 86 + .../src/nyxd/contract_traits/mod.rs | 13 + .../validator-client/src/nyxd/mod.rs | 4 + .../validator-client/src/nyxd/traits/mod.rs | 0 common/config/src/lib.rs | 1 + .../ephemera/Cargo.toml | 16 + .../ephemera/src/lib.rs | 3 + .../ephemera/src/msg.rs | 33 + .../ephemera/src/peers.rs | 25 + .../ephemera/src/types.rs | 29 + common/network-defaults/src/lib.rs | 11 + common/network-defaults/src/mainnet.rs | 9 + common/network-defaults/src/var_names.rs | 1 + contracts/Cargo.lock | 30 + contracts/Cargo.toml | 1 + contracts/ephemera/.cargo/config | 5 + contracts/ephemera/Cargo.toml | 36 + contracts/ephemera/Makefile | 2 + contracts/ephemera/schema/nym-ephemera.json | 194 ++ contracts/ephemera/schema/raw/execute.json | 60 + .../ephemera/schema/raw/instantiate.json | 18 + contracts/ephemera/schema/raw/migrate.json | 6 + contracts/ephemera/schema/raw/query.json | 35 + .../schema/raw/response_to_get_peers.json | 67 + contracts/ephemera/src/bin/schema.rs | 14 + contracts/ephemera/src/contract.rs | 97 + contracts/ephemera/src/error.rs | 21 + contracts/ephemera/src/lib.rs | 8 + contracts/ephemera/src/peers/mod.rs | 6 + contracts/ephemera/src/peers/queries.rs | 164 + contracts/ephemera/src/peers/storage.rs | 11 + contracts/ephemera/src/peers/transactions.rs | 63 + contracts/ephemera/src/state/mod.rs | 15 + contracts/ephemera/src/support/mod.rs | 5 + .../ephemera/src/support/tests/fixtures.rs | 15 + .../ephemera/src/support/tests/helpers.rs | 74 + contracts/ephemera/src/support/tests/mod.rs | 5 + envs/local.env | 1 + envs/mainnet.env | 1 + envs/qa-qwerty.env | 1 + envs/qa.env | 3 +- envs/sandbox.env | 1 + ephemera/Cargo.toml | 65 + ephemera/README.md | 142 + ephemera/bin/main.rs | 12 + ephemera/migrations/V1__block.sql | 24 + ephemera/src/api/application.rs | 104 + ephemera/src/api/http/client.rs | 497 +++ ephemera/src/api/http/mod.rs | 88 + ephemera/src/api/http/query.rs | 182 ++ ephemera/src/api/http/submit.rs | 88 + ephemera/src/api/mod.rs | 309 ++ ephemera/src/api/types.rs | 659 ++++ ephemera/src/block/builder.rs | 74 + ephemera/src/block/manager.rs | 688 ++++ ephemera/src/block/message_pool.rs | 87 + ephemera/src/block/mod.rs | 26 + ephemera/src/block/producer.rs | 92 + ephemera/src/block/types/block.rs | 327 ++ ephemera/src/block/types/message.rs | 99 + ephemera/src/block/types/mod.rs | 2 + ephemera/src/broadcast/bracha/broadcast.rs | 320 ++ ephemera/src/broadcast/bracha/mod.rs | 2 + ephemera/src/broadcast/bracha/quorum.rs | 254 ++ ephemera/src/broadcast/group.rs | 249 ++ ephemera/src/broadcast/mod.rs | 191 ++ ephemera/src/broadcast/signing.rs | 150 + ephemera/src/cli/config.rs | 132 + ephemera/src/cli/crypto.rs | 15 + ephemera/src/cli/init.rs | 165 + ephemera/src/cli/mod.rs | 49 + ephemera/src/cli/peers.rs | 65 + ephemera/src/cli/run_node.rs | 140 + ephemera/src/config/mod.rs | 334 ++ ephemera/src/core/api_cmd.rs | 390 +++ ephemera/src/core/builder.rs | 413 +++ ephemera/src/core/ephemera.rs | 421 +++ ephemera/src/core/mod.rs | 4 + ephemera/src/core/shutdown.rs | 77 + ephemera/src/lib.rs | 118 + ephemera/src/logging/mod.rs | 19 + .../libp2p/behaviours/membership/behaviour.rs | 556 ++++ .../behaviours/membership/connections.rs | 88 + .../libp2p/behaviours/membership/handler.rs | 340 ++ .../libp2p/behaviours/membership/mod.rs | 190 ++ .../libp2p/behaviours/membership/protocol.rs | 100 + ephemera/src/network/libp2p/behaviours/mod.rs | 183 ++ .../libp2p/behaviours/request_response/mod.rs | 101 + .../src/network/libp2p/ephemera_sender.rs | 58 + ephemera/src/network/libp2p/mod.rs | 4 + ephemera/src/network/libp2p/network_sender.rs | 67 + ephemera/src/network/libp2p/swarm_network.rs | 604 ++++ ephemera/src/network/members/mod.rs | 252 ++ ephemera/src/network/mod.rs | 237 ++ ephemera/src/storage/mod.rs | 65 + ephemera/src/storage/rocksdb/mod.rs | 125 + ephemera/src/storage/rocksdb/query.rs | 118 + ephemera/src/storage/rocksdb/store.rs | 79 + ephemera/src/storage/sqlite/mod.rs | 107 + ephemera/src/storage/sqlite/query.rs | 167 + ephemera/src/storage/sqlite/store.rs | 71 + ephemera/src/utilities/codec/mod.rs | 102 + ephemera/src/utilities/codec/varint_async.rs | 100 + ephemera/src/utilities/codec/varint_bytes.rs | 85 + ephemera/src/utilities/crypto/ed25519.rs | 186 ++ ephemera/src/utilities/crypto/key_manager.rs | 14 + ephemera/src/utilities/crypto/keypair.rs | 158 + ephemera/src/utilities/crypto/mod.rs | 137 + ephemera/src/utilities/hash/mod.rs | 109 + ephemera/src/utilities/id/mod.rs | 94 + ephemera/src/utilities/merkle/mod.rs | 162 + ephemera/src/utilities/mod.rs | 6 + ephemera/src/utilities/time/mod.rs | 11 + ephemera/src/websocket/mod.rs | 1 + ephemera/src/websocket/ws_manager.rs | 138 + nym-api/Cargo.toml | 11 + .../contract/V1__contract.sql | 19 + .../rewardsnew/V1__metrics.sql | 23 + .../rewardsold/V1__metrics.sql | 6 + nym-api/src/ephemera/application.rs | 209 ++ nym-api/src/ephemera/client.rs | 12 + nym-api/src/ephemera/epoch.rs | 82 + nym-api/src/ephemera/error.rs | 15 + nym-api/src/ephemera/metrics/mod.rs | 1 + nym-api/src/ephemera/metrics/types.rs | 9 + nym-api/src/ephemera/mod.rs | 51 + nym-api/src/ephemera/peers/members.rs | 108 + nym-api/src/ephemera/peers/mod.rs | 78 + nym-api/src/ephemera/reward/aggregator.rs | 42 + nym-api/src/ephemera/reward/mod.rs | 263 ++ nym-api/src/epoch_operations/error.rs | 3 + nym-api/src/epoch_operations/helpers.rs | 3 +- nym-api/src/epoch_operations/mod.rs | 34 +- nym-api/src/epoch_operations/rewarding.rs | 14 +- nym-api/src/main.rs | 42 +- nym-api/src/support/cli/mod.rs | 22 + nym-api/src/support/config/mod.rs | 66 +- .../src/support/config/old_config_v1_1_21.rs | 1 + nym-api/src/support/config/template.rs | 8 + nym-api/src/support/nyxd/mod.rs | 43 +- nym-connect/desktop/Cargo.lock | 11 + nym-wallet/Cargo.lock | 11 + nym-wallet/nym-wallet-types/src/network/qa.rs | 3 + .../nym-wallet-types/src/network/sandbox.rs | 3 + 151 files changed, 17157 insertions(+), 55 deletions(-) create mode 100644 common/client-libs/validator-client/src/nyxd/contract_traits/ephemera_query_client.rs create mode 100644 common/client-libs/validator-client/src/nyxd/contract_traits/ephemera_signing_client.rs create mode 100644 common/client-libs/validator-client/src/nyxd/traits/mod.rs create mode 100644 common/cosmwasm-smart-contracts/ephemera/Cargo.toml create mode 100644 common/cosmwasm-smart-contracts/ephemera/src/lib.rs create mode 100644 common/cosmwasm-smart-contracts/ephemera/src/msg.rs create mode 100644 common/cosmwasm-smart-contracts/ephemera/src/peers.rs create mode 100644 common/cosmwasm-smart-contracts/ephemera/src/types.rs create mode 100644 contracts/ephemera/.cargo/config create mode 100644 contracts/ephemera/Cargo.toml create mode 100644 contracts/ephemera/Makefile create mode 100644 contracts/ephemera/schema/nym-ephemera.json create mode 100644 contracts/ephemera/schema/raw/execute.json create mode 100644 contracts/ephemera/schema/raw/instantiate.json create mode 100644 contracts/ephemera/schema/raw/migrate.json create mode 100644 contracts/ephemera/schema/raw/query.json create mode 100644 contracts/ephemera/schema/raw/response_to_get_peers.json create mode 100644 contracts/ephemera/src/bin/schema.rs create mode 100644 contracts/ephemera/src/contract.rs create mode 100644 contracts/ephemera/src/error.rs create mode 100644 contracts/ephemera/src/lib.rs create mode 100644 contracts/ephemera/src/peers/mod.rs create mode 100644 contracts/ephemera/src/peers/queries.rs create mode 100644 contracts/ephemera/src/peers/storage.rs create mode 100644 contracts/ephemera/src/peers/transactions.rs create mode 100644 contracts/ephemera/src/state/mod.rs create mode 100644 contracts/ephemera/src/support/mod.rs create mode 100644 contracts/ephemera/src/support/tests/fixtures.rs create mode 100644 contracts/ephemera/src/support/tests/helpers.rs create mode 100644 contracts/ephemera/src/support/tests/mod.rs create mode 100644 ephemera/Cargo.toml create mode 100644 ephemera/README.md create mode 100644 ephemera/bin/main.rs create mode 100644 ephemera/migrations/V1__block.sql create mode 100644 ephemera/src/api/application.rs create mode 100644 ephemera/src/api/http/client.rs create mode 100644 ephemera/src/api/http/mod.rs create mode 100644 ephemera/src/api/http/query.rs create mode 100644 ephemera/src/api/http/submit.rs create mode 100644 ephemera/src/api/mod.rs create mode 100644 ephemera/src/api/types.rs create mode 100644 ephemera/src/block/builder.rs create mode 100644 ephemera/src/block/manager.rs create mode 100644 ephemera/src/block/message_pool.rs create mode 100644 ephemera/src/block/mod.rs create mode 100644 ephemera/src/block/producer.rs create mode 100644 ephemera/src/block/types/block.rs create mode 100644 ephemera/src/block/types/message.rs create mode 100644 ephemera/src/block/types/mod.rs create mode 100644 ephemera/src/broadcast/bracha/broadcast.rs create mode 100644 ephemera/src/broadcast/bracha/mod.rs create mode 100644 ephemera/src/broadcast/bracha/quorum.rs create mode 100644 ephemera/src/broadcast/group.rs create mode 100644 ephemera/src/broadcast/mod.rs create mode 100644 ephemera/src/broadcast/signing.rs create mode 100644 ephemera/src/cli/config.rs create mode 100644 ephemera/src/cli/crypto.rs create mode 100644 ephemera/src/cli/init.rs create mode 100644 ephemera/src/cli/mod.rs create mode 100644 ephemera/src/cli/peers.rs create mode 100644 ephemera/src/cli/run_node.rs create mode 100644 ephemera/src/config/mod.rs create mode 100644 ephemera/src/core/api_cmd.rs create mode 100644 ephemera/src/core/builder.rs create mode 100644 ephemera/src/core/ephemera.rs create mode 100644 ephemera/src/core/mod.rs create mode 100644 ephemera/src/core/shutdown.rs create mode 100644 ephemera/src/lib.rs create mode 100644 ephemera/src/logging/mod.rs create mode 100644 ephemera/src/network/libp2p/behaviours/membership/behaviour.rs create mode 100644 ephemera/src/network/libp2p/behaviours/membership/connections.rs create mode 100644 ephemera/src/network/libp2p/behaviours/membership/handler.rs create mode 100644 ephemera/src/network/libp2p/behaviours/membership/mod.rs create mode 100644 ephemera/src/network/libp2p/behaviours/membership/protocol.rs create mode 100644 ephemera/src/network/libp2p/behaviours/mod.rs create mode 100644 ephemera/src/network/libp2p/behaviours/request_response/mod.rs create mode 100644 ephemera/src/network/libp2p/ephemera_sender.rs create mode 100644 ephemera/src/network/libp2p/mod.rs create mode 100644 ephemera/src/network/libp2p/network_sender.rs create mode 100644 ephemera/src/network/libp2p/swarm_network.rs create mode 100644 ephemera/src/network/members/mod.rs create mode 100644 ephemera/src/network/mod.rs create mode 100644 ephemera/src/storage/mod.rs create mode 100644 ephemera/src/storage/rocksdb/mod.rs create mode 100644 ephemera/src/storage/rocksdb/query.rs create mode 100644 ephemera/src/storage/rocksdb/store.rs create mode 100644 ephemera/src/storage/sqlite/mod.rs create mode 100644 ephemera/src/storage/sqlite/query.rs create mode 100644 ephemera/src/storage/sqlite/store.rs create mode 100644 ephemera/src/utilities/codec/mod.rs create mode 100644 ephemera/src/utilities/codec/varint_async.rs create mode 100644 ephemera/src/utilities/codec/varint_bytes.rs create mode 100644 ephemera/src/utilities/crypto/ed25519.rs create mode 100644 ephemera/src/utilities/crypto/key_manager.rs create mode 100644 ephemera/src/utilities/crypto/keypair.rs create mode 100644 ephemera/src/utilities/crypto/mod.rs create mode 100644 ephemera/src/utilities/hash/mod.rs create mode 100644 ephemera/src/utilities/id/mod.rs create mode 100644 ephemera/src/utilities/merkle/mod.rs create mode 100644 ephemera/src/utilities/mod.rs create mode 100644 ephemera/src/utilities/time/mod.rs create mode 100644 ephemera/src/websocket/mod.rs create mode 100644 ephemera/src/websocket/ws_manager.rs create mode 100644 nym-api/ephemera_migrations/contract/V1__contract.sql create mode 100644 nym-api/ephemera_migrations/rewardsnew/V1__metrics.sql create mode 100644 nym-api/ephemera_migrations/rewardsold/V1__metrics.sql create mode 100644 nym-api/src/ephemera/application.rs create mode 100644 nym-api/src/ephemera/client.rs create mode 100644 nym-api/src/ephemera/epoch.rs create mode 100644 nym-api/src/ephemera/error.rs create mode 100644 nym-api/src/ephemera/metrics/mod.rs create mode 100644 nym-api/src/ephemera/metrics/types.rs create mode 100644 nym-api/src/ephemera/mod.rs create mode 100644 nym-api/src/ephemera/peers/members.rs create mode 100644 nym-api/src/ephemera/peers/mod.rs create mode 100644 nym-api/src/ephemera/reward/aggregator.rs create mode 100644 nym-api/src/ephemera/reward/mod.rs diff --git a/.github/workflows/build-and-upload-binaries-ci.yml b/.github/workflows/build-and-upload-binaries-ci.yml index 5429baf2b4..849bd32a17 100644 --- a/.github/workflows/build-and-upload-binaries-ci.yml +++ b/.github/workflows/build-and-upload-binaries-ci.yml @@ -109,6 +109,7 @@ jobs: cp contracts/target/wasm32-unknown-unknown/release/cw4_group.wasm $OUTPUT_DIR cp contracts/target/wasm32-unknown-unknown/release/nym_service_provider_directory.wasm $OUTPUT_DIR cp contracts/target/wasm32-unknown-unknown/release/nym_name_service.wasm $OUTPUT_DIR + cp contracts/target/wasm32-unknown-unknown/release/nym_ephemera.wasm $OUTPUT_DIR - name: Deploy branch to CI www continue-on-error: true diff --git a/CHANGELOG.md b/CHANGELOG.md index 19988720df..c04cce1b0a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,10 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https:// ## [Unreleased] +- Add Ephemera functionality in nym-api ([#3731]) + +[#3731]: https://github.com/nymtech/nym/pull/3731 + ## [1.1.27] (2023-08-16) - fix serialisation of contract types ([#3752]) diff --git a/Cargo.lock b/Cargo.lock index bb62518ca4..f14848f51b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8,6 +8,187 @@ version = "0.11.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fe438c63458706e03479442743baae6c88256498e6431708f6dfc520a26515d3" +[[package]] +name = "actix-codec" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "617a8268e3537fe1d8c9ead925fca49ef6400927ee7bc26750e90ecee14ce4b8" +dependencies = [ + "bitflags 1.3.2", + "bytes", + "futures-core", + "futures-sink", + "memchr", + "pin-project-lite", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "actix-http" +version = "3.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2079246596c18b4a33e274ae10c0e50613f4d32a4198e09c7b93771013fed74" +dependencies = [ + "actix-codec", + "actix-rt", + "actix-service", + "actix-utils", + "ahash 0.8.3", + "base64 0.21.2", + "bitflags 1.3.2", + "brotli", + "bytes", + "bytestring", + "derive_more", + "encoding_rs", + "flate2", + "futures-core", + "h2", + "http", + "httparse", + "httpdate", + "itoa", + "language-tags", + "local-channel", + "mime", + "percent-encoding", + "pin-project-lite", + "rand 0.8.5", + "sha1", + "smallvec", + "tokio", + "tokio-util", + "tracing", + "zstd", +] + +[[package]] +name = "actix-macros" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "465a6172cf69b960917811022d8f29bc0b7fa1398bc4f78b3c466673db1213b6" +dependencies = [ + "quote", + "syn 1.0.109", +] + +[[package]] +name = "actix-router" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d66ff4d247d2b160861fa2866457e85706833527840e4133f8f49aa423a38799" +dependencies = [ + "bytestring", + "http", + "regex", + "serde", + "tracing", +] + +[[package]] +name = "actix-rt" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15265b6b8e2347670eb363c47fc8c75208b4a4994b27192f345fcbe707804f3e" +dependencies = [ + "futures-core", + "tokio", +] + +[[package]] +name = "actix-server" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e8613a75dd50cc45f473cee3c34d59ed677c0f7b44480ce3b8247d7dc519327" +dependencies = [ + "actix-rt", + "actix-service", + "actix-utils", + "futures-core", + "futures-util", + "mio", + "num_cpus", + "socket2 0.4.9", + "tokio", + "tracing", +] + +[[package]] +name = "actix-service" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b894941f818cfdc7ccc4b9e60fa7e53b5042a2e8567270f9147d5591893373a" +dependencies = [ + "futures-core", + "paste", + "pin-project-lite", +] + +[[package]] +name = "actix-utils" +version = "3.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88a1dcdff1466e3c2488e1cb5c36a71822750ad43839937f85d2f4d9f8b705d8" +dependencies = [ + "local-waker", + "pin-project-lite", +] + +[[package]] +name = "actix-web" +version = "4.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd3cb42f9566ab176e1ef0b8b3a896529062b4efc6be0123046095914c4c1c96" +dependencies = [ + "actix-codec", + "actix-http", + "actix-macros", + "actix-router", + "actix-rt", + "actix-server", + "actix-service", + "actix-utils", + "actix-web-codegen", + "ahash 0.7.6", + "bytes", + "bytestring", + "cfg-if", + "cookie 0.16.2", + "derive_more", + "encoding_rs", + "futures-core", + "futures-util", + "http", + "itoa", + "language-tags", + "log", + "mime", + "once_cell", + "pin-project-lite", + "regex", + "serde", + "serde_json", + "serde_urlencoded", + "smallvec", + "socket2 0.4.9", + "time 0.3.25", + "url", +] + +[[package]] +name = "actix-web-codegen" +version = "4.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2262160a7ae29e3415554a3f1fc04c764b1540c116aa524683208078b7a75bc9" +dependencies = [ + "actix-router", + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "addr2line" version = "0.20.0" @@ -23,6 +204,25 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" +[[package]] +name = "aead" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fc95d1bdb8e6666b2b217308eeeb09f2d6728d104be3e31916cc74d15420331" +dependencies = [ + "generic-array 0.14.7", +] + +[[package]] +name = "aead" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b613b8e1e3cf911a086f53f03bf286f52fd7a7258e4fa606f0ef220d39d8877" +dependencies = [ + "generic-array 0.14.7", + "rand_core 0.6.4", +] + [[package]] name = "aead" version = "0.5.2" @@ -33,6 +233,17 @@ dependencies = [ "generic-array 0.14.7", ] +[[package]] +name = "aes" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "884391ef1066acaa41e766ba8f596341b96e93ce34f9a43e7d24bf0a0eaf0561" +dependencies = [ + "aes-soft", + "aesni", + "cipher 0.2.5", +] + [[package]] name = "aes" version = "0.7.5" @@ -57,20 +268,54 @@ dependencies = [ "cpufeatures", ] +[[package]] +name = "aes-gcm" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df5f85a83a7d8b0442b6aa7b504b8212c1733da07b98aae43d4bc21b2cb3cdf6" +dependencies = [ + "aead 0.4.3", + "aes 0.7.5", + "cipher 0.3.0", + "ctr 0.8.0", + "ghash 0.4.4", + "subtle 2.4.1", +] + [[package]] name = "aes-gcm" version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "209b47e8954a928e1d72e86eca7000ebb6655fe1436d33eefc2201cad027e237" dependencies = [ - "aead", + "aead 0.5.2", "aes 0.8.3", "cipher 0.4.4", "ctr 0.9.2", - "ghash", + "ghash 0.5.0", "subtle 2.4.1", ] +[[package]] +name = "aes-soft" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be14c7498ea50828a38d0e24a765ed2effe92a705885b57d029cd67d45744072" +dependencies = [ + "cipher 0.2.5", + "opaque-debug 0.3.0", +] + +[[package]] +name = "aesni" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea2e11f5e94c2f7d386164cc2aa1f97823fed6f259e486940a71c174dd01b0ce" +dependencies = [ + "cipher 0.2.5", + "opaque-debug 0.3.0", +] + [[package]] name = "ahash" version = "0.7.6" @@ -89,6 +334,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2c99f64d1e06488f620f932677e24bc6e2897582980441ae90a671415bd7ec2f" dependencies = [ "cfg-if", + "getrandom 0.2.10", "once_cell", "version_check", ] @@ -102,6 +348,21 @@ dependencies = [ "memchr", ] +[[package]] +name = "alloc-no-stdlib" +version = "2.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc7bb162ec39d46ab1ca8c77bf72e890535becd1751bb45f64c597edb4c8c6b3" + +[[package]] +name = "alloc-stdlib" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94fb8275041c72129eb51b7d0322c29b8387a0386127718b096429201a5d6ece" +dependencies = [ + "alloc-no-stdlib", +] + [[package]] name = "allocator-api2" version = "0.2.16" @@ -201,6 +462,15 @@ name = "anyhow" version = "1.0.72" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3b13c32d80ecc7ab747b80c3784bce54ee8a7a0cc4fbda9bf4cda2cf6fe90854" +dependencies = [ + "backtrace", +] + +[[package]] +name = "arc-swap" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bddcadddf5e9015d310179a59bb28c4d4b9920ad0f11e8e14dbadf654890c9a6" [[package]] name = "argon2" @@ -214,6 +484,12 @@ dependencies = [ "password-hash", ] +[[package]] +name = "array-bytes" +version = "6.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b1c5a481ec30a5abd8dfbd94ab5cf1bb4e9a66be7f1b3b322f2f1170c200fd" + [[package]] name = "arrayref" version = "0.3.7" @@ -226,6 +502,79 @@ version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711" +[[package]] +name = "asn1-rs" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30ff05a702273012438132f449575dbc804e27b2f3cbe3069aa237d26c98fa33" +dependencies = [ + "asn1-rs-derive 0.1.0", + "asn1-rs-impl", + "displaydoc", + "nom", + "num-traits", + "rusticata-macros", + "thiserror", + "time 0.3.25", +] + +[[package]] +name = "asn1-rs" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f6fd5ddaf0351dff5b8da21b2fb4ff8e08ddd02857f0bf69c47639106c0fff0" +dependencies = [ + "asn1-rs-derive 0.4.0", + "asn1-rs-impl", + "displaydoc", + "nom", + "num-traits", + "rusticata-macros", + "thiserror", + "time 0.3.25", +] + +[[package]] +name = "asn1-rs-derive" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db8b7511298d5b7784b40b092d9e9dcd3a627a5707e4b5e507931ab0d44eeebf" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", + "synstructure", +] + +[[package]] +name = "asn1-rs-derive" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "726535892e8eae7e70657b4c8ea93d26b8553afb1ce617caee529ef96d7dee6c" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", + "synstructure", +] + +[[package]] +name = "asn1-rs-impl" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2777730b2039ac0f95f093556e61b6d26cebed5393ca6f152717777cec3a42ed" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "assert_matches" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b34d609dfbaf33d6889b2b7106d3ca345eacad44200913df5ba02bfd31d2ba9" + [[package]] name = "async-channel" version = "1.9.0" @@ -247,6 +596,35 @@ dependencies = [ "tokio", ] +[[package]] +name = "async-io" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fc5b45d93ef0529756f812ca52e44c221b35341892d3dcc34132ac02f3dd2af" +dependencies = [ + "async-lock", + "autocfg 1.1.0", + "cfg-if", + "concurrent-queue", + "futures-lite", + "log", + "parking", + "polling", + "rustix 0.37.23", + "slab", + "socket2 0.4.9", + "waker-fn", +] + +[[package]] +name = "async-lock" +version = "2.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa24f727524730b077666307f2734b4a1a1c57acb79193127dcc8914d5242dd7" +dependencies = [ + "event-listener", +] + [[package]] name = "async-stream" version = "0.3.5" @@ -280,6 +658,19 @@ dependencies = [ "syn 2.0.28", ] +[[package]] +name = "asynchronous-codec" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06a0daa378f5fd10634e44b0a29b2a87b890657658e072a30d6f26e57ddee182" +dependencies = [ + "bytes", + "futures-sink", + "futures-util", + "memchr", + "pin-project-lite", +] + [[package]] name = "atoi" version = "0.4.0" @@ -304,6 +695,12 @@ version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c59bdb34bc650a32731b31bd8f0829cc15d24a708ee31559e0bb34f2bc320cba" +[[package]] +name = "atomic-waker" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1181e1e0d1fce796a03db1ae795d67167da795f9cf4a39c37589e85ef57f26d3" + [[package]] name = "atty" version = "0.2.14" @@ -390,6 +787,12 @@ dependencies = [ "rustc-demangle", ] +[[package]] +name = "base-x" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cbbc9d0964165b47557570cce6c952866c2678457aca742aafc9fb771d30270" + [[package]] name = "base16ct" version = "0.1.1" @@ -435,6 +838,26 @@ dependencies = [ "serde", ] +[[package]] +name = "bindgen" +version = "0.64.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4243e6031260db77ede97ad86c27e501d646a27ab57b59a574f725d98ab1fb4" +dependencies = [ + "bitflags 1.3.2", + "cexpr", + "clang-sys", + "lazy_static", + "lazycell", + "peeking_take_while", + "proc-macro2", + "quote", + "regex", + "rustc-hash", + "shlex", + "syn 1.0.109", +] + [[package]] name = "bip32" version = "0.5.1" @@ -550,6 +973,22 @@ dependencies = [ "generic-array 0.14.7", ] +[[package]] +name = "block-modes" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57a0e8073e8baa88212fb5823574c02ebccb395136ba9a164ab89379ec6072f0" +dependencies = [ + "block-padding", + "cipher 0.2.5", +] + +[[package]] +name = "block-padding" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d696c370c750c948ada61c69a0ee2cbbb9c50b1019ddb86d9317157a99c2cae" + [[package]] name = "bls12_381" version = "0.5.0" @@ -584,6 +1023,27 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "845141a4fade3f790628b7daaaa298a25b204fb28907eb54febe5142db6ce653" +[[package]] +name = "brotli" +version = "3.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1a0b1dbcc8ae29329621f8d4f0d835787c1c38bb1401979b49d13b0b305ff68" +dependencies = [ + "alloc-no-stdlib", + "alloc-stdlib", + "brotli-decompressor", +] + +[[package]] +name = "brotli-decompressor" +version = "2.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b6561fd3f895a11e8f72af2cb7d22e08366bebc2b6b57f7744c4bda27034744" +dependencies = [ + "alloc-no-stdlib", + "alloc-stdlib", +] + [[package]] name = "bs58" version = "0.4.0" @@ -626,6 +1086,26 @@ dependencies = [ "serde", ] +[[package]] +name = "bytestring" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "238e4886760d98c4f899360c834fa93e62cf7f721ac3c2da375cbdf4b8679aae" +dependencies = [ + "bytes", +] + +[[package]] +name = "bzip2-sys" +version = "0.1.11+1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "736a955f3fa7875102d57c82b8cac37ec45224a07fd32d58f9f7a186b6cd4cdc" +dependencies = [ + "cc", + "libc", + "pkg-config", +] + [[package]] name = "cast" version = "0.3.0" @@ -648,12 +1128,32 @@ dependencies = [ "libc", ] +[[package]] +name = "ccm" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5aca1a8fbc20b50ac9673ff014abfb2b5f4085ee1a850d408f14a159c5853ac7" +dependencies = [ + "aead 0.3.2", + "cipher 0.2.5", + "subtle 2.4.1", +] + [[package]] name = "cesu8" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" +[[package]] +name = "cexpr" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" +dependencies = [ + "nom", +] + [[package]] name = "cfg-if" version = "1.0.0" @@ -670,6 +1170,18 @@ dependencies = [ "keystream", ] +[[package]] +name = "chacha20" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c80e5460aa66fe3b91d40bcbdab953a597b60053e34d684ac6903f863b680a6" +dependencies = [ + "cfg-if", + "cipher 0.3.0", + "cpufeatures", + "zeroize", +] + [[package]] name = "chacha20" version = "0.9.1" @@ -681,16 +1193,29 @@ dependencies = [ "cpufeatures", ] +[[package]] +name = "chacha20poly1305" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a18446b09be63d457bbec447509e85f662f32952b035ce892290396bc0b0cff5" +dependencies = [ + "aead 0.4.3", + "chacha20 0.8.2", + "cipher 0.3.0", + "poly1305 0.7.2", + "zeroize", +] + [[package]] name = "chacha20poly1305" version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "10cd79432192d1c0f4e1a0fef9527696cc039165d729fb41b3f4f4f354c2dc35" dependencies = [ - "aead", - "chacha20", + "aead 0.5.2", + "chacha20 0.9.1", "cipher 0.4.4", - "poly1305", + "poly1305 0.8.0", "zeroize", ] @@ -737,6 +1262,15 @@ dependencies = [ "half", ] +[[package]] +name = "cipher" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12f8e7987cbd042a63249497f41aed09f8e65add917ea6566effbc56578d6801" +dependencies = [ + "generic-array 0.14.7", +] + [[package]] name = "cipher" version = "0.3.0" @@ -757,6 +1291,17 @@ dependencies = [ "zeroize", ] +[[package]] +name = "clang-sys" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c688fc74432808e3eb684cae8830a86be1d66a2bd58e1f248ed0960a590baf6f" +dependencies = [ + "glob", + "libc", + "libloading", +] + [[package]] name = "clap" version = "3.2.25" @@ -896,6 +1441,20 @@ dependencies = [ "crossbeam-utils", ] +[[package]] +name = "config" +version = "0.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d379af7f68bfc21714c6c7dea883544201741d2ce8274bb12fa54f89507f52a7" +dependencies = [ + "async-trait", + "lazy_static", + "nom", + "pathdiff", + "serde", + "toml 0.5.11", +] + [[package]] name = "console-api" version = "0.5.0" @@ -944,6 +1503,23 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f7144d30dcf0fafbce74250a3963025d8d52177934239851c917d29f1df280c2" +[[package]] +name = "convert_case" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" + +[[package]] +name = "cookie" +version = "0.16.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e859cd57d0710d9e06c381b550c06e76992472a8c6d527aecd2fc673dcc231fb" +dependencies = [ + "percent-encoding", + "time 0.3.25", + "version_check", +] + [[package]] name = "cookie" version = "0.17.0" @@ -971,6 +1547,15 @@ version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa" +[[package]] +name = "core2" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b49ba7ef1ad6107f8824dbe97de947cbaac53c44e7f9756a1fba0d37c1eec505" +dependencies = [ + "memchr", +] + [[package]] name = "cosmos-sdk-proto" version = "0.19.0" @@ -1191,7 +1776,7 @@ dependencies = [ "autocfg 1.1.0", "cfg-if", "crossbeam-utils", - "memoffset", + "memoffset 0.9.0", "scopeguard", ] @@ -1239,6 +1824,12 @@ dependencies = [ "winapi", ] +[[package]] +name = "crunchy" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" + [[package]] name = "crypto-bigint" version = "0.4.9" @@ -1385,6 +1976,20 @@ dependencies = [ "zeroize", ] +[[package]] +name = "curve25519-dalek" +version = "4.0.0-rc.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d4ba9852b42210c7538b75484f9daa0655e9a3ac04f693747bb0f02cf3cfe16" +dependencies = [ + "cfg-if", + "fiat-crypto", + "packed_simd_2", + "platforms", + "subtle 2.4.1", + "zeroize", +] + [[package]] name = "curve25519-dalek-ng" version = "4.1.1" @@ -1500,8 +2105,18 @@ version = "0.13.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a01d95850c592940db9b8194bc39f4bc0e89dee5c4265e4b1807c34a9aba453c" dependencies = [ - "darling_core", - "darling_macro", + "darling_core 0.13.4", + "darling_macro 0.13.4", +] + +[[package]] +name = "darling" +version = "0.14.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b750cb3417fd1b327431a470f388520309479ab0bf5e323505daf0290cd3850" +dependencies = [ + "darling_core 0.14.4", + "darling_macro 0.14.4", ] [[package]] @@ -1518,13 +2133,38 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "darling_core" +version = "0.14.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "109c1ca6e6b7f82cc233a97004ea8ed7ca123a9af07a8230878fcfda9b158bf0" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn 1.0.109", +] + [[package]] name = "darling_macro" version = "0.13.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c972679f83bdf9c42bd905396b6c3588a843a17f0f16dfcfa3e2c5d57441835" dependencies = [ - "darling_core", + "darling_core 0.13.4", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "darling_macro" +version = "0.14.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4aab4dbc9f7611d8b55048a3a16d2d010c2c8334e46304b40ac1cc14bf3b48e" +dependencies = [ + "darling_core 0.14.4", "quote", "syn 1.0.109", ] @@ -1552,6 +2192,32 @@ dependencies = [ "parking_lot_core 0.9.8", ] +[[package]] +name = "data-encoding" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2e66c9d817f1720209181c316d28635c050fa304f9c79e47a520882661b7308" + +[[package]] +name = "data-encoding-macro" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c904b33cc60130e1aeea4956ab803d08a3f4a0ca82d64ed757afac3891f2bb99" +dependencies = [ + "data-encoding", + "data-encoding-macro-internal", +] + +[[package]] +name = "data-encoding-macro-internal" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fdf3fce3ce863539ec1d7fd1b6dcc3c645663376b43ed376bbf887733e4f772" +dependencies = [ + "data-encoding", + "syn 1.0.109", +] + [[package]] name = "der" version = "0.6.1" @@ -1559,6 +2225,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f1a467a65c5e759bce6e65eaf91cc29f466cdc57cb65777bd646872a8a1fd4de" dependencies = [ "const-oid", + "pem-rfc7468", "zeroize", ] @@ -1572,6 +2239,34 @@ dependencies = [ "zeroize", ] +[[package]] +name = "der-parser" +version = "7.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe398ac75057914d7d07307bf67dc7f3f574a26783b4fc7805a20ffa9f506e82" +dependencies = [ + "asn1-rs 0.3.1", + "displaydoc", + "nom", + "num-bigint", + "num-traits", + "rusticata-macros", +] + +[[package]] +name = "der-parser" +version = "8.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbd676fbbab537128ef0278adb5576cf363cff6aa22a7b24effe97347cfab61e" +dependencies = [ + "asn1-rs 0.5.2", + "displaydoc", + "nom", + "num-bigint", + "num-traits", + "rusticata-macros", +] + [[package]] name = "deranged" version = "0.3.7" @@ -1592,6 +2287,50 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "derive_builder" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d07adf7be193b71cc36b193d0f5fe60b918a3a9db4dad0449f57bcfd519704a3" +dependencies = [ + "derive_builder_macro", +] + +[[package]] +name = "derive_builder_core" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f91d4cfa921f1c05904dc3c57b4a32c38aed3340cce209f3a6fd1478babafc4" +dependencies = [ + "darling 0.14.4", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "derive_builder_macro" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f0314b72bed045f3a68671b3c86328386762c93f82d98c65c3cb5e5f573dd68" +dependencies = [ + "derive_builder_core", + "syn 1.0.109", +] + +[[package]] +name = "derive_more" +version = "0.99.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fb810d30a7c1953f91334de7244731fc3f3c10d7fe163338a35b9f640960321" +dependencies = [ + "convert_case", + "proc-macro2", + "quote", + "rustc_version 0.4.0", + "syn 1.0.109", +] + [[package]] name = "devise" version = "0.4.1" @@ -1696,6 +2435,17 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "displaydoc" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "487585f4d0c6655fe74905e2504d8ad6908e4db67f744eb140876906c2f3175d" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.28", +] + [[package]] name = "doc-comment" version = "0.3.3" @@ -1714,6 +2464,12 @@ version = "0.15.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b" +[[package]] +name = "dtoa" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65d09067bfacaa79114679b279d7f5885b53295b1e2cfb4e79c8e4bd3d633169" + [[package]] name = "dyn-clone" version = "1.0.12" @@ -1785,7 +2541,7 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c762bae6dcaf24c4c84667b8579785430908723d5c889f469d76a41d59cc7a9d" dependencies = [ - "curve25519-dalek", + "curve25519-dalek 3.2.0", "ed25519 1.5.3", "rand 0.7.3", "serde", @@ -1800,7 +2556,7 @@ version = "3.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7c24f403d068ad0b359e577a77f92392118be3f3c927538f2bb544a5ecd828c6" dependencies = [ - "curve25519-dalek", + "curve25519-dalek 3.2.0", "hashbrown 0.12.3", "hex", "rand_core 0.6.4", @@ -1828,6 +2584,8 @@ dependencies = [ "ff 0.12.1", "generic-array 0.14.7", "group 0.12.1", + "hkdf 0.12.3", + "pem-rfc7468", "pkcs8 0.9.0", "rand_core 0.6.4", "sec1 0.3.0", @@ -1863,6 +2621,18 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "enum-as-inner" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9720bba047d567ffc8a3cba48bf19126600e249ab7f128e9233e6376976a116" +dependencies = [ + "heck 0.4.1", + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "enum-iterator" version = "1.1.3" @@ -1906,6 +2676,56 @@ dependencies = [ "regex", ] +[[package]] +name = "ephemera" +version = "0.1.0" +dependencies = [ + "actix-web", + "anyhow", + "array-bytes", + "assert_matches", + "async-trait", + "asynchronous-codec", + "blake2 0.10.6", + "bs58 0.4.0", + "bytes", + "cfg-if", + "chrono", + "clap 4.3.21", + "config", + "digest 0.10.7", + "dirs 5.0.1", + "enum-as-inner", + "futures", + "futures-util", + "lazy_static", + "libp2p", + "libp2p-identity", + "log", + "lru", + "nym-config", + "nym-ephemera-common", + "nym-task", + "pretty_env_logger", + "rand 0.8.5", + "refinery", + "reqwest", + "rocksdb", + "rusqlite", + "serde", + "serde_derive", + "serde_json", + "thiserror", + "tokio", + "tokio-tungstenite 0.18.0", + "tokio-util", + "toml 0.7.6", + "unsigned-varint", + "utoipa", + "utoipa-swagger-ui", + "uuid 1.4.1", +] + [[package]] name = "equivalent" version = "1.0.1" @@ -2014,6 +2834,18 @@ dependencies = [ "once_cell", ] +[[package]] +name = "fallible-iterator" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4443176a9f2c162692bd3d352d745ef9413eec5782a80d8fd6f8a1ac692a07f7" + +[[package]] +name = "fallible-streaming-iterator" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a" + [[package]] name = "fastrand" version = "1.9.0" @@ -2069,6 +2901,12 @@ dependencies = [ "subtle 2.4.1", ] +[[package]] +name = "fiat-crypto" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e825f6987101665dea6ec934c09ec6d721de7bc1bf92248e1d5810c8cd636b77" + [[package]] name = "figment" version = "0.10.10" @@ -2224,6 +3062,7 @@ dependencies = [ "futures-core", "futures-task", "futures-util", + "num_cpus", ] [[package]] @@ -2269,6 +3108,17 @@ dependencies = [ "syn 2.0.28", ] +[[package]] +name = "futures-rustls" +version = "0.22.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2411eed028cdf8c8034eaf21f9915f956b6c3abec4d4c7949ee67f0721127bd" +dependencies = [ + "futures-io", + "rustls 0.20.8", + "webpki 0.22.0", +] + [[package]] name = "futures-sink" version = "0.3.28" @@ -2281,6 +3131,12 @@ version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "76d3d132be6c0e6aa1534069c705a74a5997a356c0dc2f86a47765e5617c5b65" +[[package]] +name = "futures-timer" +version = "3.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e64b03909df88034c26dc1547e8970b91f98bdb65165d6a4e9110d94263dbb2c" + [[package]] name = "futures-util" version = "0.3.28" @@ -2315,7 +3171,7 @@ dependencies = [ "libc", "log", "rustversion", - "windows", + "windows 0.48.0", ] [[package]] @@ -2377,6 +3233,16 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "ghash" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1583cc1656d7839fd3732b80cf4f38850336cdb9b8ded1cd399ca62958de3c99" +dependencies = [ + "opaque-debug 0.3.0", + "polyval 0.5.3", +] + [[package]] name = "ghash" version = "0.5.0" @@ -2384,7 +3250,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d930750de5717d2dd0b8c0d42c076c0e884c81a73e6cab859bbd2339c71e3e40" dependencies = [ "opaque-debug 0.3.0", - "polyval", + "polyval 0.6.1", ] [[package]] @@ -2538,6 +3404,15 @@ dependencies = [ "ahash 0.7.6", ] +[[package]] +name = "hashbrown" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e" +dependencies = [ + "ahash 0.8.3", +] + [[package]] name = "hashbrown" version = "0.14.0" @@ -2649,6 +3524,12 @@ version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7ebdb29d2ea9ed0083cd8cece49bbd968021bd99b0849edb4a9a7ee0fdf6a4e0" +[[package]] +name = "hex_fmt" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b07f60793ff0a4d9cef0f18e63b5357e06209987153a64648c972c1e5aff336f" + [[package]] name = "hidapi" version = "1.5.0" @@ -2699,6 +3580,17 @@ dependencies = [ "digest 0.10.7", ] +[[package]] +name = "hostname" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c731c3e10504cc8ed35cfe2f1db4c9274c3d35fa486e3b31df46f068ef3e867" +dependencies = [ + "libc", + "match_cfg", + "winapi", +] + [[package]] name = "http" version = "0.2.9" @@ -2855,7 +3747,7 @@ dependencies = [ "iana-time-zone-haiku", "js-sys", "wasm-bindgen", - "windows", + "windows 0.48.0", ] [[package]] @@ -2894,6 +3786,35 @@ dependencies = [ "unicode-normalization", ] +[[package]] +name = "if-addrs" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cbc0fa01ffc752e9dbc72818cdb072cd028b86be5e09dd04c5a643704fe101a9" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "if-watch" +version = "3.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9465340214b296cd17a0009acdb890d6160010b8adf8f78a00d0d7ab270f79f" +dependencies = [ + "async-io", + "core-foundation", + "fnv", + "futures", + "if-addrs", + "ipnet", + "log", + "rtnetlink", + "system-configuration", + "tokio", + "windows 0.34.0", +] + [[package]] name = "indenter" version = "0.3.3" @@ -2994,6 +3915,25 @@ version = "3.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8bb03732005da905c88227371639bf1ad885cc712789c011c31c5fb3ab3ccf02" +[[package]] +name = "interceptor" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e8a11ae2da61704edada656798b61c94b35ecac2c58eb955156987d5e6be90b" +dependencies = [ + "async-trait", + "bytes", + "log", + "rand 0.8.5", + "rtcp", + "rtp", + "thiserror", + "tokio", + "waitgroup", + "webrtc-srtp", + "webrtc-util", +] + [[package]] name = "inventory" version = "0.1.11" @@ -3016,6 +3956,29 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "io-lifetimes" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c66c74d2ae7e79a5a8f7ac924adbe38ee42a859c6539ad869eb51f0b52dc220" +dependencies = [ + "hermit-abi 0.3.2", + "libc", + "windows-sys 0.48.0", +] + +[[package]] +name = "ipconfig" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd302af1b90f2463a98fa5ad469fc212c8e3175a41c3068601bfa2727591c5be" +dependencies = [ + "socket2 0.4.9", + "widestring", + "winapi", + "winreg", +] + [[package]] name = "ipnet" version = "2.8.0" @@ -3047,7 +4010,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cb0889898416213fab133e1d33a0e5858a48177452750691bde3666d0fdbaf8b" dependencies = [ "hermit-abi 0.3.2", - "rustix", + "rustix 0.38.8", "windows-sys 0.48.0", ] @@ -3193,12 +4156,24 @@ dependencies = [ "libc", ] +[[package]] +name = "language-tags" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4345964bb142484797b161f473a503a434de77149dd8c7427788c6e13379388" + [[package]] name = "lazy_static" version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" +[[package]] +name = "lazycell" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" + [[package]] name = "ledger" version = "0.1.0" @@ -3265,12 +4240,429 @@ dependencies = [ "pkg-config", ] +[[package]] +name = "libloading" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b67380fd3b2fbe7527a606e18729d21c6f3951633d0500574c4dc22d2d638b9f" +dependencies = [ + "cfg-if", + "winapi", +] + +[[package]] +name = "libm" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fc7aa29613bd6a620df431842069224d8bc9011086b1db4c0e0cd47fa03ec9a" + [[package]] name = "libm" version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f7012b1bbb0719e1097c47611d3898568c546d597c2e74d66f6087edd5233ff4" +[[package]] +name = "libp2p" +version = "0.51.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f210d259724eae82005b5c48078619b7745edb7b76de370b03f8ba59ea103097" +dependencies = [ + "bytes", + "futures", + "futures-timer", + "getrandom 0.2.10", + "instant", + "libp2p-allow-block-list", + "libp2p-connection-limits", + "libp2p-core", + "libp2p-dns", + "libp2p-gossipsub", + "libp2p-identity", + "libp2p-kad", + "libp2p-mdns", + "libp2p-metrics", + "libp2p-noise", + "libp2p-quic", + "libp2p-request-response", + "libp2p-swarm", + "libp2p-tcp", + "libp2p-webrtc", + "libp2p-yamux", + "multiaddr", + "pin-project", +] + +[[package]] +name = "libp2p-allow-block-list" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "510daa05efbc25184458db837f6f9a5143888f1caa742426d92e1833ddd38a50" +dependencies = [ + "libp2p-core", + "libp2p-identity", + "libp2p-swarm", + "void", +] + +[[package]] +name = "libp2p-connection-limits" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4caa33f1d26ed664c4fe2cca81a08c8e07d4c1c04f2f4ac7655c2dd85467fda0" +dependencies = [ + "libp2p-core", + "libp2p-identity", + "libp2p-swarm", + "void", +] + +[[package]] +name = "libp2p-core" +version = "0.39.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c1df63c0b582aa434fb09b2d86897fa2b419ffeccf934b36f87fcedc8e835c2" +dependencies = [ + "either", + "fnv", + "futures", + "futures-timer", + "instant", + "libp2p-identity", + "log", + "multiaddr", + "multihash", + "multistream-select", + "once_cell", + "parking_lot 0.12.1", + "pin-project", + "quick-protobuf", + "rand 0.8.5", + "rw-stream-sink", + "serde", + "smallvec", + "thiserror", + "unsigned-varint", + "void", +] + +[[package]] +name = "libp2p-dns" +version = "0.39.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "146ff7034daae62077c415c2376b8057368042df6ab95f5432ad5e88568b1554" +dependencies = [ + "futures", + "libp2p-core", + "log", + "parking_lot 0.12.1", + "smallvec", + "trust-dns-resolver", +] + +[[package]] +name = "libp2p-gossipsub" +version = "0.44.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70b34b6da8165c0bde35c82db8efda39b824776537e73973549e76cadb3a77c5" +dependencies = [ + "asynchronous-codec", + "base64 0.21.2", + "byteorder", + "bytes", + "either", + "fnv", + "futures", + "hex_fmt", + "instant", + "libp2p-core", + "libp2p-identity", + "libp2p-swarm", + "log", + "prometheus-client", + "quick-protobuf", + "quick-protobuf-codec", + "rand 0.8.5", + "regex", + "serde", + "sha2 0.10.7", + "smallvec", + "thiserror", + "unsigned-varint", + "void", + "wasm-timer", +] + +[[package]] +name = "libp2p-identity" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e2d584751cecb2aabaa56106be6be91338a60a0f4e420cf2af639204f596fc1" +dependencies = [ + "bs58 0.4.0", + "ed25519-dalek", + "log", + "multiaddr", + "multihash", + "quick-protobuf", + "rand 0.8.5", + "serde", + "sha2 0.10.7", + "thiserror", + "zeroize", +] + +[[package]] +name = "libp2p-kad" +version = "0.43.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39d5ef876a2b2323d63c258e63c2f8e36f205fe5a11f0b3095d59635650790ff" +dependencies = [ + "arrayvec", + "asynchronous-codec", + "bytes", + "either", + "fnv", + "futures", + "futures-timer", + "instant", + "libp2p-core", + "libp2p-identity", + "libp2p-swarm", + "log", + "quick-protobuf", + "rand 0.8.5", + "serde", + "sha2 0.10.7", + "smallvec", + "thiserror", + "uint", + "unsigned-varint", + "void", +] + +[[package]] +name = "libp2p-mdns" +version = "0.43.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19983e1f949f979a928f2c603de1cf180cc0dc23e4ac93a62651ccb18341460b" +dependencies = [ + "data-encoding", + "futures", + "if-watch", + "libp2p-core", + "libp2p-identity", + "libp2p-swarm", + "log", + "rand 0.8.5", + "smallvec", + "socket2 0.4.9", + "tokio", + "trust-dns-proto", + "void", +] + +[[package]] +name = "libp2p-metrics" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a42ec91e227d7d0dafa4ce88b333cdf5f277253873ab087555c92798db2ddd46" +dependencies = [ + "libp2p-core", + "libp2p-gossipsub", + "libp2p-kad", + "libp2p-swarm", + "prometheus-client", +] + +[[package]] +name = "libp2p-noise" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c3673da89d29936bc6435bafc638e2f184180d554ce844db65915113f86ec5e" +dependencies = [ + "bytes", + "curve25519-dalek 3.2.0", + "futures", + "libp2p-core", + "libp2p-identity", + "log", + "once_cell", + "quick-protobuf", + "rand 0.8.5", + "sha2 0.10.7", + "snow", + "static_assertions", + "thiserror", + "x25519-dalek 1.1.1", + "zeroize", +] + +[[package]] +name = "libp2p-quic" +version = "0.7.0-alpha.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6b26abd81cd2398382a1edfe739b539775be8a90fa6914f39b2ab49571ec735" +dependencies = [ + "bytes", + "futures", + "futures-timer", + "if-watch", + "libp2p-core", + "libp2p-identity", + "libp2p-tls", + "log", + "parking_lot 0.12.1", + "quinn-proto", + "rand 0.8.5", + "rustls 0.20.8", + "thiserror", + "tokio", +] + +[[package]] +name = "libp2p-request-response" +version = "0.24.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ffdb374267d42dc5ed5bc53f6e601d4a64ac5964779c6e40bb9e4f14c1e30d5" +dependencies = [ + "async-trait", + "futures", + "instant", + "libp2p-core", + "libp2p-identity", + "libp2p-swarm", + "rand 0.8.5", + "smallvec", +] + +[[package]] +name = "libp2p-swarm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "903b3d592d7694e56204d211f29d31bc004be99386644ba8731fc3e3ef27b296" +dependencies = [ + "either", + "fnv", + "futures", + "futures-timer", + "instant", + "libp2p-core", + "libp2p-identity", + "libp2p-swarm-derive", + "log", + "rand 0.8.5", + "smallvec", + "tokio", + "void", +] + +[[package]] +name = "libp2p-swarm-derive" +version = "0.32.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fba456131824ab6acd4c7bf61e9c0f0a3014b5fc9868ccb8e10d344594cdc4f" +dependencies = [ + "heck 0.4.1", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "libp2p-tcp" +version = "0.39.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33d33698596d7722d85d3ab0c86c2c322254fce1241e91208e3679b4eb3026cf" +dependencies = [ + "futures", + "futures-timer", + "if-watch", + "libc", + "libp2p-core", + "log", + "socket2 0.4.9", + "tokio", +] + +[[package]] +name = "libp2p-tls" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff08d13d0dc66e5e9ba6279c1de417b84fa0d0adc3b03e5732928c180ec02781" +dependencies = [ + "futures", + "futures-rustls", + "libp2p-core", + "libp2p-identity", + "rcgen 0.10.0", + "ring", + "rustls 0.20.8", + "thiserror", + "webpki 0.22.0", + "x509-parser 0.14.0", + "yasna", +] + +[[package]] +name = "libp2p-webrtc" +version = "0.4.0-alpha.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dba48592edbc2f60b4bc7c10d65445b0c3964c07df26fdf493b6880d33be36f8" +dependencies = [ + "async-trait", + "asynchronous-codec", + "bytes", + "futures", + "futures-timer", + "hex", + "if-watch", + "libp2p-core", + "libp2p-identity", + "libp2p-noise", + "log", + "multihash", + "quick-protobuf", + "quick-protobuf-codec", + "rand 0.8.5", + "rcgen 0.9.3", + "serde", + "stun", + "thiserror", + "tinytemplate", + "tokio", + "tokio-util", + "webrtc", +] + +[[package]] +name = "libp2p-yamux" +version = "0.43.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4dcd21d950662700a385d4c6d68e2f5f54d778e97068cdd718522222ef513bda" +dependencies = [ + "futures", + "libp2p-core", + "log", + "thiserror", + "yamux", +] + +[[package]] +name = "librocksdb-sys" +version = "0.10.0+7.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fe4d5874f5ff2bc616e55e8c6086d478fcda13faf9495768a4aa1c22042d30b" +dependencies = [ + "bindgen", + "bzip2-sys", + "cc", + "glob", + "libc", + "libz-sys", + "lz4-sys", + "zstd-sys", +] + [[package]] name = "libsqlite3-sys" version = "0.24.2" @@ -3294,6 +4686,18 @@ dependencies = [ "vcpkg", ] +[[package]] +name = "linked-hash-map" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" + +[[package]] +name = "linux-raw-sys" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519" + [[package]] name = "linux-raw-sys" version = "0.4.5" @@ -3312,6 +4716,24 @@ dependencies = [ "keystream", ] +[[package]] +name = "local-channel" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f303ec0e94c6c54447f84f3b0ef7af769858a9c4ef56ef2a986d3dcd4c3fc9c" +dependencies = [ + "futures-core", + "futures-sink", + "futures-util", + "local-waker", +] + +[[package]] +name = "local-waker" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e34f76eb3611940e0e7d53a9aaa4e6a3151f69541a282fd0dad5571420c53ff1" + [[package]] name = "lock_api" version = "0.4.10" @@ -3343,6 +4765,34 @@ dependencies = [ "tracing-subscriber", ] +[[package]] +name = "lru" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03f1160296536f10c833a82dca22267d5486734230d47bf00bf435885814ba1e" +dependencies = [ + "hashbrown 0.13.2", +] + +[[package]] +name = "lru-cache" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31e24f1ad8321ca0e8a1e0ac13f23cb668e6f5466c2c57319f6a5cf1cc8e3b1c" +dependencies = [ + "linked-hash-map", +] + +[[package]] +name = "lz4-sys" +version = "1.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57d27b317e207b10f69f5e75494119e391a96f48861ae870d1da6edac98ca900" +dependencies = [ + "cc", + "libc", +] + [[package]] name = "macro_rules_attribute" version = "0.1.3" @@ -3359,6 +4809,12 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "58093314a45e00c77d5c508f76e77c3396afbbc0d01506e7fae47b018bac2b1d" +[[package]] +name = "match_cfg" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffbee8634e0d45d258acb448e7eaab3fce7a0a467395d4d9f228e3c1f01fb2e4" + [[package]] name = "matchers" version = "0.1.0" @@ -3392,12 +4848,30 @@ dependencies = [ "serde", ] +[[package]] +name = "md-5" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6365506850d44bff6e2fbcb5176cf63650e48bd45ef2fe2665ae1570e0f4b9ca" +dependencies = [ + "digest 0.10.7", +] + [[package]] name = "memchr" version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" +[[package]] +name = "memoffset" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce" +dependencies = [ + "autocfg 1.1.0", +] + [[package]] name = "memoffset" version = "0.9.0" @@ -3413,6 +4887,16 @@ version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" +[[package]] +name = "mime_guess" +version = "2.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4192263c238a5f0d0c6bfd21f336a313a4ce1c450542449ca191bb657b4642ef" +dependencies = [ + "mime", + "unicase", +] + [[package]] name = "minimal-lexical" version = "0.2.1" @@ -3460,6 +4944,79 @@ dependencies = [ "version_check", ] +[[package]] +name = "multiaddr" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b36f567c7099511fa8612bbbb52dda2419ce0bdbacf31714e3a5ffdb766d3bd" +dependencies = [ + "arrayref", + "byteorder", + "data-encoding", + "log", + "multibase", + "multihash", + "percent-encoding", + "serde", + "static_assertions", + "unsigned-varint", + "url", +] + +[[package]] +name = "multibase" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b3539ec3c1f04ac9748a260728e855f261b4977f5c3406612c884564f329404" +dependencies = [ + "base-x", + "data-encoding", + "data-encoding-macro", +] + +[[package]] +name = "multihash" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "835d6ff01d610179fbce3de1694d007e500bf33a7f29689838941d6bf783ae40" +dependencies = [ + "core2", + "digest 0.10.7", + "multihash-derive", + "serde", + "serde-big-array", + "sha2 0.10.7", + "unsigned-varint", +] + +[[package]] +name = "multihash-derive" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d6d4752e6230d8ef7adf7bd5d8c4b1f6561c1014c5ba9a37445ccefe18aa1db" +dependencies = [ + "proc-macro-crate", + "proc-macro-error", + "proc-macro2", + "quote", + "syn 1.0.109", + "synstructure", +] + +[[package]] +name = "multistream-select" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8552ab875c1313b97b8d20cb857b9fd63e2d1d6a0a1b53ce9821e575405f27a" +dependencies = [ + "bytes", + "futures", + "log", + "pin-project", + "smallvec", + "unsigned-varint", +] + [[package]] name = "native-tls" version = "0.2.11" @@ -3478,12 +5035,96 @@ dependencies = [ "tempfile", ] +[[package]] +name = "netlink-packet-core" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "345b8ab5bd4e71a2986663e88c56856699d060e78e152e6e9d7966fcd5491297" +dependencies = [ + "anyhow", + "byteorder", + "libc", + "netlink-packet-utils", +] + +[[package]] +name = "netlink-packet-route" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9ea4302b9759a7a88242299225ea3688e63c85ea136371bb6cf94fd674efaab" +dependencies = [ + "anyhow", + "bitflags 1.3.2", + "byteorder", + "libc", + "netlink-packet-core", + "netlink-packet-utils", +] + +[[package]] +name = "netlink-packet-utils" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ede8a08c71ad5a95cdd0e4e52facd37190977039a4704eb82a283f713747d34" +dependencies = [ + "anyhow", + "byteorder", + "paste", + "thiserror", +] + +[[package]] +name = "netlink-proto" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65b4b14489ab424703c092062176d52ba55485a89c076b4f9db05092b7223aa6" +dependencies = [ + "bytes", + "futures", + "log", + "netlink-packet-core", + "netlink-sys", + "thiserror", + "tokio", +] + +[[package]] +name = "netlink-sys" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6471bf08e7ac0135876a9581bf3217ef0333c191c128d34878079f42ee150411" +dependencies = [ + "bytes", + "futures", + "libc", + "log", + "tokio", +] + +[[package]] +name = "nix" +version = "0.24.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa52e972a9a719cecb6864fb88568781eb706bac2cd1d4f04a648542dbf78069" +dependencies = [ + "bitflags 1.3.2", + "cfg-if", + "libc", + "memoffset 0.6.5", +] + [[package]] name = "no-std-compat" version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b93853da6d84c2e3c7d730d6473e8817692dd89be387eb01b94d7f108ecb5b8c" +[[package]] +name = "nohash-hasher" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bf50223579dc7cdcfb3bfcacf7069ff68243f8c363f62ffa99cf000a6b9c451" + [[package]] name = "nom" version = "7.1.3" @@ -3531,6 +5172,17 @@ dependencies = [ "winapi", ] +[[package]] +name = "num-bigint" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f93ab6289c7b344a8a9f60f88d80aa20032336fe78da341afc91c8a2341fc75f" +dependencies = [ + "autocfg 1.1.0", + "num-integer", + "num-traits", +] + [[package]] name = "num-derive" version = "0.3.3" @@ -3542,6 +5194,16 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "num-integer" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" +dependencies = [ + "autocfg 1.1.0", + "num-traits", +] + [[package]] name = "num-traits" version = "0.2.16" @@ -3549,7 +5211,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f30b0abd723be7e2ffca1272140fac1a2f084c77ec3e123c192b66af1ee9e6c2" dependencies = [ "autocfg 1.1.0", - "libm", + "libm 0.2.7", ] [[package]] @@ -3566,11 +5228,14 @@ dependencies = [ name = "nym-api" version = "1.1.26" dependencies = [ + "actix-web", "anyhow", + "array-bytes", "async-trait", "bip39", "bs58 0.4.0", "cfg-if", + "chrono", "clap 4.3.21", "console-subscriber", "cosmwasm-std", @@ -3579,7 +5244,9 @@ dependencies = [ "cw3", "cw4", "dirs 4.0.0", + "ephemera", "futures", + "futures-util", "getset", "humantime-serde", "lazy_static", @@ -3597,6 +5264,7 @@ dependencies = [ "nym-credentials", "nym-crypto", "nym-dkg", + "nym-ephemera-common", "nym-gateway-client", "nym-inclusion-probability", "nym-mixnet-contract-common", @@ -3621,15 +5289,18 @@ dependencies = [ "rocket_okapi", "schemars", "serde", + "serde_derive", "serde_json", "sqlx 0.6.3", "tap", + "tempfile", "thiserror", "time 0.3.25", "tokio", "tokio-stream", "ts-rs", "url", + "uuid 1.4.1", "zeroize", ] @@ -4007,7 +5678,7 @@ dependencies = [ "serde_bytes", "subtle-encoding", "thiserror", - "x25519-dalek", + "x25519-dalek 1.1.1", "zeroize", ] @@ -4034,6 +5705,16 @@ dependencies = [ "zeroize", ] +[[package]] +name = "nym-ephemera-common" +version = "0.1.0" +dependencies = [ + "cosmwasm-schema", + "cosmwasm-std", + "cw-utils", + "nym-contracts-common", +] + [[package]] name = "nym-execute" version = "0.1.0" @@ -4449,10 +6130,10 @@ name = "nym-outfox" version = "0.1.0" dependencies = [ "blake3", - "chacha20", - "chacha20poly1305", + "chacha20 0.9.1", + "chacha20poly1305 0.10.1", "criterion", - "curve25519-dalek", + "curve25519-dalek 3.2.0", "fastrand 1.9.0", "getrandom 0.2.10", "log", @@ -4467,7 +6148,7 @@ dependencies = [ name = "nym-pemstore" version = "0.3.0" dependencies = [ - "pem", + "pem 0.8.3", ] [[package]] @@ -4813,7 +6494,7 @@ dependencies = [ name = "nym-store-cipher" version = "0.1.0" dependencies = [ - "aes-gcm", + "aes-gcm 0.10.2", "argon2", "generic-array 0.14.7", "getrandom 0.2.10", @@ -4904,6 +6585,7 @@ dependencies = [ "nym-coconut-interface", "nym-config", "nym-contracts-common", + "nym-ephemera-common", "nym-group-contract-common", "nym-mixnet-contract-common", "nym-multisig-contract-common", @@ -4968,6 +6650,24 @@ dependencies = [ "memchr", ] +[[package]] +name = "oid-registry" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38e20717fa0541f39bd146692035c37bedfa532b3e5071b35761082407546b2a" +dependencies = [ + "asn1-rs 0.3.1", +] + +[[package]] +name = "oid-registry" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9bedf36ffb6ba96c2eb7144ef6270557b52e54b20c0a8e1eb2ff99a6c6959bff" +dependencies = [ + "asn1-rs 0.5.2", +] + [[package]] name = "okapi" version = "0.7.0-rc.1" @@ -5175,6 +6875,38 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" +[[package]] +name = "p256" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51f44edd08f51e2ade572f141051021c5af22677e42b7dd28a88155151c33594" +dependencies = [ + "ecdsa 0.14.8", + "elliptic-curve 0.12.3", + "sha2 0.10.7", +] + +[[package]] +name = "p384" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfc8c5bf642dde52bb9e87c0ecd8ca5a76faac2eeed98dedb7c717997e1080aa" +dependencies = [ + "ecdsa 0.14.8", + "elliptic-curve 0.12.3", + "sha2 0.10.7", +] + +[[package]] +name = "packed_simd_2" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1914cd452d8fccd6f9db48147b29fd4ae05bea9dc5d9ad578509f72415de282" +dependencies = [ + "cfg-if", + "libm 0.1.4", +] + [[package]] name = "pairing" version = "0.20.0" @@ -5264,6 +6996,12 @@ version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c" +[[package]] +name = "pathdiff" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8835116a5c179084a830efb3adc117ab007512b535bc1a21c991d3b32a6b44dd" + [[package]] name = "pbkdf2" version = "0.12.2" @@ -5297,6 +7035,12 @@ dependencies = [ "syn 2.0.28", ] +[[package]] +name = "peeking_take_while" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099" + [[package]] name = "peg" version = "0.7.0" @@ -5335,6 +7079,24 @@ dependencies = [ "regex", ] +[[package]] +name = "pem" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8835c273a76a90455d7344889b0964598e3316e2a79ede8e36f16bdcf2228b8" +dependencies = [ + "base64 0.13.1", +] + +[[package]] +name = "pem-rfc7468" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d159833a9105500e0398934e205e0773f0b27529557134ecfc51c27646adac" +dependencies = [ + "base64ct", +] + [[package]] name = "percent-encoding" version = "2.3.0" @@ -5443,6 +7205,12 @@ version = "0.3.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964" +[[package]] +name = "platforms" +version = "3.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3d7ddaed09e0eb771a79ab0fd64609ba0afb0a8366421957936ad14cbd13630" + [[package]] name = "plotters" version = "0.3.5" @@ -5487,6 +7255,17 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "poly1305" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "048aeb476be11a4b6ca432ca569e375810de9294ae78f4774e78ea98a9246ede" +dependencies = [ + "cpufeatures", + "opaque-debug 0.3.0", + "universal-hash 0.4.1", +] + [[package]] name = "poly1305" version = "0.8.0" @@ -5495,7 +7274,19 @@ checksum = "8159bd90725d2df49889a078b54f4f79e87f1f8a8444194cdca81d38f5393abf" dependencies = [ "cpufeatures", "opaque-debug 0.3.0", - "universal-hash", + "universal-hash 0.5.1", +] + +[[package]] +name = "polyval" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8419d2b623c7c0896ff2d5d96e2cb4ede590fed28fcc34934f4c33c036e620a1" +dependencies = [ + "cfg-if", + "cpufeatures", + "opaque-debug 0.3.0", + "universal-hash 0.4.1", ] [[package]] @@ -5507,7 +7298,7 @@ dependencies = [ "cfg-if", "cpufeatures", "opaque-debug 0.3.0", - "universal-hash", + "universal-hash 0.5.1", ] [[package]] @@ -5536,6 +7327,16 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "proc-macro-crate" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e17d47ce914bf4de440332250b0edd23ce48c005f59fab39d3335866b114f11a" +dependencies = [ + "thiserror", + "toml 0.5.11", +] + [[package]] name = "proc-macro-error" version = "1.0.4" @@ -5582,6 +7383,29 @@ dependencies = [ "yansi 1.0.0-rc.1", ] +[[package]] +name = "prometheus-client" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d6fa99d535dd930d1249e6c79cb3c2915f9172a540fe2b02a4c8f9ca954721e" +dependencies = [ + "dtoa", + "itoa", + "parking_lot 0.12.1", + "prometheus-client-derive-encode", +] + +[[package]] +name = "prometheus-client-derive-encode" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b6a5217beb0ad503ee7fa752d451c905113d70721b937126158f3106a48cc1" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "prost" version = "0.11.9" @@ -5637,6 +7461,46 @@ version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3" +[[package]] +name = "quick-protobuf" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d6da84cc204722a989e01ba2f6e1e276e190f22263d0cb6ce8526fcdb0d2e1f" +dependencies = [ + "byteorder", +] + +[[package]] +name = "quick-protobuf-codec" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1693116345026436eb2f10b677806169c1a1260c1c60eaaffe3fb5a29ae23d8b" +dependencies = [ + "asynchronous-codec", + "bytes", + "quick-protobuf", + "thiserror", + "unsigned-varint", +] + +[[package]] +name = "quinn-proto" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67c10f662eee9c94ddd7135043e544f3c82fa839a1e7b865911331961b53186c" +dependencies = [ + "bytes", + "rand 0.8.5", + "ring", + "rustc-hash", + "rustls 0.20.8", + "slab", + "thiserror", + "tinyvec", + "tracing", + "webpki 0.22.0", +] + [[package]] name = "quote" version = "1.0.32" @@ -5879,6 +7743,31 @@ dependencies = [ "num_cpus", ] +[[package]] +name = "rcgen" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6413f3de1edee53342e6138e75b56d32e7bc6e332b3bd62d497b1929d4cfbcdd" +dependencies = [ + "pem 1.1.1", + "ring", + "time 0.3.25", + "x509-parser 0.13.2", + "yasna", +] + +[[package]] +name = "rcgen" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffbe84efe2f38dea12e9bfc1f65377fdf03e53a18cb3b995faedf7934c7e785b" +dependencies = [ + "pem 1.1.1", + "ring", + "time 0.3.25", + "yasna", +] + [[package]] name = "rdrand" version = "0.4.0" @@ -5937,6 +7826,50 @@ dependencies = [ "syn 2.0.28", ] +[[package]] +name = "refinery" +version = "0.8.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdb0436d0dd7bd8d4fce1e828751fa79742b08e35f27cfea7546f8a322b5ef24" +dependencies = [ + "refinery-core", + "refinery-macros", +] + +[[package]] +name = "refinery-core" +version = "0.8.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19206547cd047e8f4dfa6b20c30d3ecaf24be05841b6aa0aa926a47a3d0662bb" +dependencies = [ + "async-trait", + "cfg-if", + "lazy_static", + "log", + "regex", + "rusqlite", + "serde", + "siphasher", + "thiserror", + "time 0.3.25", + "toml 0.7.6", + "url", + "walkdir", +] + +[[package]] +name = "refinery-macros" +version = "0.8.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d94d4b9241859ba19eaa5c04c86e782eb3aa0aae2c5868e0cfa90c856e58a174" +dependencies = [ + "proc-macro2", + "quote", + "refinery-core", + "regex", + "syn 2.0.28", +] + [[package]] name = "regex" version = "1.9.3" @@ -6019,6 +7952,16 @@ dependencies = [ "winreg", ] +[[package]] +name = "resolv-conf" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52e44394d2086d010551b14b53b1f24e31647570cd1deb0379e2c21b329aba00" +dependencies = [ + "hostname", + "quick-error 1.2.3", +] + [[package]] name = "rfc6979" version = "0.3.1" @@ -6140,7 +8083,7 @@ version = "0.5.0-rc.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "936012c99162a03a67f37f9836d5f938f662e26f2717809761a9ac46432090f4" dependencies = [ - "cookie", + "cookie 0.17.0", "either", "futures", "http", @@ -6183,19 +8126,125 @@ version = "0.8.0-rc.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8c43f8edc57d88750a220b0ec1870a36c1106204ec99cc35131b49de3b954a4a" dependencies = [ - "darling", + "darling 0.13.4", "proc-macro2", "quote", "rocket_http", "syn 1.0.109", ] +[[package]] +name = "rocksdb" +version = "0.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "015439787fce1e75d55f279078d33ff14b4af5d93d995e8838ee4631301c8a99" +dependencies = [ + "libc", + "librocksdb-sys", +] + +[[package]] +name = "rtcp" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1919efd6d4a6a85d13388f9487549bb8e359f17198cc03ffd72f79b553873691" +dependencies = [ + "bytes", + "thiserror", + "webrtc-util", +] + +[[package]] +name = "rtnetlink" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "322c53fd76a18698f1c27381d58091de3a043d356aa5bd0d510608b565f469a0" +dependencies = [ + "futures", + "log", + "netlink-packet-route", + "netlink-proto", + "nix", + "thiserror", + "tokio", +] + +[[package]] +name = "rtp" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2a095411ff00eed7b12e4c6a118ba984d113e1079582570d56a5ee723f11f80" +dependencies = [ + "async-trait", + "bytes", + "rand 0.8.5", + "serde", + "thiserror", + "webrtc-util", +] + +[[package]] +name = "rusqlite" +version = "0.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85127183a999f7db96d1a976a309eebbfb6ea3b0b400ddd8340190129de6eb7a" +dependencies = [ + "bitflags 1.3.2", + "fallible-iterator", + "fallible-streaming-iterator", + "hashlink 0.7.0", + "libsqlite3-sys", + "memchr", + "smallvec", +] + +[[package]] +name = "rust-embed" +version = "6.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b68543d5527e158213414a92832d2aab11a84d2571a5eb021ebe22c43aab066" +dependencies = [ + "rust-embed-impl", + "rust-embed-utils", + "walkdir", +] + +[[package]] +name = "rust-embed-impl" +version = "6.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d4e0f0ced47ded9a68374ac145edd65a6c1fa13a96447b873660b2a568a0fd7" +dependencies = [ + "proc-macro2", + "quote", + "rust-embed-utils", + "shellexpand", + "syn 1.0.109", + "walkdir", +] + +[[package]] +name = "rust-embed-utils" +version = "7.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "512b0ab6853f7e14e3c8754acb43d6f748bb9ced66aa5915a6553ac8213f7731" +dependencies = [ + "sha2 0.10.7", + "walkdir", +] + [[package]] name = "rustc-demangle" version = "0.1.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + [[package]] name = "rustc_version" version = "0.2.3" @@ -6214,6 +8263,29 @@ dependencies = [ "semver 1.0.18", ] +[[package]] +name = "rusticata-macros" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "faf0c4a6ece9950b9abdb62b1cfcf2a68b3b67a10ba445b3bb85be2a293d0632" +dependencies = [ + "nom", +] + +[[package]] +name = "rustix" +version = "0.37.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d69718bf81c6127a49dc64e44a742e8bb9213c0ff8869a22c308f84c1d4ab06" +dependencies = [ + "bitflags 1.3.2", + "errno", + "io-lifetimes", + "libc", + "linux-raw-sys 0.3.8", + "windows-sys 0.48.0", +] + [[package]] name = "rustix" version = "0.38.8" @@ -6223,7 +8295,7 @@ dependencies = [ "bitflags 2.4.0", "errno", "libc", - "linux-raw-sys", + "linux-raw-sys 0.4.5", "windows-sys 0.48.0", ] @@ -6279,6 +8351,17 @@ version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7ffc183a10b4478d04cbbbfc96d0873219d962dd5accaff2ffbd4ceb7df837f4" +[[package]] +name = "rw-stream-sink" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26338f5e09bb721b85b135ea05af7767c90b52f6de4f087d4f4a3a9d64e7dc04" +dependencies = [ + "futures", + "pin-project", + "static_assertions", +] + [[package]] name = "ryu" version = "1.0.15" @@ -6390,6 +8473,18 @@ dependencies = [ "untrusted", ] +[[package]] +name = "sdp" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d22a5ef407871893fd72b4562ee15e4742269b173959db4b8df6f538c414e13" +dependencies = [ + "rand 0.8.5", + "substring", + "thiserror", + "url", +] + [[package]] name = "sec1" version = "0.3.0" @@ -6489,6 +8584,15 @@ dependencies = [ "serde_derive", ] +[[package]] +name = "serde-big-array" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd31f59f6fe2b0c055371bb2f16d7f0aa7d8881676c04a55b1596d1a17cd10a4" +dependencies = [ + "serde", +] + [[package]] name = "serde-json-wasm" version = "0.5.0" @@ -6651,6 +8755,21 @@ dependencies = [ "lazy_static", ] +[[package]] +name = "shellexpand" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ccc8076840c4da029af4f87e4e8daeb0fca6b87bbb02e10cb60b791450e11e4" +dependencies = [ + "dirs 4.0.0", +] + +[[package]] +name = "shlex" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43b2853a4d09f215c24cc5489c992ce46052d359b5109343cbafbf26bc62f8a3" + [[package]] name = "signal-hook" version = "0.3.17" @@ -6701,6 +8820,12 @@ dependencies = [ "rand_core 0.6.4", ] +[[package]] +name = "siphasher" +version = "0.3.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7bd3e3206899af3f8b12af284fafc038cc1dc2b41d1b89dd17297221c5d225de" + [[package]] name = "slab" version = "0.4.8" @@ -6749,6 +8874,23 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "snow" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ccba027ba85743e09d15c03296797cad56395089b832b48b5a5217880f57733" +dependencies = [ + "aes-gcm 0.9.4", + "blake2 0.10.6", + "chacha20poly1305 0.9.1", + "curve25519-dalek 4.0.0-rc.1", + "rand_core 0.6.4", + "ring", + "rustc_version 0.4.0", + "sha2 0.10.7", + "subtle 2.4.1", +] + [[package]] name = "socket2" version = "0.4.9" @@ -6781,7 +8923,7 @@ dependencies = [ "bs58 0.4.0", "byteorder", "chacha", - "curve25519-dalek", + "curve25519-dalek 3.2.0", "digest 0.9.0", "hkdf 0.11.0", "hmac 0.11.0", @@ -7041,6 +9183,12 @@ dependencies = [ "loom", ] +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + [[package]] name = "stringprep" version = "0.1.3" @@ -7098,6 +9246,34 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "stun" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7e94b1ec00bad60e6410e058b52f1c66de3dc5fe4d62d09b3e52bb7d3b73e25" +dependencies = [ + "base64 0.13.1", + "crc 3.0.1", + "lazy_static", + "md-5", + "rand 0.8.5", + "ring", + "subtle 2.4.1", + "thiserror", + "tokio", + "url", + "webrtc-util", +] + +[[package]] +name = "substring" +version = "1.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42ee6433ecef213b2e72f587ef64a2f5943e7cd16fbd82dbe8bc07486c534c86" +dependencies = [ + "autocfg 1.1.0", +] + [[package]] name = "subtle" version = "1.0.0" @@ -7153,6 +9329,18 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" +[[package]] +name = "synstructure" +version = "0.12.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f36bdaa60a83aca3921b5259d5400cbf5e90fc51931376a9bd4a0eb79aa7210f" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", + "unicode-xid", +] + [[package]] name = "sysinfo" version = "0.27.8" @@ -7168,6 +9356,27 @@ dependencies = [ "winapi", ] +[[package]] +name = "system-configuration" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba3a3adc5c275d719af8cb4272ea1c4a6d668a777f37e115f6d11ddbc1c8e0e7" +dependencies = [ + "bitflags 1.3.2", + "core-foundation", + "system-configuration-sys", +] + +[[package]] +name = "system-configuration-sys" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75fb188eb626b924683e3b95e3a48e63551fcfb51949de2f06a9d91dbee93c9" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "tap" version = "1.0.1" @@ -7183,7 +9392,7 @@ dependencies = [ "cfg-if", "fastrand 2.0.0", "redox_syscall 0.3.5", - "rustix", + "rustix 0.38.8", "windows-sys 0.48.0", ] @@ -7552,6 +9761,18 @@ dependencies = [ "tungstenite 0.17.3", ] +[[package]] +name = "tokio-tungstenite" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54319c93411147bced34cb5609a80e0a8e44c5999c93903a81cd866630ec0bfd" +dependencies = [ + "futures-util", + "log", + "tokio", + "tungstenite 0.18.0", +] + [[package]] name = "tokio-util" version = "0.7.8" @@ -7560,7 +9781,10 @@ checksum = "806fe8c2c87eccc8b3267cbae29ed3ab2d0bd37fca70ab622e46aaa9375ddb7d" dependencies = [ "bytes", "futures-core", + "futures-io", "futures-sink", + "futures-util", + "hashbrown 0.12.3", "pin-project-lite", "slab", "tokio", @@ -7770,6 +9994,52 @@ dependencies = [ "tracing-subscriber", ] +[[package]] +name = "trust-dns-proto" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f7f83d1e4a0e4358ac54c5c3681e5d7da5efc5a7a632c90bb6d6669ddd9bc26" +dependencies = [ + "async-trait", + "cfg-if", + "data-encoding", + "enum-as-inner", + "futures-channel", + "futures-io", + "futures-util", + "idna 0.2.3", + "ipnet", + "lazy_static", + "rand 0.8.5", + "smallvec", + "socket2 0.4.9", + "thiserror", + "tinyvec", + "tokio", + "tracing", + "url", +] + +[[package]] +name = "trust-dns-resolver" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aff21aa4dcefb0a1afbfac26deb0adc93888c7d295fb63ab273ef276ba2b7cfe" +dependencies = [ + "cfg-if", + "futures-util", + "ipconfig", + "lazy_static", + "lru-cache", + "parking_lot 0.12.1", + "resolv-conf", + "smallvec", + "thiserror", + "tokio", + "tracing", + "trust-dns-proto", +] + [[package]] name = "try-lock" version = "0.2.4" @@ -7853,6 +10123,44 @@ dependencies = [ "utf-8", ] +[[package]] +name = "tungstenite" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30ee6ab729cd4cf0fd55218530c4522ed30b7b6081752839b68fcec8d0960788" +dependencies = [ + "base64 0.13.1", + "byteorder", + "bytes", + "http", + "httparse", + "log", + "rand 0.8.5", + "sha1", + "thiserror", + "url", + "utf-8", +] + +[[package]] +name = "turn" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4712ee30d123ec7ae26d1e1b218395a16c87cdbaf4b3925d170d684af62ea5e8" +dependencies = [ + "async-trait", + "base64 0.13.1", + "futures", + "log", + "md-5", + "rand 0.8.5", + "ring", + "stun", + "thiserror", + "tokio", + "webrtc-util", +] + [[package]] name = "typenum" version = "1.16.0" @@ -7874,6 +10182,18 @@ version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed646292ffc8188ef8ea4d1e0e0150fb15a5c2e12ad9b8fc191ae7a8a7f3c4b9" +[[package]] +name = "uint" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76f64bba2c53b04fcab63c01a7d7427eadc821e3bc48c34dc9ba29c501164b52" +dependencies = [ + "byteorder", + "crunchy", + "hex", + "static_assertions", +] + [[package]] name = "uncased" version = "0.9.9" @@ -7957,6 +10277,16 @@ dependencies = [ "extension-traits", ] +[[package]] +name = "universal-hash" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f214e8f697e925001e66ec2c6e37a4ef93f0f78c2eed7814394e10c62025b05" +dependencies = [ + "generic-array 0.14.7", + "subtle 2.4.1", +] + [[package]] name = "universal-hash" version = "0.5.1" @@ -7967,6 +10297,16 @@ dependencies = [ "subtle 2.4.1", ] +[[package]] +name = "unsigned-varint" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d86a8dc7f45e4c1b0d30e43038c38f274e77af056aa5f74b93c2cf9eb3c1c836" +dependencies = [ + "asynchronous-codec", + "bytes", +] + [[package]] name = "untrusted" version = "0.7.1" @@ -8009,6 +10349,48 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" +[[package]] +name = "utoipa" +version = "3.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68ae74ef183fae36d650f063ae7bde1cacbe1cd7e72b617cbe1e985551878b98" +dependencies = [ + "indexmap 1.9.3", + "serde", + "serde_json", + "utoipa-gen", +] + +[[package]] +name = "utoipa-gen" +version = "3.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ea8ac818da7e746a63285594cce8a96f5e00ee31994e655bd827569cb8b137b" +dependencies = [ + "lazy_static", + "proc-macro-error", + "proc-macro2", + "quote", + "regex", + "syn 2.0.28", +] + +[[package]] +name = "utoipa-swagger-ui" +version = "3.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "062bba5a3568e126ac72049a63254f4cb1da2eb713db0c1ab2a4c76be191db8c" +dependencies = [ + "actix-web", + "mime_guess", + "regex", + "rust-embed", + "serde", + "serde_json", + "utoipa", + "zip", +] + [[package]] name = "uuid" version = "0.8.2" @@ -8022,6 +10404,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "79daa5ed5740825c40b389c5e50312b9c86df53fccd33f281df655642b43869d" dependencies = [ "getrandom 0.2.10", + "serde", "wasm-bindgen", ] @@ -8060,6 +10443,21 @@ version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" +[[package]] +name = "void" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" + +[[package]] +name = "waitgroup" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1f50000a783467e6c0200f9d10642f4bc424e39efc1b770203e88b488f79292" +dependencies = [ + "atomic-waker", +] + [[package]] name = "waker-fn" version = "1.1.0" @@ -8169,6 +10567,21 @@ version = "0.2.83" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1c38c045535d93ec4f0b4defec448e4291638ee608530863b1e2ba115d4fff7f" +[[package]] +name = "wasm-timer" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be0ecb0db480561e9a7642b5d3e4187c128914e58aa84330b9493e3eb68c5e7f" +dependencies = [ + "futures", + "js-sys", + "parking_lot 0.11.2", + "pin-utils", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + [[package]] name = "wasm-utils" version = "0.1.0" @@ -8249,6 +10662,221 @@ dependencies = [ "webpki 0.22.0", ] +[[package]] +name = "webrtc" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d3bc9049bdb2cea52f5fd4f6f728184225bdb867ed0dc2410eab6df5bdd67bb" +dependencies = [ + "arc-swap", + "async-trait", + "bytes", + "hex", + "interceptor", + "lazy_static", + "log", + "rand 0.8.5", + "rcgen 0.9.3", + "regex", + "ring", + "rtcp", + "rtp", + "rustls 0.19.1", + "sdp", + "serde", + "serde_json", + "sha2 0.10.7", + "stun", + "thiserror", + "time 0.3.25", + "tokio", + "turn", + "url", + "waitgroup", + "webrtc-data", + "webrtc-dtls", + "webrtc-ice", + "webrtc-mdns", + "webrtc-media", + "webrtc-sctp", + "webrtc-srtp", + "webrtc-util", +] + +[[package]] +name = "webrtc-data" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ef36a4d12baa6e842582fe9ec16a57184ba35e1a09308307b67d43ec8883100" +dependencies = [ + "bytes", + "derive_builder", + "log", + "thiserror", + "tokio", + "webrtc-sctp", + "webrtc-util", +] + +[[package]] +name = "webrtc-dtls" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "942be5bd85f072c3128396f6e5a9bfb93ca8c1939ded735d177b7bcba9a13d05" +dependencies = [ + "aes 0.6.0", + "aes-gcm 0.10.2", + "async-trait", + "bincode", + "block-modes", + "byteorder", + "ccm", + "curve25519-dalek 3.2.0", + "der-parser 8.2.0", + "elliptic-curve 0.12.3", + "hkdf 0.12.3", + "hmac 0.12.1", + "log", + "oid-registry 0.6.1", + "p256", + "p384", + "rand 0.8.5", + "rand_core 0.6.4", + "rcgen 0.9.3", + "ring", + "rustls 0.19.1", + "sec1 0.3.0", + "serde", + "sha1", + "sha2 0.10.7", + "signature 1.6.4", + "subtle 2.4.1", + "thiserror", + "tokio", + "webpki 0.21.4", + "webrtc-util", + "x25519-dalek 2.0.0-pre.1", + "x509-parser 0.13.2", +] + +[[package]] +name = "webrtc-ice" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "465a03cc11e9a7d7b4f9f99870558fe37a102b65b93f8045392fef7c67b39e80" +dependencies = [ + "arc-swap", + "async-trait", + "crc 3.0.1", + "log", + "rand 0.8.5", + "serde", + "serde_json", + "stun", + "thiserror", + "tokio", + "turn", + "url", + "uuid 1.4.1", + "waitgroup", + "webrtc-mdns", + "webrtc-util", +] + +[[package]] +name = "webrtc-mdns" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f08dfd7a6e3987e255c4dbe710dde5d94d0f0574f8a21afa95d171376c143106" +dependencies = [ + "log", + "socket2 0.4.9", + "thiserror", + "tokio", + "webrtc-util", +] + +[[package]] +name = "webrtc-media" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f72e1650a8ae006017d1a5280efb49e2610c19ccc3c0905b03b648aee9554991" +dependencies = [ + "byteorder", + "bytes", + "rand 0.8.5", + "rtp", + "thiserror", +] + +[[package]] +name = "webrtc-sctp" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d47adcd9427eb3ede33d5a7f3424038f63c965491beafcc20bc650a2f6679c0" +dependencies = [ + "arc-swap", + "async-trait", + "bytes", + "crc 3.0.1", + "log", + "rand 0.8.5", + "thiserror", + "tokio", + "webrtc-util", +] + +[[package]] +name = "webrtc-srtp" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6183edc4c1c6c0175f8812eefdce84dfa0aea9c3ece71c2bf6ddd3c964de3da5" +dependencies = [ + "aead 0.4.3", + "aes 0.7.5", + "aes-gcm 0.9.4", + "async-trait", + "byteorder", + "bytes", + "ctr 0.8.0", + "hmac 0.11.0", + "log", + "rtcp", + "rtp", + "sha-1 0.9.8", + "subtle 2.4.1", + "thiserror", + "tokio", + "webrtc-util", +] + +[[package]] +name = "webrtc-util" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93f1db1727772c05cf7a2cfece52c3aca8045ca1e176cd517d323489aa3c6d87" +dependencies = [ + "async-trait", + "bitflags 1.3.2", + "bytes", + "cc", + "ipnet", + "lazy_static", + "libc", + "log", + "nix", + "rand 0.8.5", + "thiserror", + "tokio", + "winapi", +] + +[[package]] +name = "widestring" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17882f045410753661207383517a6f62ec3dbeb6a4ed2acce01f0728238d1983" + [[package]] name = "winapi" version = "0.3.9" @@ -8280,6 +10908,19 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "windows" +version = "0.34.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45296b64204227616fdbf2614cefa4c236b98ee64dfaaaa435207ed99fe7829f" +dependencies = [ + "windows_aarch64_msvc 0.34.0", + "windows_i686_gnu 0.34.0", + "windows_i686_msvc 0.34.0", + "windows_x86_64_gnu 0.34.0", + "windows_x86_64_msvc 0.34.0", +] + [[package]] name = "windows" version = "0.48.0" @@ -8349,6 +10990,12 @@ version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc" +[[package]] +name = "windows_aarch64_msvc" +version = "0.34.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17cffbe740121affb56fad0fc0e421804adf0ae00891205213b5cecd30db881d" + [[package]] name = "windows_aarch64_msvc" version = "0.42.2" @@ -8361,6 +11008,12 @@ version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3" +[[package]] +name = "windows_i686_gnu" +version = "0.34.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2564fde759adb79129d9b4f54be42b32c89970c18ebf93124ca8870a498688ed" + [[package]] name = "windows_i686_gnu" version = "0.42.2" @@ -8373,6 +11026,12 @@ version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241" +[[package]] +name = "windows_i686_msvc" +version = "0.34.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9cd9d32ba70453522332c14d38814bceeb747d80b3958676007acadd7e166956" + [[package]] name = "windows_i686_msvc" version = "0.42.2" @@ -8385,6 +11044,12 @@ version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00" +[[package]] +name = "windows_x86_64_gnu" +version = "0.34.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfce6deae227ee8d356d19effc141a509cc503dfd1f850622ec4b0f84428e1f4" + [[package]] name = "windows_x86_64_gnu" version = "0.42.2" @@ -8409,6 +11074,12 @@ version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953" +[[package]] +name = "windows_x86_64_msvc" +version = "0.34.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d19538ccc21819d01deaf88d6a17eae6596a12e9aafdbb97916fb49896d89de9" + [[package]] name = "windows_x86_64_msvc" version = "0.42.2" @@ -8474,12 +11145,74 @@ version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a0c105152107e3b96f6a00a65e86ce82d9b125230e1c4302940eca58ff71f4f" dependencies = [ - "curve25519-dalek", + "curve25519-dalek 3.2.0", "rand_core 0.5.1", "serde", "zeroize", ] +[[package]] +name = "x25519-dalek" +version = "2.0.0-pre.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5da623d8af10a62342bcbbb230e33e58a63255a58012f8653c578e54bab48df" +dependencies = [ + "curve25519-dalek 3.2.0", + "rand_core 0.6.4", + "zeroize", +] + +[[package]] +name = "x509-parser" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fb9bace5b5589ffead1afb76e43e34cff39cd0f3ce7e170ae0c29e53b88eb1c" +dependencies = [ + "asn1-rs 0.3.1", + "base64 0.13.1", + "data-encoding", + "der-parser 7.0.0", + "lazy_static", + "nom", + "oid-registry 0.4.0", + "ring", + "rusticata-macros", + "thiserror", + "time 0.3.25", +] + +[[package]] +name = "x509-parser" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0ecbeb7b67ce215e40e3cc7f2ff902f94a223acf44995934763467e7b1febc8" +dependencies = [ + "asn1-rs 0.5.2", + "base64 0.13.1", + "data-encoding", + "der-parser 8.2.0", + "lazy_static", + "nom", + "oid-registry 0.6.1", + "rusticata-macros", + "thiserror", + "time 0.3.25", +] + +[[package]] +name = "yamux" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5d9ba232399af1783a58d8eb26f6b5006fbefe2dc9ef36bd283324792d03ea5" +dependencies = [ + "futures", + "log", + "nohash-hasher", + "parking_lot 0.12.1", + "rand 0.8.5", + "static_assertions", +] + [[package]] name = "yansi" version = "0.5.1" @@ -8492,6 +11225,15 @@ version = "1.0.0-rc.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1367295b8f788d371ce2dbc842c7b709c73ee1364d30351dd300ec2203b12377" +[[package]] +name = "yasna" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e17bb3549cc1321ae1296b9cdc2698e2b6cb1992adfa19a8c72e5b7a738f44cd" +dependencies = [ + "time 0.3.25", +] + [[package]] name = "zeroize" version = "1.6.0" @@ -8511,3 +11253,45 @@ dependencies = [ "quote", "syn 2.0.28", ] + +[[package]] +name = "zip" +version = "0.6.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "760394e246e4c28189f19d488c058bf16f564016aefac5d32bb1f3b51d5e9261" +dependencies = [ + "byteorder", + "crc32fast", + "crossbeam-utils", + "flate2", +] + +[[package]] +name = "zstd" +version = "0.12.3+zstd.1.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76eea132fb024e0e13fd9c2f5d5d595d8a967aa72382ac2f9d39fcc95afd0806" +dependencies = [ + "zstd-safe", +] + +[[package]] +name = "zstd-safe" +version = "6.0.5+zstd.1.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d56d9e60b4b1758206c238a10165fbcae3ca37b01744e394c463463f6529d23b" +dependencies = [ + "libc", + "zstd-sys", +] + +[[package]] +name = "zstd-sys" +version = "2.0.8+zstd.1.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5556e6ee25d32df2586c098bbfa278803692a20d0ab9565e049480d52707ec8c" +dependencies = [ + "cc", + "libc", + "pkg-config", +] diff --git a/Cargo.toml b/Cargo.toml index 8ba5452d11..696f1bd9f1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -34,6 +34,7 @@ members = [ "common/cosmwasm-smart-contracts/coconut-bandwidth-contract", "common/cosmwasm-smart-contracts/coconut-dkg", "common/cosmwasm-smart-contracts/contracts-common", + "common/cosmwasm-smart-contracts/ephemera", "common/cosmwasm-smart-contracts/group-contract", "common/cosmwasm-smart-contracts/mixnet-contract", "common/cosmwasm-smart-contracts/multisig-contract", diff --git a/clients/webassembly/Cargo.lock b/clients/webassembly/Cargo.lock index c5aa4a2830..8449b45ff6 100644 --- a/clients/webassembly/Cargo.lock +++ b/clients/webassembly/Cargo.lock @@ -2729,6 +2729,16 @@ dependencies = [ "zeroize", ] +[[package]] +name = "nym-ephemera-common" +version = "0.1.0" +dependencies = [ + "cosmwasm-schema", + "cosmwasm-std", + "cw-utils", + "nym-contracts-common", +] + [[package]] name = "nym-explorer-api-requests" version = "0.1.0" @@ -3145,6 +3155,7 @@ dependencies = [ "nym-coconut-interface", "nym-config", "nym-contracts-common", + "nym-ephemera-common", "nym-group-contract-common", "nym-mixnet-contract-common", "nym-multisig-contract-common", diff --git a/common/client-libs/validator-client/Cargo.toml b/common/client-libs/validator-client/Cargo.toml index 952d106e97..f3d2716143 100644 --- a/common/client-libs/validator-client/Cargo.toml +++ b/common/client-libs/validator-client/Cargo.toml @@ -13,6 +13,7 @@ colored = "2.0" nym-coconut-dkg-common = { path = "../../cosmwasm-smart-contracts/coconut-dkg" } nym-contracts-common = { path = "../../cosmwasm-smart-contracts/contracts-common" } +nym-ephemera-common = { path = "../../cosmwasm-smart-contracts/ephemera" } 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" } diff --git a/common/client-libs/validator-client/src/nyxd/contract_traits/ephemera_query_client.rs b/common/client-libs/validator-client/src/nyxd/contract_traits/ephemera_query_client.rs new file mode 100644 index 0000000000..66595be1a1 --- /dev/null +++ b/common/client-libs/validator-client/src/nyxd/contract_traits/ephemera_query_client.rs @@ -0,0 +1,79 @@ +// Copyright 2022 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use crate::collect_paged; +use crate::nyxd::contract_traits::NymContractsProvider; +use crate::nyxd::error::NyxdError; +use crate::nyxd::CosmWasmClient; +use async_trait::async_trait; +use nym_ephemera_common::msg::QueryMsg as EphemeraQueryMsg; +use nym_ephemera_common::peers::PagedPeerResponse; +use nym_ephemera_common::types::JsonPeerInfo; +use serde::Deserialize; + +#[cfg_attr(target_arch = "wasm32", async_trait(?Send))] +#[cfg_attr(not(target_arch = "wasm32"), async_trait)] +pub trait EphemeraQueryClient { + async fn query_ephemera_contract(&self, query: EphemeraQueryMsg) -> Result + where + for<'a> T: Deserialize<'a>; + + async fn get_peers_paged( + &self, + start_after: Option, + limit: Option, + ) -> Result { + let request = EphemeraQueryMsg::GetPeers { start_after, limit }; + self.query_ephemera_contract(request).await + } +} + +// extension trait to the query client to deal with the paged queries +// (it didn't feel appropriate to combine it with the existing trait +#[cfg_attr(target_arch = "wasm32", async_trait(?Send))] +#[cfg_attr(not(target_arch = "wasm32"), async_trait)] +pub trait PagedEphemeraQueryClient: EphemeraQueryClient { + async fn get_all_ephemera_peers(&self) -> Result, NyxdError> { + collect_paged!(self, get_peers_paged, peers) + } +} + +#[async_trait] +impl PagedEphemeraQueryClient for T where T: EphemeraQueryClient {} + +#[cfg_attr(target_arch = "wasm32", async_trait(?Send))] +#[cfg_attr(not(target_arch = "wasm32"), async_trait)] +impl EphemeraQueryClient for C +where + C: CosmWasmClient + NymContractsProvider + Send + Sync, +{ + async fn query_ephemera_contract(&self, query: EphemeraQueryMsg) -> Result + where + for<'a> T: Deserialize<'a>, + { + let ephemera_contract_address = &self + .ephemera_contract_address() + .ok_or_else(|| NyxdError::unavailable_contract_address("ephemera contract"))?; + self.query_contract_smart(ephemera_contract_address, &query) + .await + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::nyxd::contract_traits::tests::IgnoreValue; + + // it's enough that this compiles and clippy is happy about it + #[allow(dead_code)] + fn all_query_variants_are_covered( + client: C, + msg: EphemeraQueryMsg, + ) { + match msg { + EphemeraQueryMsg::GetPeers { limit, start_after } => { + client.get_peers_paged(start_after, limit).ignore() + } + }; + } +} diff --git a/common/client-libs/validator-client/src/nyxd/contract_traits/ephemera_signing_client.rs b/common/client-libs/validator-client/src/nyxd/contract_traits/ephemera_signing_client.rs new file mode 100644 index 0000000000..a79ca3fe5a --- /dev/null +++ b/common/client-libs/validator-client/src/nyxd/contract_traits/ephemera_signing_client.rs @@ -0,0 +1,86 @@ +// Copyright 2022 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use crate::nyxd::contract_traits::NymContractsProvider; +use crate::nyxd::cosmwasm_client::types::ExecuteResult; +use crate::nyxd::error::NyxdError; +use crate::nyxd::{Coin, Fee, SigningCosmWasmClient}; +use crate::signing::signer::OfflineSigner; +use async_trait::async_trait; +use nym_ephemera_common::msg::ExecuteMsg as EphemeraExecuteMsg; +use nym_ephemera_common::types::JsonPeerInfo; + +#[cfg_attr(target_arch = "wasm32", async_trait(?Send))] +#[cfg_attr(not(target_arch = "wasm32"), async_trait)] +pub trait EphemeraSigningClient { + async fn execute_ephemera_contract( + &self, + fee: Option, + msg: EphemeraExecuteMsg, + memo: String, + funds: Vec, + ) -> Result; + + async fn register_as_peer( + &self, + peer_info: JsonPeerInfo, + fee: Option, + ) -> Result { + let req = EphemeraExecuteMsg::RegisterPeer { peer_info }; + + self.execute_ephemera_contract(fee, req, "registering as peer".to_string(), vec![]) + .await + } +} + +#[cfg_attr(target_arch = "wasm32", async_trait(?Send))] +#[cfg_attr(not(target_arch = "wasm32"), async_trait)] +impl EphemeraSigningClient for C +where + C: SigningCosmWasmClient + NymContractsProvider + Sync, + NyxdError: From<::Error>, +{ + async fn execute_ephemera_contract( + &self, + fee: Option, + msg: EphemeraExecuteMsg, + memo: String, + funds: Vec, + ) -> Result { + let ephemera_contract_address = self + .ephemera_contract_address() + .ok_or_else(|| NyxdError::unavailable_contract_address("ephemera contract"))?; + + let fee = fee.unwrap_or(Fee::Auto(Some(self.simulated_gas_multiplier()))); + let signer_address = &self.signer_addresses()?[0]; + + self.execute( + signer_address, + ephemera_contract_address, + &msg, + fee, + memo, + funds, + ) + .await + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::nyxd::contract_traits::tests::IgnoreValue; + + // it's enough that this compiles and clippy is happy about it + #[allow(dead_code)] + fn all_execute_variants_are_covered( + client: C, + msg: EphemeraExecuteMsg, + ) { + match msg { + EphemeraExecuteMsg::RegisterPeer { peer_info } => { + client.register_as_peer(peer_info, None).ignore() + } + }; + } +} diff --git a/common/client-libs/validator-client/src/nyxd/contract_traits/mod.rs b/common/client-libs/validator-client/src/nyxd/contract_traits/mod.rs index 545fdf05ec..dac63a2530 100644 --- a/common/client-libs/validator-client/src/nyxd/contract_traits/mod.rs +++ b/common/client-libs/validator-client/src/nyxd/contract_traits/mod.rs @@ -10,6 +10,7 @@ use std::str::FromStr; // query clients mod coconut_bandwidth_query_client; mod dkg_query_client; +mod ephemera_query_client; mod group_query_client; mod mixnet_query_client; mod multisig_query_client; @@ -20,6 +21,7 @@ mod vesting_query_client; // signing clients mod coconut_bandwidth_signing_client; mod dkg_signing_client; +mod ephemera_signing_client; mod group_signing_client; mod mixnet_signing_client; mod multisig_signing_client; @@ -32,6 +34,7 @@ pub use coconut_bandwidth_query_client::{ CoconutBandwidthQueryClient, PagedCoconutBandwidthQueryClient, }; pub use dkg_query_client::{DkgQueryClient, PagedDkgQueryClient}; +pub use ephemera_query_client::{EphemeraQueryClient, PagedEphemeraQueryClient}; pub use group_query_client::{GroupQueryClient, PagedGroupQueryClient}; pub use mixnet_query_client::{MixnetQueryClient, PagedMixnetQueryClient}; pub use multisig_query_client::{MultisigQueryClient, PagedMultisigQueryClient}; @@ -42,6 +45,7 @@ pub use vesting_query_client::{PagedVestingQueryClient, VestingQueryClient}; // re-export signing traits pub use coconut_bandwidth_signing_client::CoconutBandwidthSigningClient; pub use dkg_signing_client::DkgSigningClient; +pub use ephemera_signing_client::EphemeraSigningClient; pub use group_signing_client::GroupSigningClient; pub use mixnet_signing_client::MixnetSigningClient; pub use multisig_signing_client::MultisigSigningClient; @@ -61,6 +65,9 @@ pub trait NymContractsProvider { fn group_contract_address(&self) -> Option<&AccountId>; fn multisig_contract_address(&self) -> Option<&AccountId>; + // ephemera-related + fn ephemera_contract_address(&self) -> Option<&AccountId>; + // SPs fn name_service_contract_address(&self) -> Option<&AccountId>; fn service_provider_contract_address(&self) -> Option<&AccountId>; @@ -76,6 +83,8 @@ pub struct TypedNymContracts { pub multisig_contract_address: Option, pub coconut_dkg_contract_address: Option, + pub ephemera_contract_address: Option, + pub service_provider_directory_contract_address: Option, pub name_service_contract_address: Option, } @@ -109,6 +118,10 @@ impl TryFrom for TypedNymContracts { .coconut_dkg_contract_address .map(|addr| addr.parse()) .transpose()?, + ephemera_contract_address: value + .ephemera_contract_address + .map(|addr| addr.parse()) + .transpose()?, service_provider_directory_contract_address: value .service_provider_directory_contract_address .map(|addr| addr.parse()) diff --git a/common/client-libs/validator-client/src/nyxd/mod.rs b/common/client-libs/validator-client/src/nyxd/mod.rs index d0cf1e0b3e..16027a1970 100644 --- a/common/client-libs/validator-client/src/nyxd/mod.rs +++ b/common/client-libs/validator-client/src/nyxd/mod.rs @@ -259,6 +259,10 @@ impl NymContractsProvider for NyxdClient { self.config.contracts.multisig_contract_address.as_ref() } + fn ephemera_contract_address(&self) -> Option<&AccountId> { + self.config.contracts.ephemera_contract_address.as_ref() + } + fn name_service_contract_address(&self) -> Option<&AccountId> { self.config.contracts.name_service_contract_address.as_ref() } diff --git a/common/client-libs/validator-client/src/nyxd/traits/mod.rs b/common/client-libs/validator-client/src/nyxd/traits/mod.rs new file mode 100644 index 0000000000..e69de29bb2 diff --git a/common/config/src/lib.rs b/common/config/src/lib.rs index fe91fc3d6d..a751ba2414 100644 --- a/common/config/src/lib.rs +++ b/common/config/src/lib.rs @@ -17,6 +17,7 @@ pub mod helpers; pub mod legacy_helpers; pub const NYM_DIR: &str = ".nym"; +pub const DEFAULT_NYM_APIS_DIR: &str = "nym-api"; pub const DEFAULT_CONFIG_DIR: &str = "config"; pub const DEFAULT_DATA_DIR: &str = "data"; pub const DEFAULT_CONFIG_FILENAME: &str = "config.toml"; diff --git a/common/cosmwasm-smart-contracts/ephemera/Cargo.toml b/common/cosmwasm-smart-contracts/ephemera/Cargo.toml new file mode 100644 index 0000000000..fda88a18f5 --- /dev/null +++ b/common/cosmwasm-smart-contracts/ephemera/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "nym-ephemera-common" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +cosmwasm-schema = { workspace = true } +cosmwasm-std = { workspace = true } +cw-utils = { workspace = true } + +contracts-common = { path = "../contracts-common", package = "nym-contracts-common" } + +[features] +schema = [] \ No newline at end of file diff --git a/common/cosmwasm-smart-contracts/ephemera/src/lib.rs b/common/cosmwasm-smart-contracts/ephemera/src/lib.rs new file mode 100644 index 0000000000..f53ac15b7e --- /dev/null +++ b/common/cosmwasm-smart-contracts/ephemera/src/lib.rs @@ -0,0 +1,3 @@ +pub mod msg; +pub mod peers; +pub mod types; diff --git a/common/cosmwasm-smart-contracts/ephemera/src/msg.rs b/common/cosmwasm-smart-contracts/ephemera/src/msg.rs new file mode 100644 index 0000000000..5024a73b0b --- /dev/null +++ b/common/cosmwasm-smart-contracts/ephemera/src/msg.rs @@ -0,0 +1,33 @@ +// Copyright 2021 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +#[cfg(feature = "schema")] +use crate::peers::PagedPeerResponse; +use crate::types::JsonPeerInfo; +use cosmwasm_schema::cw_serde; +#[cfg(feature = "schema")] +use cosmwasm_schema::QueryResponses; + +#[cw_serde] +pub struct InstantiateMsg { + pub group_addr: String, + pub mix_denom: String, +} + +#[cw_serde] +pub enum ExecuteMsg { + RegisterPeer { peer_info: JsonPeerInfo }, +} + +#[cw_serde] +#[cfg_attr(feature = "schema", derive(QueryResponses))] +pub enum QueryMsg { + #[cfg_attr(feature = "schema", returns(PagedPeerResponse))] + GetPeers { + limit: Option, + start_after: Option, + }, +} + +#[cw_serde] +pub struct MigrateMsg {} diff --git a/common/cosmwasm-smart-contracts/ephemera/src/peers.rs b/common/cosmwasm-smart-contracts/ephemera/src/peers.rs new file mode 100644 index 0000000000..d2eafcad08 --- /dev/null +++ b/common/cosmwasm-smart-contracts/ephemera/src/peers.rs @@ -0,0 +1,25 @@ +// Copyright 2023 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use crate::types::JsonPeerInfo; +use cosmwasm_schema::cw_serde; +use cosmwasm_std::Addr; + +#[cw_serde] +pub struct PagedPeerResponse { + pub peers: Vec, + pub per_page: usize, + + /// Field indicating paging information for the following queries if the caller wishes to get further entries. + pub start_next_after: Option, +} + +impl PagedPeerResponse { + pub fn new(peers: Vec, per_page: usize, start_next_after: Option) -> Self { + PagedPeerResponse { + peers, + per_page, + start_next_after, + } + } +} diff --git a/common/cosmwasm-smart-contracts/ephemera/src/types.rs b/common/cosmwasm-smart-contracts/ephemera/src/types.rs new file mode 100644 index 0000000000..cc68e6dc54 --- /dev/null +++ b/common/cosmwasm-smart-contracts/ephemera/src/types.rs @@ -0,0 +1,29 @@ +// Copyright 2023 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use cosmwasm_schema::cw_serde; +use cosmwasm_std::Addr; + +#[cw_serde] +pub struct JsonPeerInfo { + /// The cosmos address of the peer, used in interacting with the chain. + pub cosmos_address: Addr, + /// The TCP/IP address of the peer. + /// Expected formats: + /// 1. `:` + /// 2. `/ip4//tcp/` - this is the format used by libp2p multiaddr + pub ip_address: String, + ///Serialized public key. + pub public_key: String, +} + +impl JsonPeerInfo { + #[must_use] + pub fn new(cosmos_address: Addr, ip_address: String, public_key: String) -> Self { + Self { + cosmos_address, + ip_address, + public_key, + } + } +} diff --git a/common/network-defaults/src/lib.rs b/common/network-defaults/src/lib.rs index 33373dcc52..8de0ed465c 100644 --- a/common/network-defaults/src/lib.rs +++ b/common/network-defaults/src/lib.rs @@ -30,6 +30,7 @@ pub struct NymContracts { pub group_contract_address: Option, pub multisig_contract_address: Option, pub coconut_dkg_contract_address: Option, + pub ephemera_contract_address: Option, pub service_provider_directory_contract_address: Option, pub name_service_contract_address: Option, } @@ -128,6 +129,9 @@ impl NymNetworkDetails { .with_coconut_dkg_contract(Some( var(var_names::COCONUT_DKG_CONTRACT_ADDRESS).expect("coconut dkg contract not set"), )) + .with_ephemera_contract(Some( + var(var_names::EPHEMERA_CONTRACT_ADDRESS).expect("ephemera contract not set"), + )) .with_service_provider_directory_contract(get_optional_env( var_names::SERVICE_PROVIDER_DIRECTORY_CONTRACT_ADDRESS, )) @@ -159,6 +163,7 @@ impl NymNetworkDetails { coconut_dkg_contract_address: parse_optional_str( mainnet::COCONUT_DKG_CONTRACT_ADDRESS, ), + ephemera_contract_address: parse_optional_str(mainnet::EPHEMERA_CONTRACT_ADDRESS), service_provider_directory_contract_address: None, name_service_contract_address: None, }, @@ -253,6 +258,12 @@ impl NymNetworkDetails { self } + #[must_use] + pub fn with_ephemera_contract>(mut self, contract: Option) -> Self { + self.contracts.ephemera_contract_address = contract.map(Into::into); + self + } + #[must_use] pub fn with_service_provider_directory_contract>( mut self, diff --git a/common/network-defaults/src/mainnet.rs b/common/network-defaults/src/mainnet.rs index f2ec070533..fbc25c4b00 100644 --- a/common/network-defaults/src/mainnet.rs +++ b/common/network-defaults/src/mainnet.rs @@ -20,6 +20,7 @@ pub(crate) const COCONUT_BANDWIDTH_CONTRACT_ADDRESS: &str = pub(crate) const GROUP_CONTRACT_ADDRESS: &str = "n19lc9u84cz0yz3fww5283nucc9yvr8gsjmgeul0"; pub(crate) const MULTISIG_CONTRACT_ADDRESS: &str = "n19lc9u84cz0yz3fww5283nucc9yvr8gsjmgeul0"; pub(crate) const COCONUT_DKG_CONTRACT_ADDRESS: &str = "n19lc9u84cz0yz3fww5283nucc9yvr8gsjmgeul0"; +pub(crate) const EPHEMERA_CONTRACT_ADDRESS: &str = "n19lc9u84cz0yz3fww5283nucc9yvr8gsjmgeul0"; pub(crate) const REWARDING_VALIDATOR_ADDRESS: &str = "n10yyd98e2tuwu0f7ypz9dy3hhjw7v772q6287gy"; pub const STATISTICS_SERVICE_DOMAIN_ADDRESS: &str = "https://mainnet-stats.nymte.ch:8090/"; @@ -84,6 +85,10 @@ pub fn export_to_env() { var_names::COCONUT_DKG_CONTRACT_ADDRESS, COCONUT_DKG_CONTRACT_ADDRESS, ); + set_var_to_default( + var_names::EPHEMERA_CONTRACT_ADDRESS, + EPHEMERA_CONTRACT_ADDRESS, + ); set_var_to_default( var_names::REWARDING_VALIDATOR_ADDRESS, REWARDING_VALIDATOR_ADDRESS, @@ -126,6 +131,10 @@ pub fn export_to_env_if_not_set() { var_names::COCONUT_DKG_CONTRACT_ADDRESS, COCONUT_DKG_CONTRACT_ADDRESS, ); + set_var_conditionally_to_default( + var_names::EPHEMERA_CONTRACT_ADDRESS, + EPHEMERA_CONTRACT_ADDRESS, + ); set_var_conditionally_to_default( var_names::REWARDING_VALIDATOR_ADDRESS, REWARDING_VALIDATOR_ADDRESS, diff --git a/common/network-defaults/src/var_names.rs b/common/network-defaults/src/var_names.rs index 7639efa8e4..26b65b7f95 100644 --- a/common/network-defaults/src/var_names.rs +++ b/common/network-defaults/src/var_names.rs @@ -18,6 +18,7 @@ pub const COCONUT_BANDWIDTH_CONTRACT_ADDRESS: &str = "COCONUT_BANDWIDTH_CONTRACT pub const GROUP_CONTRACT_ADDRESS: &str = "GROUP_CONTRACT_ADDRESS"; pub const MULTISIG_CONTRACT_ADDRESS: &str = "MULTISIG_CONTRACT_ADDRESS"; pub const COCONUT_DKG_CONTRACT_ADDRESS: &str = "COCONUT_DKG_CONTRACT_ADDRESS"; +pub const EPHEMERA_CONTRACT_ADDRESS: &str = "EPHEMERA_CONTRACT_ADDRESS"; pub const REWARDING_VALIDATOR_ADDRESS: &str = "REWARDING_VALIDATOR_ADDRESS"; pub const STATISTICS_SERVICE_DOMAIN_ADDRESS: &str = "STATISTICS_SERVICE_DOMAIN_ADDRESS"; pub const SERVICE_PROVIDER_DIRECTORY_CONTRACT_ADDRESS: &str = diff --git a/contracts/Cargo.lock b/contracts/Cargo.lock index 1895d7c925..812afbaa8d 100644 --- a/contracts/Cargo.lock +++ b/contracts/Cargo.lock @@ -1259,6 +1259,36 @@ dependencies = [ "zeroize", ] +[[package]] +name = "nym-ephemera" +version = "0.1.0" +dependencies = [ + "cosmwasm-schema", + "cosmwasm-std", + "cosmwasm-storage", + "cw-controllers", + "cw-multi-test", + "cw-storage-plus", + "cw4", + "cw4-group", + "lazy_static", + "nym-ephemera-common", + "nym-group-contract-common", + "rusty-fork", + "serde", + "thiserror", +] + +[[package]] +name = "nym-ephemera-common" +version = "0.1.0" +dependencies = [ + "cosmwasm-schema", + "cosmwasm-std", + "cw-utils", + "nym-contracts-common", +] + [[package]] name = "nym-group-contract-common" version = "0.1.0" diff --git a/contracts/Cargo.toml b/contracts/Cargo.toml index 0887794260..8063d2f543 100644 --- a/contracts/Cargo.toml +++ b/contracts/Cargo.toml @@ -3,6 +3,7 @@ members = [ "coconut-bandwidth", "coconut-dkg", "coconut-test", + "ephemera", "mixnet", "mixnet-vesting-integration-tests", "multisig/cw3-flex-multisig", diff --git a/contracts/ephemera/.cargo/config b/contracts/ephemera/.cargo/config new file mode 100644 index 0000000000..2f698e22b3 --- /dev/null +++ b/contracts/ephemera/.cargo/config @@ -0,0 +1,5 @@ +[alias] +wasm = "build --release --lib --target wasm32-unknown-unknown" +wasm-debug = "build --target wasm32-unknown-unknown" +unit-test = "test --lib" +schema = "run --bin schema --features=schema-gen" \ No newline at end of file diff --git a/contracts/ephemera/Cargo.toml b/contracts/ephemera/Cargo.toml new file mode 100644 index 0000000000..376eec374e --- /dev/null +++ b/contracts/ephemera/Cargo.toml @@ -0,0 +1,36 @@ +[package] +name = "nym-ephemera" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[[bin]] +name = "schema" +required-features = ["schema-gen"] + +[lib] +crate-type = ["cdylib", "rlib"] + +[dependencies] +nym-ephemera-common = { path = "../../common/cosmwasm-smart-contracts/ephemera" } + +cosmwasm-schema = { workspace = true, optional = true } +cosmwasm-std = { workspace = true } +cosmwasm-storage = { workspace = true } +cw-storage-plus = { workspace = true } +cw-controllers = { workspace = true } +cw4 = { workspace = true } + +serde = { version = "1.0.103", default-features = false, features = ["derive"] } +thiserror = "1.0.23" + +[dev-dependencies] +cw-multi-test = { workspace = true } +cw4-group = { path = "../multisig/cw4-group" } +nym-group-contract-common = { path = "../../common/cosmwasm-smart-contracts/group-contract" } +lazy_static = "1.4" +rusty-fork = "0.3" + +[features] +schema-gen = ["nym-ephemera-common/schema", "cosmwasm-schema"] diff --git a/contracts/ephemera/Makefile b/contracts/ephemera/Makefile new file mode 100644 index 0000000000..f138e2c879 --- /dev/null +++ b/contracts/ephemera/Makefile @@ -0,0 +1,2 @@ +generate-schema: + cargo schema \ No newline at end of file diff --git a/contracts/ephemera/schema/nym-ephemera.json b/contracts/ephemera/schema/nym-ephemera.json new file mode 100644 index 0000000000..4a0efed6bd --- /dev/null +++ b/contracts/ephemera/schema/nym-ephemera.json @@ -0,0 +1,194 @@ +{ + "contract_name": "nym-ephemera", + "contract_version": "0.1.0", + "idl_version": "1.0.0", + "instantiate": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "InstantiateMsg", + "type": "object", + "required": [ + "group_addr", + "mix_denom" + ], + "properties": { + "group_addr": { + "type": "string" + }, + "mix_denom": { + "type": "string" + } + }, + "additionalProperties": false + }, + "execute": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ExecuteMsg", + "oneOf": [ + { + "type": "object", + "required": [ + "register_peer" + ], + "properties": { + "register_peer": { + "type": "object", + "required": [ + "peer_info" + ], + "properties": { + "peer_info": { + "$ref": "#/definitions/JsonPeerInfo" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + ], + "definitions": { + "Addr": { + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + }, + "JsonPeerInfo": { + "type": "object", + "required": [ + "cosmos_address", + "ip_address", + "public_key" + ], + "properties": { + "cosmos_address": { + "description": "The cosmos address of the peer, used in interacting with the chain.", + "allOf": [ + { + "$ref": "#/definitions/Addr" + } + ] + }, + "ip_address": { + "description": "The TCP/IP address of the peer. Expected formats: 1. `:` 2. `/ip4//tcp/` - this is the format used by libp2p multiaddr", + "type": "string" + }, + "public_key": { + "description": "Serialized public key.", + "type": "string" + } + }, + "additionalProperties": false + } + } + }, + "query": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "QueryMsg", + "oneOf": [ + { + "type": "object", + "required": [ + "get_peers" + ], + "properties": { + "get_peers": { + "type": "object", + "properties": { + "limit": { + "type": [ + "integer", + "null" + ], + "format": "uint32", + "minimum": 0.0 + }, + "start_after": { + "type": [ + "string", + "null" + ] + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + ] + }, + "migrate": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "MigrateMsg", + "type": "object", + "additionalProperties": false + }, + "sudo": null, + "responses": { + "get_peers": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "PagedPeerResponse", + "type": "object", + "required": [ + "peers", + "per_page" + ], + "properties": { + "peers": { + "type": "array", + "items": { + "$ref": "#/definitions/JsonPeerInfo" + } + }, + "per_page": { + "type": "integer", + "format": "uint", + "minimum": 0.0 + }, + "start_next_after": { + "description": "Field indicating paging information for the following queries if the caller wishes to get further entries.", + "anyOf": [ + { + "$ref": "#/definitions/Addr" + }, + { + "type": "null" + } + ] + } + }, + "additionalProperties": false, + "definitions": { + "Addr": { + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + }, + "JsonPeerInfo": { + "type": "object", + "required": [ + "cosmos_address", + "ip_address", + "public_key" + ], + "properties": { + "cosmos_address": { + "description": "The cosmos address of the peer, used in interacting with the chain.", + "allOf": [ + { + "$ref": "#/definitions/Addr" + } + ] + }, + "ip_address": { + "description": "The TCP/IP address of the peer. Expected formats: 1. `:` 2. `/ip4//tcp/` - this is the format used by libp2p multiaddr", + "type": "string" + }, + "public_key": { + "description": "Serialized public key.", + "type": "string" + } + }, + "additionalProperties": false + } + } + } + } +} diff --git a/contracts/ephemera/schema/raw/execute.json b/contracts/ephemera/schema/raw/execute.json new file mode 100644 index 0000000000..becf9117ca --- /dev/null +++ b/contracts/ephemera/schema/raw/execute.json @@ -0,0 +1,60 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ExecuteMsg", + "oneOf": [ + { + "type": "object", + "required": [ + "register_peer" + ], + "properties": { + "register_peer": { + "type": "object", + "required": [ + "peer_info" + ], + "properties": { + "peer_info": { + "$ref": "#/definitions/JsonPeerInfo" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + ], + "definitions": { + "Addr": { + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + }, + "JsonPeerInfo": { + "type": "object", + "required": [ + "cosmos_address", + "ip_address", + "public_key" + ], + "properties": { + "cosmos_address": { + "description": "The cosmos address of the peer, used in interacting with the chain.", + "allOf": [ + { + "$ref": "#/definitions/Addr" + } + ] + }, + "ip_address": { + "description": "The TCP/IP address of the peer. Expected formats: 1. `:` 2. `/ip4//tcp/` - this is the format used by libp2p multiaddr", + "type": "string" + }, + "public_key": { + "description": "Serialized public key.", + "type": "string" + } + }, + "additionalProperties": false + } + } +} diff --git a/contracts/ephemera/schema/raw/instantiate.json b/contracts/ephemera/schema/raw/instantiate.json new file mode 100644 index 0000000000..907c7aa20f --- /dev/null +++ b/contracts/ephemera/schema/raw/instantiate.json @@ -0,0 +1,18 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "InstantiateMsg", + "type": "object", + "required": [ + "group_addr", + "mix_denom" + ], + "properties": { + "group_addr": { + "type": "string" + }, + "mix_denom": { + "type": "string" + } + }, + "additionalProperties": false +} diff --git a/contracts/ephemera/schema/raw/migrate.json b/contracts/ephemera/schema/raw/migrate.json new file mode 100644 index 0000000000..7fbe8c5708 --- /dev/null +++ b/contracts/ephemera/schema/raw/migrate.json @@ -0,0 +1,6 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "MigrateMsg", + "type": "object", + "additionalProperties": false +} diff --git a/contracts/ephemera/schema/raw/query.json b/contracts/ephemera/schema/raw/query.json new file mode 100644 index 0000000000..77c2991f6b --- /dev/null +++ b/contracts/ephemera/schema/raw/query.json @@ -0,0 +1,35 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "QueryMsg", + "oneOf": [ + { + "type": "object", + "required": [ + "get_peers" + ], + "properties": { + "get_peers": { + "type": "object", + "properties": { + "limit": { + "type": [ + "integer", + "null" + ], + "format": "uint32", + "minimum": 0.0 + }, + "start_after": { + "type": [ + "string", + "null" + ] + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + ] +} diff --git a/contracts/ephemera/schema/raw/response_to_get_peers.json b/contracts/ephemera/schema/raw/response_to_get_peers.json new file mode 100644 index 0000000000..3b3c85bb5b --- /dev/null +++ b/contracts/ephemera/schema/raw/response_to_get_peers.json @@ -0,0 +1,67 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "PagedPeerResponse", + "type": "object", + "required": [ + "peers", + "per_page" + ], + "properties": { + "peers": { + "type": "array", + "items": { + "$ref": "#/definitions/JsonPeerInfo" + } + }, + "per_page": { + "type": "integer", + "format": "uint", + "minimum": 0.0 + }, + "start_next_after": { + "description": "Field indicating paging information for the following queries if the caller wishes to get further entries.", + "anyOf": [ + { + "$ref": "#/definitions/Addr" + }, + { + "type": "null" + } + ] + } + }, + "additionalProperties": false, + "definitions": { + "Addr": { + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + }, + "JsonPeerInfo": { + "type": "object", + "required": [ + "cosmos_address", + "ip_address", + "public_key" + ], + "properties": { + "cosmos_address": { + "description": "The cosmos address of the peer, used in interacting with the chain.", + "allOf": [ + { + "$ref": "#/definitions/Addr" + } + ] + }, + "ip_address": { + "description": "The TCP/IP address of the peer. Expected formats: 1. `:` 2. `/ip4//tcp/` - this is the format used by libp2p multiaddr", + "type": "string" + }, + "public_key": { + "description": "Serialized public key.", + "type": "string" + } + }, + "additionalProperties": false + } + } +} diff --git a/contracts/ephemera/src/bin/schema.rs b/contracts/ephemera/src/bin/schema.rs new file mode 100644 index 0000000000..8df2100755 --- /dev/null +++ b/contracts/ephemera/src/bin/schema.rs @@ -0,0 +1,14 @@ +// Copyright 2023 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use cosmwasm_schema::write_api; +use nym_ephemera_common::msg::{ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg}; + +fn main() { + write_api! { + instantiate: InstantiateMsg, + query: QueryMsg, + execute: ExecuteMsg, + migrate: MigrateMsg, + } +} diff --git a/contracts/ephemera/src/contract.rs b/contracts/ephemera/src/contract.rs new file mode 100644 index 0000000000..0cfa78a410 --- /dev/null +++ b/contracts/ephemera/src/contract.rs @@ -0,0 +1,97 @@ +// Copyright 2023 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use crate::error::ContractError; +use crate::peers::queries::query_peers_paged; +use crate::peers::transactions::try_register_peer; +use crate::state::{State, STATE}; +use cosmwasm_std::{ + entry_point, to_binary, Deps, DepsMut, Env, MessageInfo, QueryResponse, Response, +}; +use cw4::Cw4Contract; +use nym_ephemera_common::msg::{ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg}; + +/// Instantiate the contract. +/// +/// `deps` contains Storage, API and Querier +/// `env` contains block, message and contract info +/// `msg` is the contract initialization message, sort of like a constructor call. +#[entry_point] +pub fn instantiate( + deps: DepsMut<'_>, + _env: Env, + _info: MessageInfo, + msg: InstantiateMsg, +) -> Result { + let group_addr = Cw4Contract(deps.api.addr_validate(&msg.group_addr).map_err(|_| { + ContractError::InvalidGroup { + addr: msg.group_addr.clone(), + } + })?); + + let state = State { + group_addr, + mix_denom: msg.mix_denom, + }; + STATE.save(deps.storage, &state)?; + + Ok(Response::default()) +} + +/// Handle an incoming message +#[entry_point] +pub fn execute( + deps: DepsMut<'_>, + _env: Env, + info: MessageInfo, + msg: ExecuteMsg, +) -> Result { + match msg { + ExecuteMsg::RegisterPeer { peer_info } => try_register_peer(deps, info, peer_info), + } +} + +#[entry_point] +pub fn query(deps: Deps<'_>, _env: Env, msg: QueryMsg) -> Result { + let response = match msg { + QueryMsg::GetPeers { limit, start_after } => { + to_binary(&query_peers_paged(deps, start_after, limit)?)? + } + }; + + Ok(response) +} + +#[entry_point] +pub fn migrate(_deps: DepsMut<'_>, _env: Env, _msg: MigrateMsg) -> Result { + Ok(Default::default()) +} + +#[cfg(test)] +mod tests { + use super::*; + use cosmwasm_std::testing::{mock_dependencies, mock_env, mock_info}; + use cosmwasm_std::Addr; + + #[test] + fn initialize_contract() { + let mut deps = mock_dependencies(); + let env = mock_env(); + + let init_msg = InstantiateMsg { + group_addr: "group_addr".to_string(), + mix_denom: "uatom".to_string(), + }; + + let sender = mock_info("sender", &[]); + let res = instantiate(deps.as_mut(), env, sender, init_msg); + assert!(res.is_ok()); + + let expected_state = State { + group_addr: Cw4Contract::new(Addr::unchecked("group_addr")), + mix_denom: "uatom".to_string(), + }; + let state = STATE.load(deps.as_ref().storage).unwrap(); + assert_eq!(state, expected_state); + } +} diff --git a/contracts/ephemera/src/error.rs b/contracts/ephemera/src/error.rs new file mode 100644 index 0000000000..2ee74c6ab0 --- /dev/null +++ b/contracts/ephemera/src/error.rs @@ -0,0 +1,21 @@ +// Copyright 2023 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use cosmwasm_std::StdError; +use thiserror::Error; + +/// Custom errors for contract failure conditions. +#[derive(Error, Debug, PartialEq)] +pub enum ContractError { + #[error(transparent)] + Std(#[from] StdError), + + #[error("Group contract invalid address '{addr}'")] + InvalidGroup { addr: String }, + + #[error("This potential ephemera peer is not in the ephemera group")] + Unauthorized, + + #[error("This sender is already registered")] + AlreadyRegistered, +} diff --git a/contracts/ephemera/src/lib.rs b/contracts/ephemera/src/lib.rs new file mode 100644 index 0000000000..fa30231382 --- /dev/null +++ b/contracts/ephemera/src/lib.rs @@ -0,0 +1,8 @@ +// Copyright 2022 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +pub mod contract; +pub mod error; +mod peers; +mod state; +mod support; diff --git a/contracts/ephemera/src/peers/mod.rs b/contracts/ephemera/src/peers/mod.rs new file mode 100644 index 0000000000..e82ae1570a --- /dev/null +++ b/contracts/ephemera/src/peers/mod.rs @@ -0,0 +1,6 @@ +// Copyright 2023 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +pub mod queries; +pub mod storage; +pub mod transactions; diff --git a/contracts/ephemera/src/peers/queries.rs b/contracts/ephemera/src/peers/queries.rs new file mode 100644 index 0000000000..fc24e6d583 --- /dev/null +++ b/contracts/ephemera/src/peers/queries.rs @@ -0,0 +1,164 @@ +// Copyright 2023 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use crate::peers::storage::{PEERS, PEERS_PAGE_DEFAULT_LIMIT, PEERS_PAGE_MAX_LIMIT}; +use cosmwasm_std::{Deps, Order, StdResult}; +use cw_storage_plus::Bound; +use nym_ephemera_common::peers::PagedPeerResponse; + +pub fn query_peers_paged( + deps: Deps<'_>, + start_after: Option, + limit: Option, +) -> StdResult { + let limit = limit + .unwrap_or(PEERS_PAGE_DEFAULT_LIMIT) + .min(PEERS_PAGE_MAX_LIMIT) as usize; + + let addr = start_after + .map(|addr| deps.api.addr_validate(&addr)) + .transpose()?; + + let start = addr.map(Bound::exclusive); + + let peers = PEERS + .range(deps.storage, start, None, Order::Ascending) + .take(limit) + .map(|res| res.map(|item| item.1)) + .collect::>>()?; + + let start_next_after = peers + .last() + .map(|peer_info| peer_info.cosmos_address.clone()); + + Ok(PagedPeerResponse::new(peers, limit, start_next_after)) +} + +#[cfg(test)] +pub(crate) mod tests { + use super::*; + use crate::peers::storage::{PEERS_PAGE_DEFAULT_LIMIT, PEERS_PAGE_MAX_LIMIT}; + use crate::support::tests::fixtures::peer_fixture; + use crate::support::tests::helpers::init_contract; + use cosmwasm_std::DepsMut; + + fn fill_peers(deps: DepsMut<'_>, size: usize) { + for n in 0..size { + let peer = peer_fixture(&format!("peer{}", n)); + PEERS + .save(deps.storage, peer.cosmos_address.clone(), &peer) + .unwrap(); + } + } + + fn remove_peers(deps: DepsMut<'_>, size: usize) { + for n in 0..size { + let peer = peer_fixture(&format!("peer{}", n)); + PEERS.remove(deps.storage, peer.cosmos_address); + } + } + + #[test] + fn peers_empty_on_init() { + let deps = init_contract(); + + let page1 = query_peers_paged(deps.as_ref(), None, None).unwrap(); + assert_eq!(0, page1.peers.len() as u32); + } + + #[test] + fn peers_paged_retrieval_obeys_limits() { + let mut deps = init_contract(); + let limit = 2; + + fill_peers(deps.as_mut(), 1000); + + let page1 = query_peers_paged(deps.as_ref(), None, Option::from(limit)).unwrap(); + assert_eq!(limit, page1.peers.len() as u32); + + remove_peers(deps.as_mut(), 1000); + } + + #[test] + fn peers_paged_retrieval_has_default_limit() { + let mut deps = init_contract(); + + fill_peers(deps.as_mut(), 1000); + + // query without explicitly setting a limit + let page1 = query_peers_paged(deps.as_ref(), None, None).unwrap(); + + assert_eq!(PEERS_PAGE_DEFAULT_LIMIT, page1.peers.len() as u32); + + remove_peers(deps.as_mut(), 1000); + } + + #[test] + fn peers_paged_retrieval_has_max_limit() { + let mut deps = init_contract(); + + // query with a crazily high limit in an attempt to use too many resources + let crazy_limit = 1000 * PEERS_PAGE_MAX_LIMIT; + + fill_peers(deps.as_mut(), 1000); + + let page1 = query_peers_paged(deps.as_ref(), None, Option::from(crazy_limit)).unwrap(); + + // we default to a decent sized upper bound instead + let expected_limit = PEERS_PAGE_MAX_LIMIT; + assert_eq!(expected_limit, page1.peers.len() as u32); + + remove_peers(deps.as_mut(), 1000); + } + + #[test] + fn peers_pagination_works() { + let mut deps = init_contract(); + + let per_page = 2; + + fill_peers(deps.as_mut(), 1); + let page1 = query_peers_paged(deps.as_ref(), None, Option::from(per_page)).unwrap(); + + // page should have 1 result on it + assert_eq!(1, page1.peers.len()); + remove_peers(deps.as_mut(), 1); + + fill_peers(deps.as_mut(), 2); + // page1 should have 2 results on it + let page1 = query_peers_paged(deps.as_ref(), None, Option::from(per_page)).unwrap(); + assert_eq!(2, page1.peers.len()); + remove_peers(deps.as_mut(), 2); + + fill_peers(deps.as_mut(), 3); + // page1 still has 2 results + let page1 = query_peers_paged(deps.as_ref(), None, Option::from(per_page)).unwrap(); + assert_eq!(2, page1.peers.len()); + + // retrieving the next page should start after the last key on this page + let start_after = page1.start_next_after.unwrap(); + let page2 = query_peers_paged( + deps.as_ref(), + Option::from(start_after.to_string()), + Option::from(per_page), + ) + .unwrap(); + + assert_eq!(1, page2.peers.len()); + remove_peers(deps.as_mut(), 3); + + fill_peers(deps.as_mut(), 4); + let page1 = query_peers_paged(deps.as_ref(), None, Option::from(per_page)).unwrap(); + let start_after = page1.start_next_after.unwrap(); + let page2 = query_peers_paged( + deps.as_ref(), + Option::from(start_after.to_string()), + Option::from(per_page), + ) + .unwrap(); + + // now we have 2 pages, with 2 results on the second page + assert_eq!(2, page2.peers.len()); + remove_peers(deps.as_mut(), 4); + } +} diff --git a/contracts/ephemera/src/peers/storage.rs b/contracts/ephemera/src/peers/storage.rs new file mode 100644 index 0000000000..b3a1734109 --- /dev/null +++ b/contracts/ephemera/src/peers/storage.rs @@ -0,0 +1,11 @@ +// Copyright 2023 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use cosmwasm_std::Addr; +use cw_storage_plus::Map; +use nym_ephemera_common::types::JsonPeerInfo; + +pub(crate) const PEERS_PAGE_MAX_LIMIT: u32 = 75; +pub(crate) const PEERS_PAGE_DEFAULT_LIMIT: u32 = 50; + +pub(crate) const PEERS: Map<'_, Addr, JsonPeerInfo> = Map::new("prs"); diff --git a/contracts/ephemera/src/peers/transactions.rs b/contracts/ephemera/src/peers/transactions.rs new file mode 100644 index 0000000000..6aa587bcb2 --- /dev/null +++ b/contracts/ephemera/src/peers/transactions.rs @@ -0,0 +1,63 @@ +// Copyright 2023 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use crate::error::ContractError; +use crate::peers::storage::PEERS; +use crate::state::STATE; +use cosmwasm_std::{DepsMut, MessageInfo, Response}; +use nym_ephemera_common::types::JsonPeerInfo; + +pub fn try_register_peer( + deps: DepsMut<'_>, + info: MessageInfo, + peer_info: JsonPeerInfo, +) -> Result { + if PEERS.may_load(deps.storage, info.sender.clone())?.is_none() { + if STATE + .load(deps.storage)? + .group_addr + .is_voting_member(&deps.querier, &info.sender, None)? + .is_some() + { + PEERS.save(deps.storage, info.sender, &peer_info)?; + Ok(Default::default()) + } else { + Err(ContractError::Unauthorized {}) + } + } else { + Err(ContractError::AlreadyRegistered) + } +} + +#[cfg(test)] +pub(crate) mod tests { + use super::*; + use crate::support::tests::fixtures::peer_fixture; + use crate::support::tests::helpers; + use crate::support::tests::helpers::GROUP_MEMBERS; + use cosmwasm_std::testing::mock_info; + use cw4::Member; + + #[test] + fn peer_registration() { + let mut deps = helpers::init_contract(); + let peer_info = peer_fixture("owner"); + let info = mock_info("owner", &[]); + + let ret = try_register_peer(deps.as_mut(), info.clone(), peer_info.clone()).unwrap_err(); + assert_eq!(ret, ContractError::Unauthorized); + + GROUP_MEMBERS.lock().unwrap().push(( + Member { + addr: "owner".to_string(), + weight: 10, + }, + 1, + )); + + try_register_peer(deps.as_mut(), info.clone(), peer_info.clone()).unwrap(); + + let ret = try_register_peer(deps.as_mut(), info, peer_info).unwrap_err(); + assert_eq!(ret, ContractError::AlreadyRegistered); + } +} diff --git a/contracts/ephemera/src/state/mod.rs b/contracts/ephemera/src/state/mod.rs new file mode 100644 index 0000000000..6dec1262d2 --- /dev/null +++ b/contracts/ephemera/src/state/mod.rs @@ -0,0 +1,15 @@ +// Copyright 2023 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use cw4::Cw4Contract; +use cw_storage_plus::Item; +use serde::{Deserialize, Serialize}; + +// unique items +pub const STATE: Item = Item::new("state"); + +#[derive(Serialize, Deserialize, Clone, PartialEq, Debug)] +pub struct State { + pub mix_denom: String, + pub group_addr: Cw4Contract, +} diff --git a/contracts/ephemera/src/support/mod.rs b/contracts/ephemera/src/support/mod.rs new file mode 100644 index 0000000000..070bbc8eed --- /dev/null +++ b/contracts/ephemera/src/support/mod.rs @@ -0,0 +1,5 @@ +// Copyright 2023 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +#[cfg(test)] +pub mod tests; diff --git a/contracts/ephemera/src/support/tests/fixtures.rs b/contracts/ephemera/src/support/tests/fixtures.rs new file mode 100644 index 0000000000..bf1f469b6d --- /dev/null +++ b/contracts/ephemera/src/support/tests/fixtures.rs @@ -0,0 +1,15 @@ +// Copyright 2023 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use cosmwasm_std::Addr; +use nym_ephemera_common::types::JsonPeerInfo; + +pub const TEST_MIX_DENOM: &str = "unym"; + +pub fn peer_fixture(cosmos_address: &str) -> JsonPeerInfo { + JsonPeerInfo { + cosmos_address: Addr::unchecked(cosmos_address), + ip_address: "127.0.0.1".to_string(), + public_key: "random_key".to_string(), + } +} diff --git a/contracts/ephemera/src/support/tests/helpers.rs b/contracts/ephemera/src/support/tests/helpers.rs new file mode 100644 index 0000000000..df665eba1e --- /dev/null +++ b/contracts/ephemera/src/support/tests/helpers.rs @@ -0,0 +1,74 @@ +// Copyright 2023 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use crate::contract::instantiate; +use cosmwasm_std::testing::{mock_dependencies, mock_env, mock_info, MockApi, MockQuerier}; +use cosmwasm_std::{ + from_binary, to_binary, ContractResult, Empty, MemoryStorage, OwnedDeps, QuerierResult, + SystemResult, WasmQuery, +}; +use cw4::{Cw4QueryMsg, Member, MemberListResponse, MemberResponse}; +use lazy_static::lazy_static; +use nym_ephemera_common::msg::InstantiateMsg; +use std::sync::Mutex; + +use super::fixtures::TEST_MIX_DENOM; + +pub const ADMIN_ADDRESS: &str = "admin address"; +pub const GROUP_CONTRACT: &str = "group contract address"; + +lazy_static! { + pub static ref GROUP_MEMBERS: Mutex> = Mutex::new(vec![]); +} + +fn querier_handler(query: &WasmQuery) -> QuerierResult { + let bin = match query { + WasmQuery::Smart { contract_addr, msg } => { + if contract_addr != GROUP_CONTRACT { + panic!("Not supported"); + } + match from_binary(msg) { + Ok(Cw4QueryMsg::Member { addr, at_height }) => { + let weight = GROUP_MEMBERS.lock().unwrap().iter().find_map(|(m, h)| { + if m.addr == addr { + if let Some(height) = at_height { + if height != *h { + return None; + } + } + Some(m.weight) + } else { + None + } + }); + to_binary(&MemberResponse { weight }).unwrap() + } + Ok(Cw4QueryMsg::ListMembers { .. }) => { + let members = GROUP_MEMBERS + .lock() + .unwrap() + .iter() + .map(|m| m.0.clone()) + .collect(); + to_binary(&MemberListResponse { members }).unwrap() + } + _ => panic!("Not supported"), + } + } + _ => panic!("Not supported"), + }; + SystemResult::Ok(ContractResult::Ok(bin)) +} + +pub fn init_contract() -> OwnedDeps> { + let mut deps = mock_dependencies(); + deps.querier.update_wasm(querier_handler); + let msg = InstantiateMsg { + group_addr: GROUP_CONTRACT.to_string(), + mix_denom: TEST_MIX_DENOM.to_string(), + }; + let env = mock_env(); + let info = mock_info(ADMIN_ADDRESS, &[]); + instantiate(deps.as_mut(), env, info, msg).unwrap(); + deps +} diff --git a/contracts/ephemera/src/support/tests/mod.rs b/contracts/ephemera/src/support/tests/mod.rs new file mode 100644 index 0000000000..44f93cb286 --- /dev/null +++ b/contracts/ephemera/src/support/tests/mod.rs @@ -0,0 +1,5 @@ +// Copyright 2023 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +pub mod fixtures; +pub mod helpers; diff --git a/envs/local.env b/envs/local.env index 7efbad719e..0fba848a3e 100644 --- a/envs/local.env +++ b/envs/local.env @@ -17,6 +17,7 @@ COCONUT_BANDWIDTH_CONTRACT_ADDRESS=n1lp5zex6685kd22agzskhqsylpnssxnweyuvsz4edr4p GROUP_CONTRACT_ADDRESS=n1rw8fw2mpcpzzq3jpa4e52ufawnmj5a4u68p35umvgskewuw0nlzsaa5w4m MULTISIG_CONTRACT_ADDRESS=n1rw8fw2mpcpzzq3jpa4e52ufawnmj5a4u68p35umvgskewuw0nlzsaa5w4m COCONUT_DKG_CONTRACT_ADDRESS=n1vwtgazgpancsfel04y7syc95ausmat47cjtldqzkdmx6phyrwx2qvkv32p +EPHEMERA_CONTRACT_ADDRESS=n1vwtgazgpancsfel04y7syc95ausmat47cjtldqzkdmx6phyrwx2qvkv32p REWARDING_VALIDATOR_ADDRESS=n1tfzd4qz3a45u8p4mr5zmzv66457uwjgcl05jdq STATISTICS_SERVICE_DOMAIN_ADDRESS="http://0.0.0.0" NYXD="http://127.0.0.1:26657" diff --git a/envs/mainnet.env b/envs/mainnet.env index 4092b50ef8..4bf406bd64 100644 --- a/envs/mainnet.env +++ b/envs/mainnet.env @@ -17,6 +17,7 @@ COCONUT_BANDWIDTH_CONTRACT_ADDRESS=n19lc9u84cz0yz3fww5283nucc9yvr8gsjmgeul0 GROUP_CONTRACT_ADDRESS=n1rw8fw2mpcpzzq3jpa4e52ufawnmj5a4u68p35umvgskewuw0nlzsaa5w4m MULTISIG_CONTRACT_ADDRESS=n19lc9u84cz0yz3fww5283nucc9yvr8gsjmgeul0 COCONUT_DKG_CONTRACT_ADDRESS=n19lc9u84cz0yz3fww5283nucc9yvr8gsjmgeul0 +EPHEMERA_CONTRACT_ADDRESS=n19lc9u84cz0yz3fww5283nucc9yvr8gsjmgeul0 REWARDING_VALIDATOR_ADDRESS=n10yyd98e2tuwu0f7ypz9dy3hhjw7v772q6287gy STATISTICS_SERVICE_DOMAIN_ADDRESS="https://mainnet-stats.nymte.ch:8090" NYXD="https://rpc.nymtech.net" diff --git a/envs/qa-qwerty.env b/envs/qa-qwerty.env index 219d9c7a6c..9080c9d37b 100644 --- a/envs/qa-qwerty.env +++ b/envs/qa-qwerty.env @@ -17,6 +17,7 @@ COCONUT_BANDWIDTH_CONTRACT_ADDRESS=n19d2nwj7fdhxqmyvgy8lf3ad49a6vmww4shryhrkj2mq GROUP_CONTRACT_ADDRESS=n1fqquzw4mk0pkamgr2ywt2v7h2j9nuyjjn4gvpy8zlpp6xn0uyuzqfm28l5 MULTISIG_CONTRACT_ADDRESS=n1gaq3666chd5348apj8cka8t2mckv7azp9espyr7wgpxyuzur5d0sazpysy COCONUT_DKG_CONTRACT_ADDRESS=n18yadscxw8v35dds7ksv3j0svmjh3h6e7tmxpadk96mvgz27zygkshuf4vs +EPHEMERA_CONTRACT_ADDRESS=n18yadscxw8v35dds7ksv3j0svmjh3h6e7tmxpadk96mvgz27zygkshuf4vs REWARDING_VALIDATOR_ADDRESS=n10yyd98e2tuwu0f7ypz9dy3hhjw7v772q6287gy SERVICE_PROVIDER_DIRECTORY_CONTRACT_ADDRESS=n1qsn2655eflc0nx2uwqtwyv5kad5dwm4c0gn72yr4q4de5r3jaz2slvqjgt NAME_SERVICE_CONTRACT_ADDRESS=n1cm2u5vfjd3zalfw0p65xyh4tcrw3hjlm0960gzhewga449h4mgas77mjkl diff --git a/envs/qa.env b/envs/qa.env index fbaa9fd556..82bb8b9d3c 100644 --- a/envs/qa.env +++ b/envs/qa.env @@ -17,9 +17,10 @@ COCONUT_BANDWIDTH_CONTRACT_ADDRESS=n1ghd753shjuwexxywmgs4xz7x2q732vcn7ty4yw GROUP_CONTRACT_ADDRESS=n1rw8fw2mpcpzzq3jpa4e52ufawnmj5a4u68p35umvgskewuw0nlzsaa5w4m MULTISIG_CONTRACT_ADDRESS=n17p9rzwnnfxcjp32un9ug7yhhzgtkhvl988qccs COCONUT_DKG_CONTRACT_ADDRESS=n17p9rzwnnfxcjp32un9ug7yhhzgtkhvl988qccs +EPHEMERA_CONTRACT_ADDRESS=n1fc7nakzuyfn2qzkclafcsc54asamnclg064962lwne40w2lq558qftzjza REWARDING_VALIDATOR_ADDRESS=n1tfzd4qz3a45u8p4mr5zmzv66457uwjgcl05jdq STATISTICS_SERVICE_DOMAIN_ADDRESS="http://0.0.0.0" NYXD="https://qa-validator.nymtech.net" NYM_API="https://qa-validator-api.nymtech.net/api/" -DKG_TIME_CONFIGURATION="600,300,300,60,60,1209600" \ No newline at end of file +DKG_TIME_CONFIGURATION="600,300,300,60,60,1209600" diff --git a/envs/sandbox.env b/envs/sandbox.env index 921bc5e215..95b0b4d49a 100644 --- a/envs/sandbox.env +++ b/envs/sandbox.env @@ -19,6 +19,7 @@ COCONUT_BANDWIDTH_CONTRACT_ADDRESS=n16a32stm6kknhq5cc8rx77elr66pygf2hfszw7wvpq74 GROUP_CONTRACT_ADDRESS=n1pd7kfgvr5tpcv0xnlv46c4jsq9jg2r799xxrcwqdm4l2jhq2pjwqrmz5ju MULTISIG_CONTRACT_ADDRESS=n14ph4e660eyqz0j36zlkaey4zgzexm5twkmjlqaequxr2cjm9eprqsmad6k COCONUT_DKG_CONTRACT_ADDRESS=n1ahg0erc2fs6xx3j5m8sfx3ryuzdjh6kf6qm9plsf865fltekyrfsesac6a +EPHEMERA_CONTRACT_ADDRESS="nymt1gwk6muhmzeuxje7df7rjvqwl2vex0kj4t2hwuzmyx5k62kfusu5qk4k5z4" STATISTICS_SERVICE_DOMAIN_ADDRESS="http://0.0.0.0" NYXD="https://sandbox-validator1.nymtech.net" diff --git a/ephemera/Cargo.toml b/ephemera/Cargo.toml new file mode 100644 index 0000000000..c831726134 --- /dev/null +++ b/ephemera/Cargo.toml @@ -0,0 +1,65 @@ +[package] +name = "ephemera" +version = "0.1.0" +edition = "2021" + +[[bin]] +name = "ephemera" +path = "bin/main.rs" + +[dependencies] +## internal +nym-task = { path = "../common/task" } + +actix-web = "4" +anyhow = { version = "1.0.66", features = ["backtrace"] } +array-bytes = "6.0.0" +async-trait = "0.1.59" +asynchronous-codec = "0.6.1" +blake2 = "0.10.6" +bs58 = "0.4.0" +bytes = "1.3.0" +cfg-if = "1.0.0" +chrono = { version = "0.4.24", default-features = false, features = ["clock"] } +clap = { version = "4.0.32", features = ["derive"] } +config = { version = "0.13", default-features = false, features = ["toml"] } +digest = "0.10.6" +dirs = "5.0.0" +futures = "0.3.18" +futures-util = "0.3.25" +lazy_static = "1.4.0" +libp2p = { version = "0.51.3", default-features = false, features = ["dns", "gossipsub", "kad", "macros", "noise", "request-response", "serde", "tcp", "tokio", "yamux"] } +libp2p-identity = "0.1.0" +log = "0.4.14" +lru = "0.10.0" +nym-config = { path = "../common/config" } +nym-ephemera-common = { path = "../common/cosmwasm-smart-contracts/ephemera" } +pretty_env_logger = "0.4" +refinery = { version = "0.8.7", features = ["rusqlite"], optional = true } +reqwest = { version = "0.11.6", features = ["json"] } +rocksdb = { version = "0.20.1", optional = true } +rusqlite = { version = "0.27.0", features = ["bundled"], optional = true } +serde = { version = "1.0", features = ["derive"] } +serde_derive = "1.0.149" +serde_json = "1.0.91" +thiserror = "1.0.37" +tokio = { version = "1", features = ["macros", "net","rt-multi-thread"] } +tokio-tungstenite = "0.18.0" +tokio-util = { version = "0.7.4", features = ["full"] } +toml = "0.7.0" +unsigned-varint = "0.7.1" +utoipa = { version = "3.0.1", features = ["actix_extras"] } +utoipa-swagger-ui = { version = "3.0.2", features = ["actix-web"] } +uuid = { version = "1.2.2", features = ["v4"] } + +# Temporary fix to https://github.com/bluejekyll/trust-dns/issues/1946 +enum-as-inner = "=0.5.1" + +[dev-dependencies] +assert_matches = "1.5.0" +rand = "0.8.5" + +[features] +default = ["sqlite_storage"] +rocksdb_storage = ["rocksdb"] +sqlite_storage = ["rusqlite", "refinery"] diff --git a/ephemera/README.md b/ephemera/README.md new file mode 100644 index 0000000000..5d4a9a6de7 --- /dev/null +++ b/ephemera/README.md @@ -0,0 +1,142 @@ +# Ephemera - reliable broadcast protocol implementation + +Ephemera does reliable broadcast for blocks. + +## Short Overview + +All Ephemera nodes accept messages submitted by clients. Node then gossips these to other nodes in the cluster. After certain interval, +a node collects messages and produces a block. Then it does reliable broadcast for the block with other nodes in the cluster. + +Ephemera doesn't have the concept of (decentralised) leader at the moment. It's up to an _Application_ to decide which block to use. +For example in case of Nym-Api, it is the first block submitted to a "Smart Contract". + +At the same time, the purpose of blocks is to reach consensus about which messages are included. It's just that Ephemera doesn't make the final decision, +instead it leaves that to an _Application_. + +## Main concepts + +- **Node** - a single instance of Ephemera. +- **Cluster** - a set of nodes participating in reliable broadcast. +- **EphemeraMessage** - a message submitted by a client. +- **Block** - a set of messages collected by a node. +- **Application(ABCI)** - a trait which Ephemera users implement to accept messages and blocks. + - check_tx + - check_block + - accept_block + +## How to run + +[README](../scripts/README.md) + +## HTTP API + +See [Rust](src/api/http/mod.rs) + +### Endpoints + +**NODE** +- `/ephemera/node/health` +- `/ephemera/node/config` + +**BLOCKS** +- `/ephemera/broadcast/block/{hash}` +- `/ephemera/broadcast/block/height/{height}` +- `/ephemera/broadcast/blocks/last` +- `/ephemera/broadcast/block/certificates/{hash}` +- `/ephemera/broadcast/block/broadcast_info/{hash}` + +**GROUP** +- `/ephemera/broadcast/group/info` + +**MESSAGES** +- `/ephemera/broadcast/submit_message` + +**DHT** +- `/ephemera/dht/query/{key}` +- `/ephemera/dht/store` + +## Rust API + +Almost identical to HTTP API. + +See [Rust](src/api/mod.rs) + +## Application(Ephemera ABCI) + +Cosmos style ABCI application hook +- `check_tx` +- `check_block` +- `deliver_block` + +See [Rust](src/api/application.rs) + +## Examples + +### Ephemera HTTP and WS external interfaces example/tests + +See [README.md](../examples/http-ws-sync/README.md) + +### Nym Api simulation + +See [README.md](../examples/nym-api/README.md) + +### http API example/tests + +See [README.md](../examples/cluster-http-api/README.md) + +### Membership over HTTP API example/tests + +See [README.md](../examples/members_provider_http/README.md) + +## About reliable broadcast and consensus + +In blockchain technology blocks have two main purposes: +1. To maintain chain of blocks, so that the validity of each block can be cryptographically verified by the previous blocks +2. As a unit of consensus, each block contains a set of transactions/messages/actions that are agreed upon by + the network. This set of transactions is chosen from the global set of all possible transactions that are pending. + We call the set of transactions in a block consensus because the set of nodes trying to achieve global shared state + agreed on this particular set of transactions. + +Ephemera is not a blockchain. But it uses blocks to agree on the set of transactions in a block. +But at the same time it doesn't behave like a blockchain consensus algorithm. +We may say that it allows each application that uses Ephemera to "propose" something what can be +afterwards to be used to achieve consensus. + +### In Summary + +1. Ephemera provides functionality to reach agreement on a single value between a set of nodes. +2. Ephemera also provides the concept of a block, which application can take advantage of to reach consensus externally. + +### Reliable broadcast, consensus and blocks + +In distributed systems(including byzantine), we try to solve the problem of reaching to a commons state between a set of nodes. + +One way to define this problem is using the following properties: +1. + 1) Agreement: All nodes agree on the same value.(TODO clarify) + 2) Consensus: All nodes agree on the same value.(TODO clarify) +2. Validity: All nodes agree on a value that is valid. +3. Termination: All nodes eventually agree on a value. + +Reliable broadcast ensures the properties of 1.1 and 1.2. It's left to a particular consensus algorithm to ensure the termination property. + +One important feature of consensus in blockchain is that it guarantees total ordering of transactions. +Reliable broadcast with blocks helps to ensure this total ordering. + +### Ephemera specific properties + +Because Ephemera doesn't use the idea of leader, we can say that it solves consensus partially. +It allows each instance to create a block. And then it's up to an application to decide which block to use. + +Also, as it doesn't implement a full consensus algorithm, it doesn't ensure the termination. +There's no algorithm in place what tries to reach a consensus about a single block globally and sequentially +in time. + +When a block contains a single message, then it's semantically equivalent to a reliable broadcast. + +But when a block contains multiple messages, then it can be part of a consensus process. Except that in Ephemera each node +can create a block. To achieve consensus in a more traditional sense, it needs an application help if more strict +consensus is required. + +For example, Nym-Api allows each node to create a block but uses external coordinator(a smart contract) +to decide which block to use. \ No newline at end of file diff --git a/ephemera/bin/main.rs b/ephemera/bin/main.rs new file mode 100644 index 0000000000..5f7d12976c --- /dev/null +++ b/ephemera/bin/main.rs @@ -0,0 +1,12 @@ +use clap::Parser; + +use ephemera::cli::Cli; +use ephemera::logging; + +#[tokio::main] +async fn main() -> anyhow::Result<()> { + logging::init(); + + Cli::parse().execute().await?; + Ok(()) +} diff --git a/ephemera/migrations/V1__block.sql b/ephemera/migrations/V1__block.sql new file mode 100644 index 0000000000..512f9c3af1 --- /dev/null +++ b/ephemera/migrations/V1__block.sql @@ -0,0 +1,24 @@ +CREATE TABLE IF NOT EXISTS blocks ( + id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, + block_hash TEXT NOT NULL UNIQUE, + height TEXT NOT NULL UNIQUE, + block BLOB NOT NULL +); + +CREATE TABLE IF NOT EXISTS block_certificates ( + id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, + block_hash TEXT NOT NULL UNIQUE, + certificates BLOB NOT NULL +); + +CREATE TABLE IF NOT EXISTS block_broadcast_group ( + id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, + block_hash TEXT NOT NULL UNIQUE, + members BLOB NOT NULL +); + +CREATE TABLE IF NOT EXISTS block_merkle_tree ( + id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, + block_hash TEXT NOT NULL UNIQUE, + merkle_tree BLOB NOT NULL +); \ No newline at end of file diff --git a/ephemera/src/api/application.rs b/ephemera/src/api/application.rs new file mode 100644 index 0000000000..4d605273f0 --- /dev/null +++ b/ephemera/src/api/application.rs @@ -0,0 +1,104 @@ +use log::trace; +use thiserror::Error; + +use crate::api::types::{ApiBlock, ApiEphemeraMessage}; + +#[derive(Debug, Clone, PartialEq)] +pub enum RemoveMessages { + /// Remove all messages from the mempool + All, + /// Remove only inclued messages from the mempool + Selected(Vec), +} + +#[derive(Debug, Clone, PartialEq)] +pub enum CheckBlockResult { + /// Accept the block + Accept, + /// Reject the block with a reason. + Reject, + /// Reject the block and remove messages from the mempool + RejectAndRemoveMessages(RemoveMessages), +} + +#[derive(Debug, Clone, PartialEq)] +pub struct CheckBlockResponse { + pub accept: bool, + pub reason: Option, +} + +#[derive(Error, Debug)] +pub enum Error { + //Just a placeholder for now + #[error("ApplicationError: {0}")] + Application(#[from] anyhow::Error), +} + +pub type Result = std::result::Result; + +/// Cosmos style ABCI application hook +/// +/// These functions should be relatively fast, as they are called synchronously by Ephemera main loop. +pub trait Application { + /// It's called when receiving a new message from network before adding it to the mempool. + /// It's up to the application to decide whether the message is valid or not. + /// Basic check could be for example signature verification. + /// + /// # Arguments + /// * `message` - message to be checked + /// + /// # Returns + /// * `true` - if the message is valid + /// * `false` - if the message is invalid + /// + /// # Errors + /// * `Error::General` - if there was an error during validation + fn check_tx(&self, message: ApiEphemeraMessage) -> Result; + + /// Ephemera produces new blocks with configured interval. + /// Application can decide whether to accept the block or not. + /// For example, if the block doesn't contain any transactions, it can be rejected. + /// + /// # Arguments + /// * `block` - block to be checked + /// + /// # Returns + /// * `CheckBlockResult::Accept` - if the block is valid + /// * `CheckBlockResult::Reject` - if the block is invalid + /// * `CheckBlockResult::RejectAndRemoveMessages` - if the block is invalid and some messages should be removed from the mempool + /// + /// # Errors + /// * `Error::General` - if there was an error during validation + fn check_block(&self, block: &ApiBlock) -> Result; + + /// Deliver Block is called after block is confirmed by Ephemera and persisted to the storage. + /// + /// # Arguments + /// * `block` - block to be delivered + /// + /// # Errors + /// * `Error::General` - if there was an error during validation + fn deliver_block(&self, block: ApiBlock) -> Result<()>; +} + +/// Dummy application which doesn't do any validation. +/// Might be useful for testing. +#[derive(Default)] +pub struct Dummy; + +impl Application for Dummy { + fn check_tx(&self, tx: ApiEphemeraMessage) -> Result { + trace!("check_tx: {tx:?}"); + Ok(true) + } + + fn check_block(&self, block: &ApiBlock) -> Result { + trace!("accept_block: {block:?}"); + Ok(CheckBlockResult::Accept) + } + + fn deliver_block(&self, block: ApiBlock) -> Result<()> { + trace!("deliver_block: {block:?}"); + Ok(()) + } +} diff --git a/ephemera/src/api/http/client.rs b/ephemera/src/api/http/client.rs new file mode 100644 index 0000000000..593ee5d0f6 --- /dev/null +++ b/ephemera/src/api/http/client.rs @@ -0,0 +1,497 @@ +use std::time::Duration; + +use thiserror::Error; + +use crate::api::types::{ApiBlockBroadcastInfo, ApiBroadcastInfo, ApiHealth}; +use crate::ephemera_api::{ + ApiBlock, ApiCertificate, ApiDhtQueryRequest, ApiDhtQueryResponse, ApiDhtStoreRequest, + ApiEphemeraConfig, ApiEphemeraMessage, ApiVerifyMessageInBlock, +}; + +#[derive(Error, Debug)] +pub enum Error { + #[error(transparent)] + Internal(#[from] reqwest::Error), + #[error("Unexpected response: {status} {body}")] + UnexpectedResponse { + status: reqwest::StatusCode, + body: String, + }, +} + +pub type Result = std::result::Result; + +/// Client to interact with the node over HTTP api. +pub struct Client { + pub(crate) client: reqwest::Client, + pub(crate) url: String, +} + +impl Client { + /// Create a http new client. + /// + /// # Arguments + /// * `url` - The url of the node api endpoint. + #[must_use] + pub fn new(url: String) -> Self { + let client = reqwest::Client::new(); + Self { client, url } + } + + /// Create a new client. + /// + /// # Arguments + /// * `url` - The url of the node api endpoint. + /// * `timeout_sec` - Request timeout in seconds. + /// + /// # Panics + /// If the client cannot be created. + #[must_use] + pub fn new_with_timeout(url: String, timeout_sec: u64) -> Self { + let client = reqwest::ClientBuilder::new() + .timeout(Duration::from_secs(timeout_sec)) + .build() + .unwrap(); + Self { client, url } + } + + /// Get the health of the node. + /// + /// # Example + /// ```no_run + /// use ephemera::ephemera_api::{Client, ApiHealth}; + /// + /// #[tokio::main] + ///async fn main() -> Result<(), Box> { + /// let client = Client::new("http://localhost:7000".to_string()); + /// let health = client.health().await.unwrap(); + /// Ok(()) + /// } + /// ``` + /// + /// # Returns + /// * [`ApiHealth`] - The health of the node. + /// + /// # Errors + /// If the request fails. + pub async fn health(&self) -> Result { + self.query("ephemera/node/health").await + } + + /// Get the block by hash. + /// + /// # Example + /// ```no_run + /// use ephemera::ephemera_api::{ApiBlock, Client}; + /// + /// #[tokio::main] + ///async fn main() -> Result<(), Box> { + /// let client = Client::new("http://localhost:7000".to_string()); + /// let block = client.get_block_by_hash("9D2LaY17rbnxfgKUbvcsJ5cB2BRHEd8fPJwsBnDHNGBX").await?; + /// Ok(()) + /// } + /// ``` + /// + /// # Returns + /// * Option<[`ApiBlock`]> - The block. + /// + /// # Errors + /// If the request fails. + pub async fn get_block_by_hash(&self, hash: &str) -> Result> { + let url = format!("ephemera/broadcast/block/{hash}",); + self.query_optional(&url).await + } + + /// Get the block certificates by hash. + /// + /// # Example + /// ```no_run + /// use ephemera::ephemera_api::{ApiCertificate, Client}; + /// + /// #[tokio::main] + /// async fn main() -> Result<(), Box> { + /// let client = Client::new("http://localhost:7000".to_string()); + /// let certificates = client.get_block_certificates("9D2LaY17rbnxfgKUbvcsJ5cB2BRHEd8fPJwsBnDHNGBX").await?; + /// Ok(()) + /// } + /// ``` + /// + /// # Arguments + /// * `hash` - The hash of the block. + /// + /// # Returns + /// * Option> - The block certificates. + /// + /// # Errors + /// If the request fails. + pub async fn get_block_certificates(&self, hash: &str) -> Result>> { + let url = format!("ephemera/broadcast/block/certificates/{hash}",); + self.query_optional(&url).await + } + + /// Get the block by height. + /// + /// # Example + /// ```no_run + /// use ephemera::ephemera_api::{ApiBlock, Client}; + /// + /// #[tokio::main] + /// async fn main() -> Result<(), Box> { + /// let client = Client::new("http://localhost:7000/".to_string()); + /// let block = client.get_block_by_height(1).await?; + /// Ok(()) + /// } + /// ``` + /// + /// # Arguments + /// * `height` - The height of the block. + /// + /// # Returns + /// * Option<[`ApiBlock`]> - The block. + /// + /// # Errors + /// If the request fails. + pub async fn get_block_by_height(&self, height: u64) -> Result> { + let url = format!("ephemera/broadcast/block/height/{height}",); + self.query_optional(&url).await + } + + /// Get the last block. + /// + /// # Example + /// ```no_run + /// use ephemera::ephemera_api::{ApiBlock, Client}; + /// + /// #[tokio::main] + /// async fn main() -> Result<(), Box> { + /// let client = Client::new("http://localhost:7000/".to_string()); + /// let block = client.get_last_block().await?; + /// Ok(()) + /// } + /// ``` + /// + /// # Returns + /// * [`ApiBlock`] - The last block. + /// + /// # Errors + /// If the request fails. + pub async fn get_last_block(&self) -> Result { + self.query("ephemera/broadcast/blocks/last").await + } + + /// Get the node configuration. + /// + /// # Example + /// ```no_run + /// use ephemera::ephemera_api::{ApiEphemeraConfig, Client}; + /// + /// #[tokio::main] + /// async fn main() -> Result<(), Box> { + /// let client = Client::new("http://localhost:7000/".to_string()); + /// let config = client.get_ephemera_config().await?; + /// Ok(()) + /// } + /// ``` + /// + /// # Returns + /// * [`ApiEphemeraConfig`] - The node configuration. + /// + /// # Errors + /// If the request fails. + pub async fn get_ephemera_config(&self) -> Result { + self.query("ephemera/node/config").await + } + + /// Submit a message to the node. + /// + /// # Example + /// ```no_run + /// use ephemera::ephemera_api::{ApiEphemeraMessage, Client}; + /// + /// #[tokio::main] + /// async fn main() -> Result<(), Box> { + /// let client = Client::new("http://localhost:7000/".to_string()); + /// let message = unimplemented!("See how to create a ApiEphemeraMessage"); + /// client.submit_message(message).await?; + /// Ok(()) + /// } + /// + /// ``` + /// + /// # Arguments + /// * `message` - The message to submit. + /// + /// # Errors + /// If the request fails. + pub async fn submit_message(&self, message: ApiEphemeraMessage) -> Result<()> { + let url = format!("{}/{}", self.url, "ephemera/broadcast/submit_message"); + let response = self.client.post(&url).json(&message).send().await?; + if response.status().is_success() { + Ok(()) + } else { + Err(Error::UnexpectedResponse { + status: response.status(), + body: response.text().await?, + }) + } + } + + ///Store Key Value pair in the DHT. + /// + /// # Example + ///```no_run + /// + /// #[tokio::main] + /// async fn main() -> Result<(), Box> { + /// use ephemera::ephemera_api::Client; + /// let client = Client::new("http://localhost:7000/".to_string()); + /// let request = unimplemented!("See how to create a ApiDhtStoreRequest"); + /// client.store_in_dht(request).await?; + /// Ok(()) + /// } + /// ``` + /// + /// # Arguments + /// * `request` - Key Value pair to store. + /// + /// # Errors + /// If the request fails. + pub async fn store_in_dht(&self, request: ApiDhtStoreRequest) -> Result<()> { + let url = format!("{}/{}", self.url, "ephemera/dht/store"); + let response = self.client.post(&url).json(&request).send().await?; + if response.status().is_success() { + Ok(()) + } else { + Err(Error::UnexpectedResponse { + status: response.status(), + body: response.text().await?, + }) + } + } + + ///Store Key Value pair in the DHT. + /// + /// # Example + ///```no_run + /// #[tokio::main] + /// async fn main() -> Result<(), Box> { + /// use ephemera::ephemera_api::Client; + /// let client = Client::new("http://localhost:7000/".to_string()); + /// let key = &[1, 2, 3]; + /// let value = &[4, 5, 6]; + /// client.store_in_dht_key_value(key, value).await?; + /// Ok(()) + /// } + /// ``` + /// # Arguments + /// * `key` - Key to use to store the value. + /// * `value` - Value to store. + /// + /// # Errors + /// If the request fails. + pub async fn store_in_dht_key_value(&self, key: &[u8], value: &[u8]) -> Result<()> { + let request = ApiDhtStoreRequest::new(key, value); + self.store_in_dht(request).await + } + + /// Query the DHT for a given key. + /// + /// # Example + ///```no_run + /// #[tokio::main] + /// async fn main() -> Result<(), Box> { + /// use ephemera::ephemera_api::{ApiDhtQueryRequest, Client}; + /// let client = Client::new("http://localhost:7000/".to_string()); + /// let request = ApiDhtQueryRequest::new(&[1, 2, 3]); + /// let response = client.query_dht(request).await?; + /// Ok(()) + /// } + /// ``` + /// + /// # Arguments + /// * `request` - Key to query. + /// + /// # Returns + /// * Option<[`ApiDhtQueryResponse`]> - The value stored in the DHT for the given key. + /// + /// # Errors + /// If the request fails. + pub async fn query_dht( + &self, + request: ApiDhtQueryRequest, + ) -> Result> { + let url = format!("ephemera/dht/query/{}", request.key_encoded()); + self.query_optional(&url).await + } + + /// Query the DHT for a given key. + /// + /// # Example + /// ```no_run + /// #[tokio::main] + /// async fn main() -> Result<(), Box> { + /// use ephemera::ephemera_api::Client; + /// let client = Client::new("http://localhost:7000/".to_string()); + /// let key = &[1, 2, 3]; + /// let response = client.query_dht_key(key).await?; + /// Ok(()) + /// } + /// ``` + /// + /// # Arguments + /// * `key` - Key to query. + /// + /// # Returns + /// * Option<[`ApiDhtQueryResponse`]> - The value stored in the DHT for the given key. + /// + /// # Errors + /// If the request fails. + pub async fn query_dht_key(&self, key: &[u8]) -> Result> { + let request = ApiDhtQueryRequest::new(key); + self.query_dht(request).await + } + + /// Get broadcast group info. + /// + /// # Example + /// ```no_run + /// use ephemera::ephemera_api::Client; + /// + /// #[tokio::main] + /// async fn main() -> Result<(), Box> { + /// let client = Client::new("http://localhost:7000/".to_string()); + /// let info = client.broadcast_info().await?; + /// Ok(()) + /// } + /// ``` + /// + /// # Returns + /// * [`ApiBroadcastInfo`] - The broadcast group info. + /// + /// # Errors + /// If the request fails. + pub async fn broadcast_info(&self) -> Result { + self.query("ephemera/broadcast/group/info").await + } + + /// Get block broadcast info + /// + /// # Example + /// ```no_run + /// use ephemera::ephemera_api::Client; + /// + /// #[tokio::main] + /// async fn main() -> Result<(), Box> { + /// let client = Client::new("http://localhost:7000/".to_string()); + /// let info = client.get_block_broadcast_info("hash").await?; + /// Ok(()) + /// } + ///``` + /// + /// # Arguments + /// * `hash` - Hash of the block to query. + /// + /// # Returns + /// * Some([`ApiBlockBroadcastInfo`]) - The block broadcast info. + /// * None - If the block is not found. + /// + /// # Errors + /// If the request fails. + pub async fn get_block_broadcast_info( + &self, + hash: &str, + ) -> Result> { + let url = format!("ephemera/broadcast/block/broadcast_info/{hash}",); + self.query_optional(&url).await + } + + /// Verifies if given message is in block identified by block hash + /// + /// # Example + /// ```no_run + /// use ephemera::ephemera_api::Client; + /// + /// #[tokio::main] + /// async fn main() -> Result<(), Box> { + /// let client = Client::new("http://localhost:7000/".to_string()); + /// let is_in_block = client.verify_message_in_block("block_hash", "message_hash", 0).await?; + /// Ok(()) + /// } + /// ``` + /// + /// # Arguments + /// * `block_hash` - Hash of the block to query. + /// * `message_hash` - Hash of the message to query. + /// * `message_index` - Index of the message in the block. + /// + /// # Returns + /// * bool - True if the message is in the block, false otherwise. + /// + /// # Errors + /// If the request fails. + pub async fn verify_message_in_block( + &self, + block_hash: &str, + message_hash: &str, + message_index: usize, + ) -> Result { + let request = ApiVerifyMessageInBlock::new( + block_hash.to_string(), + message_hash.to_string(), + message_index, + ); + + let url = format!("{}/{}", self.url, "ephemera/messages/verify"); + let response = self.client.post(&url).json(&request).send().await?; + if response.status().is_success() { + let body = response.json::().await?; + Ok(body) + } else { + Err(Error::UnexpectedResponse { + status: response.status(), + body: response.text().await?, + }) + } + } + + async fn query_optional serde::Deserialize<'de>>( + &self, + path: &str, + ) -> Result> { + let url = format!("{}/{}", self.url, path); + match self.client.get(&url).send().await { + Ok(response) => { + if response.status().is_success() { + let body = response.json::().await?; + Ok(Some(body)) + } else if response.status() == reqwest::StatusCode::NOT_FOUND { + Ok(None) + } else { + return Err(Error::UnexpectedResponse { + status: response.status(), + body: response.text().await?, + }); + } + } + Err(err) => Err(err.into()), + } + } + + async fn query serde::Deserialize<'de>>(&self, path: &str) -> Result { + let url = format!("{}/{}", self.url, path); + match self.client.get(&url).send().await { + Ok(response) => { + if response.status().is_success() { + let body = response.json::().await?; + Ok(body) + } else { + Err(Error::UnexpectedResponse { + status: response.status(), + body: response.text().await?, + }) + } + } + Err(err) => Err(err.into()), + } + } +} diff --git a/ephemera/src/api/http/mod.rs b/ephemera/src/api/http/mod.rs new file mode 100644 index 0000000000..010e397aec --- /dev/null +++ b/ephemera/src/api/http/mod.rs @@ -0,0 +1,88 @@ +use actix_web::{dev::Server, http::KeepAlive, web::Data, App, HttpServer}; +use log::info; +use utoipa::OpenApi; +use utoipa_swagger_ui::SwaggerUi; + +use crate::api::CommandExecutor; +use crate::core::builder::NodeInfo; + +pub(crate) mod client; +pub(crate) mod query; +pub(crate) mod submit; + +/// Starts the HTTP server. +pub(crate) fn init(node_info: &NodeInfo, api: CommandExecutor) -> anyhow::Result { + print_startup_messages(node_info); + + let server = HttpServer::new(move || { + App::new() + .app_data(Data::new(api.clone())) + .service(query::health) + .service(query::block_by_hash) + .service(query::block_certificates) + .service(query::block_by_height) + .service(query::block_broadcast_group) + .service(query::last_block) + .service(query::node_config) + .service(query::query_dht) + .service(query::broadcast_info) + .service(submit::submit_message) + .service(submit::store_in_dht) + .service(submit::verify_message_in_block) + .service(swagger_ui()) + }) + .keep_alive(KeepAlive::Os) + .bind((node_info.ip.as_str(), node_info.initial_config.http.port))? + .run(); + Ok(server) +} + +/// Builds the Swagger UI. +/// +/// Note that all routes you want Swagger docs for must be in the `paths` annotation. +fn swagger_ui() -> SwaggerUi { + use crate::api::types; + #[derive(OpenApi)] + #[openapi( + paths( + query::health, + query::block_by_hash, + query::block_certificates, + query::block_by_height, + query::last_block, + query::block_broadcast_group, + query::node_config, + query::query_dht, + query::broadcast_info, + submit::submit_message, + submit::store_in_dht, + submit::verify_message_in_block + ), + components(schemas( + types::ApiBlock, + types::ApiEphemeraMessage, + types::ApiCertificate, + types::ApiSignature, + types::ApiPublicKey, + types::ApiHealth, + types::HealthStatus, + types::ApiEphemeraConfig, + types::ApiDhtStoreRequest, + types::ApiDhtQueryRequest, + types::ApiDhtQueryResponse, + types::ApiBroadcastInfo, + types::ApiVerifyMessageInBlock, + )) + )] + struct ApiDoc; + SwaggerUi::new("/swagger-ui/{_:.*}").url("/api-doc/openapi.json", ApiDoc::openapi()) +} + +/// Prints messages saying which ports HTTP is running on, and some helpful pointers +/// `OpenAPI` and `Swagger UI` endpoints. +fn print_startup_messages(info: &NodeInfo) { + let http_root = info.api_address_http(); + info!("Server running on {}", http_root); + info!("Swagger UI: {}/swagger-ui/", http_root); + info!("OpenAPI spec is at: {}/api-doc/openapi.json", http_root); +} diff --git a/ephemera/src/api/http/query.rs b/ephemera/src/api/http/query.rs new file mode 100644 index 0000000000..cb7f53288e --- /dev/null +++ b/ephemera/src/api/http/query.rs @@ -0,0 +1,182 @@ +use actix_web::{get, web, HttpResponse, Responder}; +use log::error; + +use crate::{ + api::{types::ApiHealth, types::HealthStatus::Healthy, CommandExecutor}, + ephemera_api::{ApiDhtQueryRequest, ApiDhtQueryResponse}, +}; + +#[utoipa::path( +responses( +(status = 200, description = "Endpoint to check if the server is running")), +)] +#[get("/ephemera/node/health")] +#[allow(clippy::unused_async)] +pub(crate) async fn health() -> impl Responder { + HttpResponse::Ok().json(ApiHealth { status: Healthy }) +} + +#[utoipa::path( +responses( +(status = 200, description = "Get current broadcast group"), +(status = 500, description = "Server failed to process request")), +)] +#[get("/ephemera/broadcast/group/info")] +pub(crate) async fn broadcast_info(api: web::Data) -> impl Responder { + match api.get_broadcast_info().await { + Ok(group) => HttpResponse::Ok().json(group), + Err(err) => { + error!("Failed to get current broadcast group: {err}",); + HttpResponse::InternalServerError().json("Server failed to process request") + } + } +} + +#[utoipa::path( +responses( +(status = 200, description = "GET block by hash"), +(status = 404, description = "Block not found"), +(status = 500, description = "Server failed to process request")), +params(("hash", description = "Block hash")), +)] +#[get("/ephemera/broadcast/block/{hash}")] +pub(crate) async fn block_by_hash( + hash: web::Path, + api: web::Data, +) -> impl Responder { + match api.get_block_by_id(hash.into_inner()).await { + Ok(Some(block)) => HttpResponse::Ok().json(block), + Ok(_) => HttpResponse::NotFound().json("Block not found"), + Err(err) => { + error!("Failed to get block by hash: {err}",); + HttpResponse::InternalServerError().json("Server failed to process request") + } + } +} + +#[utoipa::path( +responses( +(status = 200, description = "Get block signatures"), +(status = 404, description = "Certificates not found"), +(status = 500, description = "Server failed to process request")), +params(("hash", description = "Block hash")), +)] +#[get("/ephemera/broadcast/block/certificates/{hash}")] +pub(crate) async fn block_certificates( + hash: web::Path, + api: web::Data, +) -> impl Responder { + let id = hash.into_inner(); + match api.get_block_certificates(id.clone()).await { + Ok(Some(signatures)) => HttpResponse::Ok().json(signatures), + Ok(_) => HttpResponse::NotFound().json("Certificates not found"), + Err(err) => { + error!("Failed to get signatures {err}",); + HttpResponse::InternalServerError().json("Server failed to process request") + } + } +} + +#[utoipa::path( +responses( +(status = 200, description = "Get block by height"), +(status = 404, description = "Block not found"), +(status = 500, description = "Server failed to process request")), +params(("height", description = "Block height")), +)] +#[get("/ephemera/broadcast/block/height/{height}")] +pub(crate) async fn block_by_height( + height: web::Path, + api: web::Data, +) -> impl Responder { + match api.get_block_by_height(height.into_inner()).await { + Ok(Some(block)) => HttpResponse::Ok().json(block), + Ok(_) => HttpResponse::NotFound().json("Block not found"), + Err(err) => { + error!("Failed to get block {err}",); + HttpResponse::InternalServerError().json("Server failed to process request") + } + } +} + +#[utoipa::path( +responses( +(status = 200, description = "Get last block"), +(status = 500, description = "Server failed to process request")), +)] +//Need to use plural(blocks), otherwise overlaps with block_by_id route +#[get("/ephemera/broadcast/blocks/last")] +pub(crate) async fn last_block(api: web::Data) -> impl Responder { + match api.get_last_block().await { + Ok(block) => HttpResponse::Ok().json(block), + Err(err) => { + error!("Failed to get block {err}",); + HttpResponse::InternalServerError().json("Server failed to process request") + } + } +} + +#[utoipa::path( +responses( +(status = 200, description = "Get block broadcast group"), +(status = 404, description = "Block not found"), +(status = 500, description = "Server failed to process request")), +params(("hash", description = "Block hash")), +)] +#[get("/ephemera/broadcast/block/broadcast_info/{hash}")] +pub(crate) async fn block_broadcast_group( + hash: web::Path, + api: web::Data, +) -> impl Responder { + let hash = hash.into_inner(); + match api.get_block_broadcast_info(hash).await { + Ok(Some(group)) => HttpResponse::Ok().json(group), + Ok(_) => HttpResponse::NotFound().json("Block not found"), + Err(err) => { + error!("Failed to get block broadcast group {err}",); + HttpResponse::InternalServerError().json("Server failed to process request") + } + } +} + +#[utoipa::path( +responses( +(status = 200, description = "Get node config"), +(status = 500, description = "Server failed to process request")), +)] +#[get("/ephemera/node/config")] +pub(crate) async fn node_config(api: web::Data) -> impl Responder { + match api.get_node_config().await { + Ok(config) => HttpResponse::Ok().json(config), + Err(err) => { + error!("Failed to get node config {err}",); + HttpResponse::InternalServerError().json("Server failed to process request") + } + } +} + +#[utoipa::path( +responses( +(status = 200, description = "Query dht"), +(status = 500, description = "Server failed to process request")), +params(("query", description = "Dht query")), +)] +#[get("/ephemera/dht/query/{key}")] +pub(crate) async fn query_dht( + api: web::Data, + key: web::Path, +) -> impl Responder { + let key = ApiDhtQueryRequest::parse_key(key.into_inner().as_str()); + + match api.query_dht(key).await { + Ok(Some((key, value))) => { + let response = ApiDhtQueryResponse::new(key, value); + HttpResponse::Ok().json(response) + } + Ok(_) => HttpResponse::NotFound().json("Not found"), + Err(err) => { + error!("Failed to query dht {err}",); + HttpResponse::InternalServerError().json("Server failed to process request") + } + } +} diff --git a/ephemera/src/api/http/submit.rs b/ephemera/src/api/http/submit.rs new file mode 100644 index 0000000000..7142c2ad9b --- /dev/null +++ b/ephemera/src/api/http/submit.rs @@ -0,0 +1,88 @@ +use actix_web::{post, web, HttpResponse}; +use log::{debug, error}; + +use crate::api::types::ApiVerifyMessageInBlock; +use crate::api::{ + types::{ApiDhtStoreRequest, ApiEphemeraMessage}, + ApiError, CommandExecutor, +}; + +#[utoipa::path( +request_body = ApiEphemeraMessage, +responses( +(status = 200, description = "Send a message to an Ephemera node which will be broadcast to the network"), +(status = 500, description = "Server failed to process request")), +params(("message", description = "Message to send")) +)] +#[post("/ephemera/broadcast/submit_message")] +pub(crate) async fn submit_message( + message: web::Json, + api: web::Data, +) -> HttpResponse { + match api.send_ephemera_message(message.into_inner()).await { + Ok(_) => HttpResponse::Ok().json("Message submitted"), + Err(err) => { + if let ApiError::DuplicateMessage = err { + debug!("Message already submitted {err:?}"); + HttpResponse::BadRequest().json("Message already submitted") + } else { + error!("Error submitting message: {}", err); + HttpResponse::InternalServerError().json("Server failed to process request") + } + } + } +} + +#[utoipa::path( +request_body = ApiDhtStoreRequest, +responses( +(status = 200, description = "Request to store a value in the DHT"), +(status = 500, description = "Server failed to process request")), +params( +("request", description = "Dht store request") +) +)] +#[post("/ephemera/dht/store")] +pub(crate) async fn store_in_dht( + request: web::Json, + api: web::Data, +) -> HttpResponse { + let request = request.into_inner(); + + let key = request.key(); + let value = request.value(); + + match api.store_in_dht(key, value).await { + Ok(_) => HttpResponse::Ok().json("Store request submitted"), + Err(err) => { + error!("Error storing in dht: {}", err); + HttpResponse::InternalServerError().json("Server failed to process request") + } + } +} + +#[utoipa::path( +request_body = ApiVerifyMessageInBlock, +responses( +(status = 200, description = "Verifies if given message is in block identified by block hash.\ +Returns true if message is in block, false otherwise. False can also mean that block or message \ +does not exist in that block."), +(status = 500, description = "Server failed to process request")), +params( +("request", description = "Verify message request") +) +)] +#[post("/ephemera/messages/verify")] +pub(crate) async fn verify_message_in_block( + request: web::Json, + api: web::Data, +) -> HttpResponse { + let request = request.into_inner(); + match api.verify_message_in_block(request).await { + Ok(valid) => HttpResponse::Ok().json(valid), + Err(err) => { + error!("Error verifying message: {}", err); + HttpResponse::InternalServerError().json("Server failed to process request") + } + } +} diff --git a/ephemera/src/api/mod.rs b/ephemera/src/api/mod.rs new file mode 100644 index 0000000000..ae2b4f6de4 --- /dev/null +++ b/ephemera/src/api/mod.rs @@ -0,0 +1,309 @@ +//! # Ephemera API +//! +//! This module contains all the types and functions available as part of Ephemera public API. +//! +//! This API is also available over HTTP. + +use std::fmt::Display; + +use log::{error, trace}; +use tokio::sync::{ + mpsc::{channel, Receiver, Sender}, + oneshot, +}; + +use crate::api::types::{ + ApiBlock, ApiBlockBroadcastInfo, ApiBroadcastInfo, ApiCertificate, ApiEphemeraConfig, + ApiEphemeraMessage, ApiError, ApiVerifyMessageInBlock, +}; + +pub(crate) mod application; +pub(crate) mod http; +pub(crate) mod types; + +/// Kademlia DHT key +pub(crate) type DhtKey = Vec; + +/// Kademlia DHT value +pub(crate) type DhtValue = Vec; + +/// Kademlia DHT key/value pair +pub(crate) type DhtKV = (DhtKey, DhtValue); + +pub(crate) type Result = std::result::Result; + +#[derive(Debug)] +pub(crate) enum ToEphemeraApiCmd { + SubmitEphemeraMessage(Box, oneshot::Sender>), + QueryBlockByHeight(u64, oneshot::Sender>>), + QueryBlockByHash(String, oneshot::Sender>>), + QueryLastBlock(oneshot::Sender>), + QueryBlockCertificates(String, oneshot::Sender>>>), + QueryDht(DhtKey, oneshot::Sender>>), + StoreInDht(DhtKey, DhtValue, oneshot::Sender>), + QueryEphemeraConfig(oneshot::Sender>), + QueryBroadcastGroup(oneshot::Sender>), + QueryBlockBroadcastInfo( + String, + oneshot::Sender>>, + ), + VerifyMessageInBlock(String, String, usize, oneshot::Sender>), +} + +impl Display for ToEphemeraApiCmd { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + ToEphemeraApiCmd::SubmitEphemeraMessage(message, _) => { + write!(f, "SubmitEphemeraMessage({message})",) + } + ToEphemeraApiCmd::QueryBlockByHeight(height, _) => { + write!(f, "QueryBlockByHeight({height})",) + } + ToEphemeraApiCmd::QueryBlockByHash(hash, _) => write!(f, "QueryBlockByHash({hash})",), + ToEphemeraApiCmd::QueryLastBlock(_) => write!(f, "QueryLastBlock"), + ToEphemeraApiCmd::QueryBlockCertificates(id, _) => { + write!(f, "QueryBlockSignatures{id}") + } + ToEphemeraApiCmd::QueryDht(_, _) => { + write!(f, "QueryDht") + } + ToEphemeraApiCmd::StoreInDht(_, _, _) => { + write!(f, "StoreInDht") + } + ToEphemeraApiCmd::QueryEphemeraConfig(_) => { + write!(f, "EphemeraConfig") + } + ToEphemeraApiCmd::QueryBroadcastGroup(_) => { + write!(f, "BroadcastGroup") + } + ToEphemeraApiCmd::QueryBlockBroadcastInfo(hash, ..) => { + write!(f, "BlockBroadcastInfo({hash})") + } + ToEphemeraApiCmd::VerifyMessageInBlock(block_id, message_id, height, _) => { + write!( + f, + "VerifyMessageInBlock({block_id}, {message_id}, {height})", + ) + } + } + } +} + +pub(crate) struct ApiListener { + pub(crate) messages_rcv: Receiver, +} + +impl ApiListener { + pub(crate) fn new(messages_rcv: Receiver) -> Self { + Self { messages_rcv } + } +} + +#[derive(Clone)] +pub struct CommandExecutor { + pub(crate) commands_channel: Sender, +} + +impl CommandExecutor { + pub(crate) fn new() -> (CommandExecutor, ApiListener) { + let (commands_channel, signed_messages_rcv) = channel(100); + let api_listener = ApiListener::new(signed_messages_rcv); + let api = CommandExecutor { commands_channel }; + (api, api_listener) + } + + /// Returns block with given id if it exists + /// + /// # Arguments + /// * `block_id` - Block id + /// + /// # Returns + /// * `ApiBlock` - Block + /// + /// # Errors + /// * `ApiError::InternalError` - If there is an internal error + pub async fn get_block_by_id(&self, block_id: String) -> Result> { + trace!("get_block_by_id({:?})", block_id); + self.send_and_wait_response(|tx| ToEphemeraApiCmd::QueryBlockByHash(block_id, tx)) + .await + } + + /// Returns block with given height if it exists + /// + /// # Arguments + /// * `height` - Block height + /// + /// # Returns + /// * `ApiBlock` - Block + /// + /// # Errors + /// * `ApiError::InternalError` - If there is an internal error + pub async fn get_block_by_height(&self, height: u64) -> Result> { + trace!("get_block_by_height({:?})", height); + self.send_and_wait_response(|tx| ToEphemeraApiCmd::QueryBlockByHeight(height, tx)) + .await + } + + /// Returns last block. Which has maximum height and is stored in database + /// + /// # Returns + /// * `ApiBlock` - Last block + /// + /// # Errors + /// * `ApiError::InternalError` - If there is an internal error + pub async fn get_last_block(&self) -> Result { + trace!("get_last_block()"); + self.send_and_wait_response(ToEphemeraApiCmd::QueryLastBlock) + .await + } + + /// Returns signatures for given block id + /// + /// # Arguments + /// * `block_hash` - Block id + /// + /// # Returns + /// * `Vec` - Certificates + /// + /// # Errors + /// * `ApiError::InternalError` - If there is an internal error + pub async fn get_block_certificates( + &self, + block_hash: String, + ) -> Result>> { + trace!("get_block_certificates({block_hash:?})",); + self.send_and_wait_response(|tx| ToEphemeraApiCmd::QueryBlockCertificates(block_hash, tx)) + .await + } + + /// Queries DHT for given key + /// + /// # Arguments + /// * `key` - DHT key + /// + /// # Errors + /// * `ApiError::InternalError` - If there is an internal error + /// + /// # Returns + /// * `Some((key, value))` - If key is found + /// * `None` - If key is not found + pub async fn query_dht(&self, key: DhtKey) -> Result> { + trace!("get_dht({key:?})"); + //TODO: this needs timeout(somewhere around dht query functionality) + self.send_and_wait_response(|tx| ToEphemeraApiCmd::QueryDht(key, tx)) + .await + } + + /// Stores given key-value pair in DHT + /// + /// # Arguments + /// * `key` - DHT key + /// * `value` - DHT value + /// + /// # Errors + /// * `ApiError::InternalError` - If there is an internal error + pub async fn store_in_dht(&self, key: DhtKey, value: DhtValue) -> Result<()> { + trace!("store_in_dht({key:?}, {value:?})"); + self.send_and_wait_response(|tx| ToEphemeraApiCmd::StoreInDht(key, value, tx)) + .await + } + + /// Returns node configuration + /// + /// # Returns + /// * `ApiEphemeraConfig` - Node configuration + /// + /// # Errors + /// * `ApiError::InternalError` - If there is an internal error + pub async fn get_node_config(&self) -> Result { + trace!("get_node_config()"); + self.send_and_wait_response(ToEphemeraApiCmd::QueryEphemeraConfig) + .await + } + + /// Returns broadcast group + /// + /// # Errors + /// * `ApiError::InternalError` - If there is an internal error + /// + /// # Return + /// * `ApiBroadcastInfo` - Broadcast group + pub async fn get_broadcast_info(&self) -> Result { + trace!("get_broadcast_group()"); + self.send_and_wait_response(ToEphemeraApiCmd::QueryBroadcastGroup) + .await + } + + /// Returns block broadcast info. + /// + /// # Arguments + /// * `block_hash` - Block hash + /// + /// # Return + /// * `ApiBlockBroadcastInfo` - Block broadcast info + /// + /// # Errors + /// * `ApiError::InternalError` - If there is an internal error + pub async fn get_block_broadcast_info( + &self, + block_hash: String, + ) -> Result> { + trace!("get_broadcast_group()"); + self.send_and_wait_response(|tx| ToEphemeraApiCmd::QueryBlockBroadcastInfo(block_hash, tx)) + .await + } + + /// Send a message to Ephemera which should then be included in mempool and broadcast to all peers + /// + /// # Arguments + /// * `message` - Message to be sent + /// + /// # Errors + /// * `ApiError::InternalError` - If there is an internal error + pub async fn send_ephemera_message(&self, message: ApiEphemeraMessage) -> Result<()> { + trace!("send_ephemera_message({message})",); + self.send_and_wait_response(|tx| { + ToEphemeraApiCmd::SubmitEphemeraMessage(message.into(), tx) + }) + .await + } + + /// Verifies if given message is in block identified by block hash + /// Returns true if message is in block, false otherwise. False can also mean that block or message + /// does not exist. + /// + /// # Arguments + /// * `request` - Message and block hash + /// + /// # Errors + /// * `ApiError::InternalError` - If there is an internal error + pub async fn verify_message_in_block(&self, request: ApiVerifyMessageInBlock) -> Result { + trace!("verify_message_in_block({request})",); + let block_hash = request.block_hash; + let message_hash = request.message_hash; + let index = request.message_index; + self.send_and_wait_response(|tx| { + ToEphemeraApiCmd::VerifyMessageInBlock(block_hash, message_hash, index, tx) + }) + .await + } + + async fn send_and_wait_response(&self, f: F) -> Result + where + F: FnOnce(oneshot::Sender>) -> ToEphemeraApiCmd, + R: Send + 'static, + { + let (tx, rcv) = oneshot::channel(); + let cmd = f(tx); + if let Err(e) = self.commands_channel.send(cmd).await { + error!("Failed to send command to Ephemera: {e:?}",); + return Err(ApiError::Internal( + "Failed to receive response from Ephemera".to_string(), + )); + } + rcv.await.map_err(|e| { + error!("Failed to receive response from Ephemera: {e:?}",); + ApiError::Internal("Failed to receive response from Ephemera".to_string()) + })? + } +} diff --git a/ephemera/src/api/types.rs b/ephemera/src/api/types.rs new file mode 100644 index 0000000000..cfd99a300d --- /dev/null +++ b/ephemera/src/api/types.rs @@ -0,0 +1,659 @@ +//! This module contains all the types that are used in the API. +//! +//! - `ApiEphemeraMessage` +//! - `RawApiEphemeraMessage` +//! - `ApiBlock` +//! - `ApiCertificate` +//! - `Health` +//! - `ApiError` +//! - `ApiEphemeraConfig` +//! - `ApiDhtQueryRequest` +//! - `ApiDhtQueryResponse` +//! - `ApiDhtStoreRequest` +//! - `ApiBroadcastInfo` +//! - `ApiBlockBroadcastInfo` +//! - `ApiVerifyMessageInBlock` + +use std::collections::HashSet; +use std::fmt::Display; + +use array_bytes::{bytes2hex, hex2bytes}; +use log::error; +use serde::{Deserialize, Serialize}; +use thiserror::Error; +use utoipa::ToSchema; + +use crate::peer::{PeerId, ToPeerId}; +use crate::utilities::codec::{Codec, DecodingError, EncodingError, EphemeraCodec}; +use crate::{ + block::types::{block::Block, block::BlockHeader, message::EphemeraMessage}, + codec::{Decode, Encode}, + crypto::{Keypair, PublicKey}, + ephemera_api, + utilities::{ + crypto::{Certificate, Signature}, + time::EphemeraTime, + }, +}; + +#[derive(Error, Debug)] +pub enum ApiError { + #[error("Application rejected ephemera message")] + ApplicationRejectedMessage, + #[error("Duplicate message")] + DuplicateMessage, + #[error("Invalid hash: {0}")] + InvalidHash(String), + #[error("ApplicationError: {0}")] + Application(#[from] ephemera_api::ApplicationError), + #[error("Internal error: {0}")] + Internal(String), +} + +/// # Ephemera message. +/// +/// A message submitted to an Ephemera node will be gossiped to other nodes. +/// And will be eventually included in a Ephemera block. +/// +/// It needs to signed by the sender. The signature is included in the certificate. +/// +/// The fields of the message what are signed: +/// - timestamp +/// - label +/// - data +/// +/// Currently it's up provided [`ephemera_api::application::Application`] to verify the signature. +#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ToSchema)] +pub struct ApiEphemeraMessage { + /// The timestamp of the message. + pub timestamp: u64, + /// The label of the message. It can be used to identify the type of a message for example. + pub label: String, + /// The data of the message. It is application specific. + pub data: Vec, + /// The certificate of the message. All messages are required to be signed. + pub certificate: ApiCertificate, +} + +impl ApiEphemeraMessage { + #[must_use] + pub fn new(raw_message: RawApiEphemeraMessage, certificate: ApiCertificate) -> Self { + Self { + timestamp: raw_message.timestamp, + label: raw_message.label, + data: raw_message.data, + certificate, + } + } + + /// Generates the message hash. + /// + /// # Errors + /// - If internal hash function fails. + pub fn hash(&self) -> anyhow::Result { + let em = EphemeraMessage::from(self.clone()); + let hash = em.hash_with_default_hasher()?.to_string(); + Ok(hash) + } +} + +/// `RawApiEphemeraMessage` contains the fields of the `ApiEphemeraMessage` that are signed. +#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ToSchema)] +pub struct RawApiEphemeraMessage { + /// The timestamp of the message. It's initialized when the message is created. + /// It uses UTC time. + pub timestamp: u64, + /// The label of the message. It can be used to identify the type of a message without decoding full data. + pub label: String, + /// The data of the message. It is application specific. + pub data: Vec, +} + +impl RawApiEphemeraMessage { + #[must_use] + pub fn new(label: String, data: Vec) -> Self { + Self { + timestamp: EphemeraTime::now(), + label, + data, + } + } + + /// Signs the message with the given keypair. + /// + /// # Signing example + /// + /// ``` + /// use ephemera::codec::Encode; + /// use ephemera::crypto::{EphemeraKeypair, EphemeraPublicKey, Keypair}; + /// use ephemera::ephemera_api::{ApiEphemeraMessage, RawApiEphemeraMessage}; + /// + /// let keypair = Keypair::generate(None); + /// let raw_message = RawApiEphemeraMessage::new("test".to_string(), vec![]); + /// + /// let signed_message:ApiEphemeraMessage = raw_message.sign(&keypair).unwrap(); + /// + /// assert_eq!(signed_message.certificate.public_key, keypair.public_key().into()); + /// + /// let bytes = raw_message.encode().unwrap(); + /// assert!(keypair.public_key().verify(&bytes, &signed_message.certificate.signature.into())); + /// ``` + /// + /// # Errors + /// - If the message can't be encoded. + /// - If the message can't be signed. + pub fn sign(&self, keypair: &Keypair) -> anyhow::Result { + let certificate = Certificate::prepare(keypair, &self)?; + let message = ApiEphemeraMessage::new(self.clone(), certificate.into()); + Ok(message) + } +} + +#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] +pub struct ApiBlockHeader { + /// The timestamp of the block. It's initialized when the block is created. + /// It uses UTC time. + pub timestamp: u64, + /// The PeerId of the block producer instance. + pub creator: PeerId, + /// The height of the block. + pub height: u64, + /// The hash of the current block. + pub hash: String, +} + +#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ToSchema)] +pub struct ApiBlock { + pub header: ApiBlockHeader, + pub messages: Vec, +} + +#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] +pub struct ApiRawBlock { + pub(crate) header: ApiBlockHeader, + pub(crate) messages: Vec, +} + +#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ToSchema)] +pub struct ApiCertificate { + pub signature: ApiSignature, + pub public_key: ApiPublicKey, +} + +#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ToSchema)] +pub struct ApiSignature(pub(crate) Signature); + +#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ToSchema)] +pub struct ApiPublicKey(pub(crate) PublicKey); + +#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ToSchema)] +pub struct ApiEphemeraConfig { + /// The address of the node. It's the address what Ephemera instance uses to communicate with other nodes. + pub protocol_address: String, + /// The HTTP API address of the node. + pub api_address: String, + /// The WebSocket address of the node. + pub websocket_address: String, + /// Node's public key. + /// + /// # Converting to string and back example + /// ``` + /// use ephemera::crypto::{EphemeraKeypair, Keypair, PublicKey}; + /// + /// let keypair = Keypair::generate(None); + /// let public_key = keypair.public_key().to_string(); + /// + /// let from_str = public_key.parse::().unwrap(); + /// + /// assert_eq!(keypair.public_key(), from_str); + /// ``` + pub public_key: String, + /// True if the node is a block producer. It's a configuration option. + pub block_producer: bool, + /// The interval of block creation in seconds. It's a configuration option. + pub block_creation_interval_sec: u64, +} + +#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ToSchema)] +pub struct ApiDhtQueryRequest { + /// The key to query for in hex format. + key: String, +} + +#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ToSchema)] +pub struct ApiDhtQueryResponse { + /// The key that was queried for in hex format. + key: String, + /// The value that was stored under the queried key in hex format. + value: String, +} + +#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ToSchema)] +pub enum HealthStatus { + Healthy, + Unhealthy, +} + +#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ToSchema)] +pub struct ApiHealth { + pub(crate) status: HealthStatus, +} + +#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ToSchema)] +pub struct ApiDhtStoreRequest { + /// The key to store the value under in hex format. + key: String, + /// The value to store in hex format. + value: String, +} + +#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ToSchema)] +pub struct ApiBroadcastInfo { + /// The PeerId of the local node. + pub local_peer_id: PeerId, + /// The list of the current members of the network. + pub current_members: HashSet, +} + +#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ToSchema)] +pub struct ApiBlockBroadcastInfo { + pub local_peer_id: PeerId, + pub broadcast_group: Vec, +} + +#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ToSchema)] +pub struct ApiVerifyMessageInBlock { + pub block_hash: String, + pub message_hash: String, + pub message_index: usize, +} + +impl ApiVerifyMessageInBlock { + #[must_use] + pub fn new(block_hash: String, message_hash: String, message_index: usize) -> Self { + Self { + block_hash, + message_hash, + message_index, + } + } +} + +impl Display for ApiVerifyMessageInBlock { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, + "{{ block_hash: {}, message_hash: {} }}", + self.block_hash, self.message_hash + ) + } +} + +impl ApiBlockBroadcastInfo { + pub(crate) fn new(local_peer_id: PeerId, broadcast_group: Vec) -> Self { + Self { + local_peer_id, + broadcast_group, + } + } +} + +impl ApiBroadcastInfo { + pub(crate) fn new(current_members: HashSet, local_peer_id: PeerId) -> Self { + Self { + local_peer_id, + current_members, + } + } +} + +impl Display for ApiBroadcastInfo { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let current_members = self.current_members.iter().map(ToString::to_string); + write!( + f, + "{{ local_peer_id: {}, current_members: {current_members:?} }}", + self.local_peer_id, + ) + } +} + +impl Display for ApiEphemeraMessage { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, + "ApiEphemeraMessage(timestamp: {}, label: {})", + self.timestamp, self.label, + ) + } +} + +impl From for RawApiEphemeraMessage { + fn from(message: ApiEphemeraMessage) -> Self { + RawApiEphemeraMessage { + timestamp: message.timestamp, + label: message.label, + data: message.data, + } + } +} + +impl From for EphemeraMessage { + fn from(message: ApiEphemeraMessage) -> Self { + Self { + timestamp: message.timestamp, + label: message.label, + data: message.data, + certificate: message.certificate.into(), + } + } +} + +impl Decode for RawApiEphemeraMessage { + type Output = Self; + + fn decode(bytes: &[u8]) -> Result { + Codec::decode(bytes) + } +} + +impl Encode for RawApiEphemeraMessage { + fn encode(&self) -> Result, EncodingError> { + Codec::encode(self) + } +} + +impl Encode for &RawApiEphemeraMessage { + fn encode(&self) -> Result, EncodingError> { + Codec::encode(self) + } +} + +impl Display for ApiBlockHeader { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, + "ApiBlockHeader(timestamp: {}, creator: {}, height: {}, hash: {})", + self.timestamp, self.creator, self.height, self.hash, + ) + } +} + +impl ApiBlock { + #[must_use] + pub fn as_raw_block(&self) -> ApiRawBlock { + ApiRawBlock { + header: self.header.clone(), + messages: self.messages.clone(), + } + } + + #[must_use] + pub fn message_count(&self) -> usize { + self.messages.len() + } + + #[must_use] + pub fn hash(&self) -> String { + self.header.hash.clone() + } + + /// # Errors + /// - If the block is invalid. + /// - If the block's certificate is invalid. + /// - If the block's certificate is not signed by the block's creator. + pub fn verify(&self, certificate: &ApiCertificate) -> Result { + let block: Block = self.clone().try_into()?; + let valid = block.verify(&(certificate.clone()).into()).map_err(|e| { + error!("Failed to verify block: {}", e); + ApiError::Internal("Failed to verify block certificate".to_string()) + })?; + Ok(valid) + } +} + +impl Display for ApiBlock { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, + "ApiBlock(header: {}, message_count: {})", + self.header, + self.message_count() + ) + } +} + +impl ApiRawBlock { + pub fn new(header: ApiBlockHeader, messages: Vec) -> Self { + Self { header, messages } + } +} + +impl ApiCertificate { + /// # Errors + /// - `EncodingError` if the message cannot be encoded. + /// - `KeyPairError` if the message cannot be signed. + pub fn prepare(key_pair: &Keypair, data: &D) -> anyhow::Result { + Certificate::prepare(key_pair, data).map(Into::into) + } + + /// # Errors + /// -`EncodingError` if the message cannot be encoded. + pub fn verify(&self, data: &D) -> anyhow::Result { + let certificate: Certificate = (self.clone()).into(); + Certificate::verify(&certificate, data) + } +} + +impl From for ApiEphemeraMessage { + fn from(ephemera_message: EphemeraMessage) -> Self { + Self { + timestamp: ephemera_message.timestamp, + label: ephemera_message.label, + data: ephemera_message.data, + certificate: ApiCertificate { + signature: ephemera_message.certificate.signature.into(), + public_key: ephemera_message.certificate.public_key.into(), + }, + } + } +} + +impl From for ApiCertificate { + fn from(signature: Certificate) -> Self { + Self { + signature: signature.signature.into(), + public_key: signature.public_key.into(), + } + } +} + +impl From for Certificate { + fn from(value: ApiCertificate) -> Self { + Certificate { + signature: value.signature.into(), + public_key: value.public_key.into(), + } + } +} + +impl From<&Block> for &ApiBlock { + fn from(block: &Block) -> Self { + let api_block: ApiBlock = block.clone().into(); + Box::leak(Box::new(api_block)) + } +} + +impl From for ApiSignature { + fn from(signature: Signature) -> Self { + Self(signature) + } +} + +impl From for Signature { + fn from(signature: ApiSignature) -> Self { + signature.0 + } +} + +impl ApiPublicKey { + pub fn peer_id(&self) -> String { + self.0.peer_id().to_string() + } +} + +impl From for ApiPublicKey { + fn from(public_key: PublicKey) -> Self { + Self(public_key) + } +} + +impl From for PublicKey { + fn from(public_key: ApiPublicKey) -> Self { + public_key.0 + } +} + +impl From for ApiBlock { + fn from(block: Block) -> Self { + Self { + header: ApiBlockHeader { + timestamp: block.header.timestamp, + creator: block.header.creator, + height: block.header.height, + hash: block.header.hash.to_string(), + }, + messages: block.messages.into_iter().map(Into::into).collect(), + } + } +} + +impl TryFrom for Block { + type Error = ApiError; + + fn try_from(api_block: ApiBlock) -> Result { + let messages: Vec = api_block + .messages + .into_iter() + .map(Into::into) + .collect::>(); + Ok(Self { + header: BlockHeader { + timestamp: api_block.header.timestamp, + creator: api_block.header.creator, + height: api_block.header.height, + hash: api_block.header.hash.parse().map_err(|e| { + error!("Failed to parse block hash: {}", e); + ApiError::Internal("Failed to parse block hash".to_string()) + })?, + }, + messages, + }) + } +} + +impl ApiDhtStoreRequest { + #[must_use] + pub fn new(key: &[u8], value: &[u8]) -> Self { + let key = bytes2hex("0x", key); + let value = bytes2hex("0x", value); + Self { key, value } + } + + #[allow(clippy::missing_panics_doc)] + #[must_use] + pub fn key(&self) -> Vec { + //We can unwrap here because the key is always valid. + hex2bytes(&self.key).unwrap() + } + + #[allow(clippy::missing_panics_doc)] + #[must_use] + pub fn value(&self) -> Vec { + //We can unwrap here because the value is always valid. + hex2bytes(&self.value).unwrap() + } +} + +impl ApiDhtQueryRequest { + #[must_use] + pub fn new(key: &[u8]) -> Self { + let key = bytes2hex("0x", key); + Self { key } + } + + #[must_use] + pub fn key_encoded(&self) -> String { + self.key.clone() + } + + #[allow(clippy::missing_panics_doc)] + #[must_use] + pub fn key(&self) -> Vec { + //We can unwrap here because the value is always valid. + hex2bytes(&self.key).unwrap() + } + + pub(crate) fn parse_key(key: &str) -> Vec { + hex2bytes(key).unwrap() + } +} + +impl ApiDhtQueryResponse { + pub(crate) fn new(key: Vec, value: Vec) -> Self { + let key = bytes2hex("0x", key); + let value = bytes2hex("0x", value); + Self { key, value } + } + + #[allow(clippy::missing_panics_doc)] + #[must_use] + pub fn key(&self) -> Vec { + //We can unwrap here because the key is always valid. + hex2bytes(&self.key).unwrap() + } + + #[allow(clippy::missing_panics_doc)] + #[must_use] + pub fn value(&self) -> Vec { + //We can unwrap here because the value is always valid. + hex2bytes(&self.value).unwrap() + } +} + +#[cfg(test)] +mod test { + use crate::crypto::EphemeraKeypair; + use crate::crypto::Keypair; + + use super::*; + + #[test] + fn test_message_sign_ok() { + let message_signing_keypair = Keypair::generate(None); + + let message = RawApiEphemeraMessage::new("test".to_string(), vec![1, 2, 3]); + let signed_message = message + .sign(&message_signing_keypair) + .expect("Failed to sign message"); + + let certificate = signed_message.certificate; + + assert!(certificate.verify(&message).unwrap()); + } + + #[test] + fn test_message_sign_fail() { + let message_signing_keypair = Keypair::generate(None); + + let message = RawApiEphemeraMessage::new("test1".to_string(), vec![1, 2, 3]); + let signed_message = message + .sign(&message_signing_keypair) + .expect("Failed to sign message"); + + let certificate = signed_message.certificate; + + let modified_message = RawApiEphemeraMessage::new("test2".to_string(), vec![1, 2, 3]); + assert!(!certificate.verify(&modified_message).unwrap()); + } +} diff --git a/ephemera/src/block/builder.rs b/ephemera/src/block/builder.rs new file mode 100644 index 0000000000..e74530ae81 --- /dev/null +++ b/ephemera/src/block/builder.rs @@ -0,0 +1,74 @@ +use std::collections::HashSet; +use std::{sync::Arc, time::Duration}; + +use log::{debug, info}; + +use crate::block::manager::State; +use crate::peer::ToPeerId; +use crate::{ + block::{ + manager::{BlockChainState, BlockManager}, + message_pool::MessagePool, + producer::BlockProducer, + types::block::Block, + }, + broadcast::signing::BlockSigner, + config::BlockManagerConfiguration, + crypto::Keypair, + storage::EphemeraDatabase, +}; + +pub(crate) struct BlockManagerBuilder { + config: BlockManagerConfiguration, + block_producer: BlockProducer, + keypair: Arc, +} + +impl BlockManagerBuilder { + pub(crate) fn new(config: BlockManagerConfiguration, keypair: Arc) -> Self { + let block_producer = BlockProducer::new(keypair.peer_id()); + Self { + config, + block_producer, + keypair, + } + } + + pub(crate) fn build( + self, + storage: &mut D, + ) -> anyhow::Result { + let mut most_recent_block = storage.get_last_block()?; + if most_recent_block.is_none() { + //Although Ephemera is not a blockchain(chain of historically dependent blocks), + //it's helpful to have some sort of notion of progress in time. So we use the concept of height. + //The genesis block helps to define the start of it. + + info!("No last block found in database. Creating genesis block."); + + let genesis_block = Block::new_genesis_block(self.block_producer.peer_id); + storage.store_block(&genesis_block, HashSet::new(), HashSet::new())?; + most_recent_block = Some(genesis_block); + } + + let last_created_block = most_recent_block.expect("Block should be present"); + debug!("Most recent block: {:?}", last_created_block); + + let block_signer = BlockSigner::new(self.keypair.clone()); + let message_pool = MessagePool::new(); + let block_chain_state = BlockChainState::new(last_created_block); + let block_creation_interval = + tokio::time::interval(Duration::from_secs(self.config.creation_interval_sec)); + + Ok(BlockManager { + config: self.config, + block_producer: self.block_producer, + block_signer, + message_pool, + block_chain_state, + state: State::Paused, + backoff: None, + block_creation_interval, + }) + } +} diff --git a/ephemera/src/block/manager.rs b/ephemera/src/block/manager.rs new file mode 100644 index 0000000000..481cd92418 --- /dev/null +++ b/ephemera/src/block/manager.rs @@ -0,0 +1,688 @@ +use std::collections::HashSet; +use std::future::Future; +use std::task::Poll; +use std::time::Duration; +use std::{ + num::NonZeroUsize, + pin::Pin, + task, + task::Poll::{Pending, Ready}, +}; + +use anyhow::anyhow; +use futures::Stream; +use futures_util::FutureExt; +use log::{debug, error, info, trace}; +use lru::LruCache; +use thiserror::Error; +use tokio::time; +use tokio::time::{Instant, Interval}; + +use crate::network::PeerId; +use crate::peer::ToPeerId; +use crate::{ + api::application::RemoveMessages, + block::{ + message_pool::MessagePool, + producer::BlockProducer, + types::{block::Block, message::EphemeraMessage}, + }, + broadcast::signing::BlockSigner, + config::BlockManagerConfiguration, + utilities::{crypto::Certificate, hash::Hash}, +}; + +pub(crate) type Result = std::result::Result; + +#[derive(Error, Debug)] +pub(crate) enum BlockManagerError { + #[error("Message is already in pool: {0}")] + DuplicateMessage(String), + //Just a placeholder for now + #[error("BlockManagerError: {0}")] + BlockManager(#[from] anyhow::Error), +} + +/// It helps to use atomic state management for new blocks. +pub(crate) struct BlockChainState { + pub(crate) last_blocks: LruCache, + /// Last block that we created. + /// It's not Option because we always have genesis block + last_produced_block: Option, + /// Last block that we accepted + /// It's not Option because we always have genesis block + last_committed_block: Block, +} + +impl BlockChainState { + pub(crate) fn new(last_committed_block: Block) -> Self { + Self { + //1000 is just a "big enough". + last_blocks: LruCache::new(NonZeroUsize::new(1000).unwrap()), + last_produced_block: None, + last_committed_block, + } + } + + fn mark_last_produced_block_as_committed(&mut self) { + self.last_committed_block = self + .last_produced_block + .take() + .expect("Block should be present"); + } + + fn is_last_produced_block(&self, hash: Hash) -> bool { + match self.last_produced_block.as_ref() { + Some(block) => block.get_hash() == hash, + None => false, + } + } + + fn is_last_produced_block_is_pending(&self) -> bool { + self.last_produced_block.is_some() + } + + fn next_block_height(&self) -> u64 { + self.last_committed_block.get_height() + 1 + } + + fn remove_last_produced_block(&mut self) -> Block { + self.last_produced_block + .take() + .expect("Block should be present") + } +} + +pub(crate) enum State { + Paused, + Running, +} + +#[derive(Debug)] +pub(crate) struct BackOffInterval { + /// Maximum number of attempts before this backoff expires. + maximum_times: u32, + /// Number of attempts that have been made so far. + nr_of_attempts: u32, + /// Backoff rate. Previous delay is multiplied by this rate to get next delay. + backoff_rate: u32, + /// Delay between before next attempt. + delay: Interval, +} + +impl BackOffInterval { + fn new(maximum_times: u32, backoff_rate: u32, initial_wait: Duration) -> Self { + let delay = time::interval_at(Instant::now() + initial_wait, initial_wait); + Self { + maximum_times, + nr_of_attempts: 0, + backoff_rate, + delay, + } + } + + fn is_expired(&self) -> bool { + self.nr_of_attempts >= self.maximum_times + } +} + +impl Future for BackOffInterval { + type Output = (); + + fn poll(mut self: Pin<&mut Self>, cx: &mut task::Context<'_>) -> Poll { + if self.nr_of_attempts >= self.maximum_times { + debug!("Backoff expired after {} attempts", self.nr_of_attempts); + return Pending; + } + + match Pin::new(&mut self.delay).poll_tick(cx) { + Ready(_) => { + self.nr_of_attempts += 1; + let next_tick = Instant::now() + + self.delay.period() * self.backoff_rate.pow(self.nr_of_attempts); + debug!("Backoff attempt: {}", self.nr_of_attempts); + self.delay = time::interval_at(next_tick, self.delay.period()); + Ready(()) + } + Pending => Pending, + } + } +} + +pub(crate) struct BlockManager { + pub(crate) config: BlockManagerConfiguration, + /// Block producer. Simple helper that creates blocks + pub(crate) block_producer: BlockProducer, + /// Message pool. Contains all messages that we received from the network and not included in any(committed) block yet. + pub(crate) message_pool: MessagePool, + /// Delay between block creation attempts. + pub(crate) block_creation_interval: Interval, + /// Backoff between block creation attempts. When `last_produced_block` is not committed during + /// certain time window, and normal delay is not passed yet, we use backoff delay to try again. + pub(crate) backoff: Option, + /// Signs and verifies blocks + pub(crate) block_signer: BlockSigner, + /// State management for new blocks + pub(crate) block_chain_state: BlockChainState, + /// Current state of the block manager + pub(crate) state: State, +} + +impl BlockManager { + pub(crate) fn on_new_message(&mut self, msg: EphemeraMessage) -> Result<()> { + trace!("Message received: {:?}", msg); + + let message_hash = msg.hash_with_default_hasher()?; + if self.message_pool.contains(&message_hash) { + return Err(BlockManagerError::DuplicateMessage( + message_hash.to_string(), + )); + } + + self.message_pool.add_message(msg)?; + Ok(()) + } + + pub(crate) fn on_block( + &mut self, + sender: &PeerId, + block: &Block, + certificate: &Certificate, + ) -> Result<()> { + let hash = block.hash_with_default_hasher()?; + + trace!( + "Received block: {:?} from peer {sender:?}", + block.get_hash() + ); + + //Reject blocks with invalid hash + if block.header.hash != hash { + return Err(anyhow!("Block hash is invalid: {} != {hash}", block.header.hash).into()); + } + + //Block signer should be also its sender + let signer_peer_id = certificate.public_key.peer_id(); + if *sender != signer_peer_id { + return Err(anyhow!( + "Block signer is not the block sender: {sender:?} != {signer_peer_id:?}", + ) + .into()); + } + + //Verify that block signature is valid + if self.block_signer.verify_block(block, certificate).is_err() { + return Err(anyhow!("Block signature is invalid: {hash}").into()); + } + + self.block_chain_state.last_blocks.put(hash, block.clone()); + Ok(()) + } + + pub(crate) fn sign_block(&mut self, block: &Block) -> Result { + let hash = block.hash_with_default_hasher()?; + + trace!("Signing block: {block}"); + + let certificate = self.block_signer.sign_block(block, &hash)?; + + trace!("Block certificate: {certificate:?}",); + + Ok(certificate) + } + + pub(crate) fn on_application_rejected_block( + &mut self, + messages_to_remove: RemoveMessages, + ) -> Result<()> { + debug!("Application rejected last created block"); + + let last_produced_block = self.block_chain_state.remove_last_produced_block(); + match messages_to_remove { + RemoveMessages::All => { + let messages = last_produced_block + .messages + .into_iter() + .map(Into::into) + .collect::>(); + + debug!("Removing block messages from pool: all: {messages:?}",); + self.message_pool.remove_messages(&messages)?; + } + RemoveMessages::Selected(messages) => { + debug!("Removing block messages from pool: selected: {messages:?}",); + let messages = messages.into_iter().map(Into::into).collect::>(); + self.message_pool.remove_messages(messages.as_slice())?; + } + }; + Ok(()) + } + + /// After a block gets committed, clear up mempool from its messages + pub(crate) fn on_block_committed(&mut self, block: &Block) -> Result<()> { + info!("Block committed: {}", block); + + let hash = &block.header.hash; + + if !self.block_chain_state.is_last_produced_block(*hash) { + let last_produced_block = self + .block_chain_state + .last_produced_block + .as_ref() + .expect("Last produced block should be present"); + log::error!( + "Received unexpected committed block: {hash}, was expecting: {}", + last_produced_block.get_hash() + ); + panic!("Received committed block which isn't last produced block, this is a bug!"); + } + + match self.message_pool.remove_messages(&block.messages) { + Ok(_) => { + self.block_chain_state + .mark_last_produced_block_as_committed(); + } + Err(e) => { + return Err(anyhow!("Failed to remove messages from mempool: {}", e).into()); + } + } + Ok(()) + } + + pub(crate) fn get_block_by_hash(&mut self, block_id: &Hash) -> Option { + self.block_chain_state.last_blocks.get(block_id).cloned() + } + + pub(crate) fn get_block_certificates(&mut self, hash: &Hash) -> Option<&HashSet> { + self.block_signer.get_block_certificates(hash) + } + + pub(crate) fn stop(&mut self) { + debug!("Stopping block creation"); + self.state = State::Paused; + self.backoff = None; + } + + pub(crate) fn start(&mut self) { + if !self.config.producer { + return; + } + if let State::Running = self.state { + return; + } + debug!("Starting block creation"); + self.state = State::Running; + self.block_creation_interval = + tokio::time::interval(Duration::from_secs(self.config.creation_interval_sec)); + } +} + +//Produces blocks at a predefined interval. +//If blocks will be actually broadcast depends on the application. +impl Stream for BlockManager { + type Item = (Block, Certificate); + + fn poll_next(mut self: Pin<&mut Self>, cx: &mut task::Context) -> Poll> { + //Optionally it is possible to turn off block production and let the node behave just as voter. + //For example for testing purposes. + if !self.config.producer { + return Pending; + } + + //It is dynamically turned off when node is not part of most recent broadcast group. + if let State::Paused = self.state { + return Pending; + } + + let is_previous_pending = self.block_chain_state.is_last_produced_block_is_pending(); + if !is_previous_pending { + self.backoff = None; + } + + if self.block_creation_interval.poll_tick(cx).is_pending() { + if let Some(mut backoff) = self.backoff.take() { + if backoff.is_expired() { + return Pending; + } + if backoff.poll_unpin(cx).is_pending() { + self.backoff = Some(backoff); + return Pending; + } + self.backoff = Some(backoff); + } else { + return Pending; + } + } else { + self.backoff = None; + } + + //If backoff is expired and we still don't have previous block committed + let repeat_previous = is_previous_pending && self.config.repeat_last_block_messages; + + let pending_messages = if repeat_previous { + let block = self + .block_chain_state + .last_produced_block + .clone() + .expect("Block should be present"); + + //Use only previous block messages but create new block with new timestamp. + debug!("Producing block with previous messages"); + block.messages + } else { + debug!("Producing block with new messages"); + self.message_pool.get_messages() + }; + + let new_height = self.block_chain_state.next_block_height(); + let created_block = self + .block_producer + .create_block(new_height, pending_messages); + + if let Ok(block) = created_block { + info!("Created block: {}", block); + + let hash = block.get_hash(); + self.block_chain_state.last_produced_block = Some(block.clone()); + self.block_chain_state.last_blocks.put(hash, block.clone()); + + let certificate = self + .block_signer + .sign_block(&block, &hash) + .expect("Failed to sign block"); + + if self.backoff.is_none() { + let backoff = BackOffInterval::new(100, 2, Duration::from_secs(10)); + self.backoff = Some(backoff); + } + + Ready(Some((block, certificate))) + } else { + error!("Error producing block: {:?}", created_block); + Pending + } + } +} + +#[cfg(test)] +mod test { + use std::sync::Arc; + use std::time::Duration; + + use assert_matches::assert_matches; + use futures_util::StreamExt; + + use crate::crypto::{EphemeraKeypair, Keypair}; + use crate::ephemera_api::RawApiEphemeraMessage; + + use super::*; + + #[tokio::test] + async fn test_add_message() { + let (mut manager, _) = block_manager_with_defaults(); + + let signed_message = message("test"); + let hash = signed_message.hash_with_default_hasher().unwrap(); + + manager.on_new_message(signed_message).unwrap(); + + assert!(manager.message_pool.contains(&hash)); + } + + #[tokio::test] + async fn test_add_duplicate_message() { + let (mut manager, _) = block_manager_with_defaults(); + + let signed_message = message("test"); + + manager.on_new_message(signed_message.clone()).unwrap(); + + assert_matches!( + manager.on_new_message(signed_message), + Err(BlockManagerError::DuplicateMessage(_)) + ); + } + + #[tokio::test] + async fn test_accept_valid_block() { + let (mut manager, peer_id) = block_manager_with_defaults(); + + let block = block(); + let certificate = manager.sign_block(&block).unwrap(); + + let result = manager.on_block(&peer_id, &block, &certificate); + + assert!(result.is_ok()); + } + + #[tokio::test] + async fn test_reject_invalid_sender() { + let (mut manager, _) = block_manager_with_defaults(); + + let block = block(); + let certificate = manager.sign_block(&block).unwrap(); + + let invalid_peer_id = PeerId::from_public_key(&Keypair::generate(None).public_key()); + let result = manager.on_block(&invalid_peer_id, &block, &certificate); + + assert!(result.is_err()); + } + + #[tokio::test] + async fn test_reject_invalid_hash() { + let (mut manager, peer_id) = block_manager_with_defaults(); + + let mut block = block(); + let certificate = manager.sign_block(&block).unwrap(); + + block.header.hash = Hash::new([0; 32]); + let result = manager.on_block(&peer_id, &block, &certificate); + + assert!(result.is_err()); + } + + #[tokio::test] + async fn test_reject_invalid_signature() { + let (mut manager, peer_id) = block_manager_with_defaults(); + + let correct_block = block(); + let fake_block = block(); + + let fake_certificate = manager.sign_block(&fake_block).unwrap(); + let correct_certificate = manager.sign_block(&correct_block).unwrap(); + + let result = manager.on_block(&peer_id, &correct_block, &fake_certificate); + assert!(result.is_err()); + + let result = manager.on_block(&peer_id, &fake_block, &correct_certificate); + assert!(result.is_err()); + } + + #[tokio::test] + async fn test_next_block_empty() { + let (mut manager, _) = block_manager_with_defaults(); + + let (block, _) = manager.next().await.unwrap(); + assert_eq!(block.header.height, 1); + assert!(block.messages.is_empty()); + } + + #[tokio::test] + async fn test_next_block_with_message() { + let (mut manager, _) = block_manager_with_defaults(); + + let signed_message = message("test"); + manager.on_new_message(signed_message).unwrap(); + + match manager.next().await { + Some((block, _)) => { + assert_eq!(block.header.height, 1); + assert_eq!(block.messages.len(), 1); + } + None => { + panic!("No block produced"); + } + } + } + + #[tokio::test] + async fn test_next_block_previous_not_committed_repeat() { + let (mut manager, _) = block_manager_with_defaults(); + + let signed_message = message("test"); + manager.on_new_message(signed_message).unwrap(); + + let (block1, _) = manager.next().await.unwrap(); + + let signed_message = message("test"); + manager.on_new_message(signed_message).unwrap(); + + let (block2, _) = manager.next().await.unwrap(); + + assert_eq!(block1.messages.len(), block2.messages.len()); + assert_eq!(block1.header.height, block2.header.height); + } + + #[tokio::test] + async fn test_next_block_previous_not_committed_repeat_false() { + let config = BlockManagerConfiguration::new(true, 0, false); + let (mut manager, _) = block_manager_with_config(config); + + let signed_message = message("test"); + manager.on_new_message(signed_message).unwrap(); + + let (block1, _) = manager.next().await.unwrap(); + + let signed_message = message("test"); + manager.on_new_message(signed_message).unwrap(); + + let (block2, _) = manager.next().await.unwrap(); + + assert_eq!(block1.messages.len(), 1); + assert_eq!(block2.messages.len(), 2); + //We create new block but don't leave gap + assert_eq!(block1.header.height, block2.header.height); + } + + #[tokio::test] + async fn test_on_committed_with_correct_pending_block() { + let (mut manager, _) = block_manager_with_defaults(); + + let signed_message = message("test"); + manager.on_new_message(signed_message).unwrap(); + + let (block, _) = manager.next().await.unwrap(); + + let result = manager.on_block_committed(&block); + + assert!(result.is_ok()); + assert!(manager.message_pool.get_messages().is_empty()); + } + + #[tokio::test] + #[should_panic] + async fn test_on_committed_with_invalid_pending_block() { + let (mut manager, _) = block_manager_with_defaults(); + + let signed_message = message("test"); + manager.on_new_message(signed_message).unwrap(); + + manager.next().await.unwrap(); + + //Create invalid block + let wrong_block = block(); + + //This shouldn't remove messages from the pool + manager.on_block_committed(&wrong_block).unwrap(); + } + + #[tokio::test] + async fn application_rejected_messages_all() { + let (mut manager, _) = block_manager_with_defaults(); + + //Add messages to pool + let signed_message = message("test"); + manager.on_new_message(signed_message).unwrap(); + + let signed_message = message("test"); + manager.on_new_message(signed_message).unwrap(); + + //Produce new block + manager.next().await.unwrap(); + + //Application Rejects the block with ALL messages + manager + .on_application_rejected_block(RemoveMessages::All) + .unwrap(); + + assert!(manager.message_pool.get_messages().is_empty()); + } + + #[tokio::test] + async fn application_rejected_messages_selected() { + let (mut manager, _) = block_manager_with_defaults(); + + //Add messages to pool + let signed_message1 = message("test"); + manager.on_new_message(signed_message1.clone()).unwrap(); + + let signed_message2 = message("test"); + manager.on_new_message(signed_message2.clone()).unwrap(); + + //Produce new block + manager.next().await.unwrap(); + + //Application Rejects the block with ALL messages + manager + .on_application_rejected_block(RemoveMessages::Selected(vec![signed_message2.into()])) + .unwrap(); + + assert_eq!(manager.message_pool.get_messages().len(), 1); + let message = manager + .message_pool + .get_messages() + .into_iter() + .next() + .unwrap(); + assert_eq!(message, signed_message1); + } + + fn block_manager_with_defaults() -> (BlockManager, PeerId) { + let config = BlockManagerConfiguration::new(true, 0, true); + block_manager_with_config(config) + } + + fn block_manager_with_config(config: BlockManagerConfiguration) -> (BlockManager, PeerId) { + let keypair: Arc = Keypair::generate(None).into(); + let peer_id = keypair.public_key().peer_id(); + let genesis_block = Block::new_genesis_block(peer_id); + let block_chain_state = BlockChainState::new(genesis_block); + ( + BlockManager { + config, + block_producer: BlockProducer::new(peer_id), + message_pool: MessagePool::new(), + block_creation_interval: tokio::time::interval(Duration::from_millis(1)), + backoff: None, + block_signer: BlockSigner::new(keypair), + block_chain_state, + state: State::Running, + }, + peer_id, + ) + } + + fn block() -> Block { + let keypair: Arc = Keypair::generate(None).into(); + let peer_id = keypair.public_key().peer_id(); + let mut producer = BlockProducer::new(peer_id); + producer.create_block(1, vec![]).unwrap() + } + + fn message(label: &str) -> EphemeraMessage { + let message1 = RawApiEphemeraMessage::new(label.into(), vec![1, 2, 3]); + let keypair = Keypair::generate(None); + let signed_message1 = message1.sign(&keypair).expect("Failed to sign message"); + signed_message1.into() + } +} diff --git a/ephemera/src/block/message_pool.rs b/ephemera/src/block/message_pool.rs new file mode 100644 index 0000000000..bf1e062be2 --- /dev/null +++ b/ephemera/src/block/message_pool.rs @@ -0,0 +1,87 @@ +//! Message pool for Ephemera messages +//! +//! It stores pending Ephemera messages which will be added to a future block. +//! It doesn't have any other logic than just storing messages. +//! +//! It's up to the user provided [`crate::ephemera_api::Application::check_tx`] to decide which messages to include. + +use std::collections::HashMap; + +use log::{trace, warn}; + +use crate::block::types::message::EphemeraMessage; +use crate::utilities::hash::Hash; + +pub(crate) struct MessagePool { + pending_messages: HashMap, +} + +impl MessagePool { + pub(super) fn new() -> Self { + Self { + pending_messages: HashMap::default(), + } + } + + pub(crate) fn contains(&self, hash: &Hash) -> bool { + self.pending_messages.contains_key(hash) + } + + pub(super) fn add_message(&mut self, msg: EphemeraMessage) -> anyhow::Result<()> { + trace!("Adding message to pool: {:?}", msg); + + let msg_hash = msg.hash_with_default_hasher()?; + + self.pending_messages.insert(msg_hash, msg); + + trace!("Message pool size: {:?}", self.pending_messages.len()); + Ok(()) + } + + pub(super) fn remove_messages(&mut self, messages: &[EphemeraMessage]) -> anyhow::Result<()> { + trace!( + "Mempool size before removing messages {}", + self.pending_messages.len() + ); + for msg in messages { + let hash = msg.hash_with_default_hasher()?; + if self.pending_messages.remove(&hash).is_none() { + warn!("Message not found in pool: {:?}", msg); + } + } + trace!( + "Mempool size after removing messages {}", + self.pending_messages.len() + ); + Ok(()) + } + + /// Returns a `Vec` of all `EphemeraMessage`s in the message pool. + /// The message pool is not cleared. + pub(super) fn get_messages(&self) -> Vec { + self.pending_messages.values().cloned().collect() + } +} + +#[cfg(test)] +mod test { + use crate::block::message_pool::MessagePool; + use crate::block::types::message::EphemeraMessage; + use crate::crypto::{EphemeraKeypair, Keypair}; + use crate::ephemera_api::RawApiEphemeraMessage; + + #[test] + fn test_add_remove() { + let keypair = Keypair::generate(None); + + let message = RawApiEphemeraMessage::new("test".to_string(), vec![1, 2, 3]); + let signed_message = message.sign(&keypair).expect("Failed to sign message"); + let signed_message: EphemeraMessage = signed_message.into(); + + let mut pool = MessagePool::new(); + pool.add_message(signed_message.clone()).unwrap(); + pool.remove_messages(&[signed_message]).unwrap(); + + assert_eq!(pool.get_messages().len(), 0); + } +} diff --git a/ephemera/src/block/mod.rs b/ephemera/src/block/mod.rs new file mode 100644 index 0000000000..b6d3684d80 --- /dev/null +++ b/ephemera/src/block/mod.rs @@ -0,0 +1,26 @@ +//! # Block manager +//! +//! Block manager is quite simple. It keeps pending messages in memory and puts all of them into a block +//! at predefined intervals. That's all it does. +//! +//! If the block actually will be broadcast or not is decided by the application. If not, it will produce next block with +//! the same messages plus the new ones. +//! +//! When application shuts down, pending messages are lost. +//! +//! When a block gets accepted by reliable broadcast then Block Manager will remove all messages included in the block from the +//! pending messages queue. +//! +//! # Synchronization and duplicate messages in sequence of blocks +//! +//! When previous block hasn't been accepted yet, then the next block will contain the same messages as the previous one. +//! One way to solve this is that an application itself keeps track of duplicate messages and discards them if necessary. +//! +//! But it seems a reasonable assumption that in general duplicate messages are unwanted. Therefore, Ephemera solves this +//! by dropping previous blocks which get Finalised/Committed after a new block has been created. + +pub(crate) mod builder; +pub(crate) mod manager; +pub(crate) mod message_pool; +pub(crate) mod producer; +pub(crate) mod types; diff --git a/ephemera/src/block/producer.rs b/ephemera/src/block/producer.rs new file mode 100644 index 0000000000..20f82bc328 --- /dev/null +++ b/ephemera/src/block/producer.rs @@ -0,0 +1,92 @@ +use crate::block::{ + types::block::{Block, RawBlock, RawBlockHeader}, + types::message::EphemeraMessage, +}; +use crate::peer::PeerId; +use log::trace; + +pub(crate) struct BlockProducer { + pub(crate) peer_id: PeerId, +} + +impl BlockProducer { + pub(super) fn new(peer_id: PeerId) -> Self { + Self { peer_id } + } + + pub(super) fn create_block( + &mut self, + height: u64, + pending_messages: Vec, + ) -> anyhow::Result { + trace!("Pending messages for new block: {:?}", pending_messages); + let block = self.new_block(height, pending_messages)?; + Ok(block) + } + + fn new_block(&self, height: u64, mut messages: Vec) -> anyhow::Result { + //Ordering is fundamental for block hash. Simple sort is fine for now. + messages.sort(); + + let raw_header = RawBlockHeader::new(self.peer_id, height); + let raw_block = RawBlock::new(raw_header, messages); + + //Better idea is probably combine header hash with Merkle tree root hash + let block_hash = raw_block.hash_with_default_hasher()?; + + let block = Block::new(raw_block, block_hash); + Ok(block) + } +} + +#[cfg(test)] +mod test { + use crate::crypto::{EphemeraKeypair, Keypair}; + use crate::ephemera_api::RawApiEphemeraMessage; + use std::cmp::Ordering; + + use super::*; + + #[test] + fn test_produce_block() { + let peer_id = PeerId::random(); + + let mut block_producer = BlockProducer::new(peer_id); + + let message = RawApiEphemeraMessage::new("test".to_string(), vec![1, 2, 3]); + let signed_message = message + .sign(&Keypair::generate(None)) + .expect("Failed to sign message"); + let signed_message1: EphemeraMessage = signed_message.into(); + + let message = RawApiEphemeraMessage::new("test".to_string(), vec![1, 2, 3]); + let signed_message = message + .sign(&Keypair::generate(None)) + .expect("Failed to sign message"); + let signed_message2: EphemeraMessage = signed_message.into(); + + let messages = vec![signed_message1.clone(), signed_message2.clone()]; + + let block = block_producer.create_block(1, messages).unwrap(); + + assert_eq!(block.header.height, 1); + assert_eq!(block.header.creator, peer_id); + assert_eq!(block.messages.len(), 2); + + //Nondeterministic because of timestamp + match signed_message1.cmp(&signed_message2) { + Ordering::Less => { + assert_eq!(block.messages[0], signed_message1); + assert_eq!(block.messages[1], signed_message2); + } + Ordering::Greater => { + assert_eq!(block.messages[0], signed_message2); + assert_eq!(block.messages[1], signed_message1); + } + + Ordering::Equal => { + panic!("Messages are equal"); + } + } + } +} diff --git a/ephemera/src/block/types/block.rs b/ephemera/src/block/types/block.rs new file mode 100644 index 0000000000..7b1538a5a5 --- /dev/null +++ b/ephemera/src/block/types/block.rs @@ -0,0 +1,327 @@ +use std::fmt::{Debug, Display}; + +use serde::{Deserialize, Serialize}; + +use crate::utilities::merkle::MerkleTree; +use crate::{ + block::types::message::EphemeraMessage, + codec::{Decode, Encode}, + crypto::Keypair, + peer::PeerId, + utilities::{ + codec::{Codec, DecodingError, EncodingError, EphemeraCodec}, + crypto::Certificate, + hash::{EphemeraHash, EphemeraHasher}, + hash::{Hash, Hasher}, + time::EphemeraTime, + }, +}; + +#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)] +pub(crate) struct BlockHeader { + pub(crate) timestamp: u64, + pub(crate) creator: PeerId, + pub(crate) height: u64, + pub(crate) hash: Hash, +} + +impl BlockHeader { + pub(crate) fn new(raw_header: &RawBlockHeader, hash: Hash) -> Self { + Self { + timestamp: raw_header.timestamp, + creator: raw_header.creator, + height: raw_header.height, + hash, + } + } +} + +impl Display for BlockHeader { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let hash = &self.hash; + let time = self.timestamp; + let creator = &self.creator; + let height = self.height; + write!( + f, + "hash: {hash}, timestamp: {time}, creator: {creator}, height: {height}", + ) + } +} + +impl Encode for BlockHeader { + fn encode(&self) -> Result, EncodingError> { + Codec::encode(&self) + } +} + +impl Decode for BlockHeader { + type Output = Self; + + fn decode(bytes: &[u8]) -> Result { + Codec::decode(bytes) + } +} + +impl EphemeraHash for BlockHeader { + fn hash(&self, state: &mut H) -> anyhow::Result<()> { + let bytes = Codec::encode(&self)?; + state.update(&bytes); + Ok(()) + } +} + +#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] +pub(crate) struct RawBlockHeader { + pub(crate) timestamp: u64, + pub(crate) creator: PeerId, + pub(crate) height: u64, +} + +impl RawBlockHeader { + pub(crate) fn new(creator: PeerId, height: u64) -> Self { + Self { + timestamp: EphemeraTime::now(), + creator, + height, + } + } + + pub(crate) fn hash_with_default_hasher(&self) -> anyhow::Result { + let mut hasher = Hasher::default(); + self.hash(&mut hasher)?; + let header_hash = hasher.finish().into(); + Ok(header_hash) + } +} + +impl Display for RawBlockHeader { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let creator = &self.creator; + let height = self.height; + write!(f, "creator: {creator}, height: {height}",) + } +} + +impl From for RawBlockHeader { + fn from(block_header: BlockHeader) -> Self { + Self { + timestamp: block_header.timestamp, + creator: block_header.creator, + height: block_header.height, + } + } +} + +#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)] +pub(crate) struct Block { + pub(crate) header: BlockHeader, + pub(crate) messages: Vec, +} + +impl Block { + pub(crate) fn new(raw_block: RawBlock, block_hash: Hash) -> Self { + let header = BlockHeader::new(&raw_block.header, block_hash); + Self { + header, + messages: raw_block.messages, + } + } + + pub(crate) fn get_hash(&self) -> Hash { + self.header.hash + } + + pub(crate) fn get_height(&self) -> u64 { + self.header.height + } + + pub(crate) fn new_genesis_block(creator: PeerId) -> Self { + let mut block = Self { + header: BlockHeader { + timestamp: EphemeraTime::now(), + creator, + height: 0, + hash: Hash::new([0; 32]), + }, + messages: Vec::new(), + }; + + let hash = block + .hash_with_default_hasher() + .expect("Failed to hash genesis block"); + block.header.hash = hash; + block + } + + pub(crate) fn sign(&self, keypair: &Keypair) -> anyhow::Result { + let raw_block: RawBlock = self.clone().into(); + let certificate = Certificate::prepare(keypair, &raw_block)?; + Ok(certificate) + } + + pub(crate) fn verify(&self, certificate: &Certificate) -> anyhow::Result { + let raw_block: RawBlock = self.clone().into(); + certificate.verify(&raw_block) + } + + pub(crate) fn hash_with_default_hasher(&self) -> anyhow::Result { + let raw_block: RawBlock = self.clone().into(); + raw_block.hash_with_default_hasher() + } + + pub(crate) fn merkle_tree(&self) -> anyhow::Result { + merkle_tree(&self.messages) + } +} + +impl Display for Block { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let header = &self.header; + write!(f, "{header}, nr of messages: {}", self.messages.len()) + } +} + +impl Encode for Block { + fn encode(&self) -> Result, EncodingError> { + Codec::encode(&self) + } +} + +impl Decode for Block { + type Output = Block; + + fn decode(bytes: &[u8]) -> Result { + Codec::decode(bytes) + } +} + +#[derive(Debug, Clone, Deserialize, Serialize)] +pub(crate) struct RawBlock { + pub(crate) header: RawBlockHeader, + pub(crate) messages: Vec, +} + +impl RawBlock { + pub(crate) fn new(header: RawBlockHeader, messages: Vec) -> Self { + Self { header, messages } + } + + pub(crate) fn hash_with_default_hasher(&self) -> anyhow::Result { + let header_hash = self.header.hash_with_default_hasher()?; + let merkle_root = merkle_tree(&self.messages)?.root_hash(); + let block_hash = Hasher::digest(&[header_hash.inner(), merkle_root.inner()].concat()); + Ok(block_hash.into()) + } +} + +impl From for RawBlock { + fn from(block: Block) -> Self { + Self { + header: block.header.into(), + messages: block.messages, + } + } +} + +impl Encode for RawBlockHeader { + fn encode(&self) -> Result, EncodingError> { + Codec::encode(&self) + } +} + +impl Decode for RawBlockHeader { + type Output = RawBlockHeader; + + fn decode(bytes: &[u8]) -> Result { + Codec::decode(bytes) + } +} + +impl Encode for RawBlock { + fn encode(&self) -> Result, EncodingError> { + Codec::encode(&self) + } +} + +impl Decode for RawBlock { + type Output = RawBlock; + + fn decode(bytes: &[u8]) -> Result { + Codec::decode(bytes) + } +} + +impl EphemeraHash for RawBlockHeader { + fn hash(&self, state: &mut H) -> anyhow::Result<()> { + state.update(&self.encode()?); + Ok(()) + } +} + +impl EphemeraHash for RawBlock { + fn hash(&self, state: &mut H) -> anyhow::Result<()> { + self.header.hash(state)?; + for message in &self.messages { + message.hash(state)?; + } + Ok(()) + } +} + +pub(crate) fn merkle_tree(messages: &[EphemeraMessage]) -> anyhow::Result { + let message_hashes = messages + .iter() + .map(EphemeraMessage::hash_with_default_hasher) + .collect::>>()?; + let merkle_tree = MerkleTree::build_tree(&message_hashes); + Ok(merkle_tree) +} + +#[cfg(test)] +mod test { + use crate::block::types::message::RawEphemeraMessage; + use crate::crypto::EphemeraKeypair; + + use super::*; + + #[test] + fn test_block_hash_no_messages() { + let block = Block::new_genesis_block(PeerId::random()); + let block_hash = block.hash_with_default_hasher().unwrap(); + assert_eq!(block_hash, block.get_hash()); + } + + #[test] + fn test_block_hash_with_messages() { + let messages = create_ephemera_messages(10); + let message_hashes = messages + .iter() + .map(EphemeraMessage::hash_with_default_hasher) + .collect::>>() + .unwrap(); + + let raw_block = RawBlock::new(RawBlockHeader::new(PeerId::random(), 0), messages); + let block_hash = raw_block.hash_with_default_hasher().unwrap(); + + let header_hash = raw_block.header.hash_with_default_hasher().unwrap(); + let merkle_root = MerkleTree::build_tree(&message_hashes).root_hash(); + let expected_block_hash = + Hasher::digest(&[header_hash.inner(), merkle_root.inner()].concat()); + + assert_eq!(block_hash, expected_block_hash.into()); + } + + fn create_ephemera_messages(n: usize) -> Vec { + let keypair = Keypair::generate(None); + let mut messages = Vec::new(); + for i in 0..n { + let label = format!("test {i}",); + let message = RawEphemeraMessage::new(label, vec![0; 32]); + let certificate = Certificate::prepare(&keypair, &message).unwrap(); + let ephemera_message = EphemeraMessage::new(message, certificate); + messages.push(ephemera_message); + } + messages + } +} diff --git a/ephemera/src/block/types/message.rs b/ephemera/src/block/types/message.rs new file mode 100644 index 0000000000..0cf271ba46 --- /dev/null +++ b/ephemera/src/block/types/message.rs @@ -0,0 +1,99 @@ +use serde::{Deserialize, Serialize}; + +use crate::utilities::codec::{Codec, DecodingError, EncodingError, EphemeraCodec}; +use crate::{ + codec::{Decode, Encode}, + utilities::{ + crypto::Certificate, + hash::{EphemeraHash, EphemeraHasher}, + hash::{Hash, Hasher}, + }, +}; + +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Deserialize, Serialize)] +pub(crate) struct EphemeraMessage { + ///Timestamp of the message + pub(crate) timestamp: u64, + ///Application specific logical identifier of the message + pub(crate) label: String, + ///Application specific data + pub(crate) data: Vec, + ///Signature of the raw message + pub(crate) certificate: Certificate, +} + +impl EphemeraMessage { + #[cfg(test)] + pub(crate) fn new(raw_message: RawEphemeraMessage, certificate: Certificate) -> Self { + Self { + timestamp: raw_message.timestamp, + label: raw_message.label, + data: raw_message.data, + certificate, + } + } + + pub(crate) fn hash_with_default_hasher(&self) -> anyhow::Result { + let mut hasher = Hasher::default(); + self.hash(&mut hasher)?; + let hash = hasher.finish().into(); + Ok(hash) + } +} + +impl Encode for EphemeraMessage { + fn encode(&self) -> Result, EncodingError> { + Codec::encode(&self) + } +} + +impl Decode for EphemeraMessage { + type Output = Self; + + fn decode(bytes: &[u8]) -> Result { + Codec::decode(bytes) + } +} + +impl EphemeraHash for EphemeraMessage { + fn hash(&self, state: &mut H) -> anyhow::Result<()> { + state.update(&self.encode()?); + Ok(()) + } +} + +/// Raw message represents all the data what will be signed. +#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] +pub(crate) struct RawEphemeraMessage { + pub(crate) timestamp: u64, + pub(crate) label: String, + pub(crate) data: Vec, +} + +impl RawEphemeraMessage { + #[cfg(test)] + pub(crate) fn new(label: String, data: Vec) -> Self { + use crate::utilities::time::EphemeraTime; + Self { + timestamp: EphemeraTime::now(), + label, + data, + } + } +} + +impl From for RawEphemeraMessage { + fn from(message: EphemeraMessage) -> Self { + Self { + timestamp: message.timestamp, + label: message.label, + data: message.data, + } + } +} + +impl Encode for RawEphemeraMessage { + fn encode(&self) -> Result, EncodingError> { + Codec::encode(&self) + } +} diff --git a/ephemera/src/block/types/mod.rs b/ephemera/src/block/types/mod.rs new file mode 100644 index 0000000000..21c274a325 --- /dev/null +++ b/ephemera/src/block/types/mod.rs @@ -0,0 +1,2 @@ +pub(crate) mod block; +pub(crate) mod message; diff --git a/ephemera/src/broadcast/bracha/broadcast.rs b/ephemera/src/broadcast/bracha/broadcast.rs new file mode 100644 index 0000000000..19e3e00d4b --- /dev/null +++ b/ephemera/src/broadcast/bracha/broadcast.rs @@ -0,0 +1,320 @@ +use std::num::NonZeroUsize; + +use log::{debug, trace}; +use lru::LruCache; + +use crate::broadcast::bracha::quorum::BrachaMessageType; +use crate::peer::PeerId; +use crate::{ + block::types::block::Block, + broadcast::{ + bracha::quorum::Quorum, + MessageType::{Echo, Vote}, + ProtocolContext, RawRbMsg, + }, + utilities::hash::Hash, +}; + +#[allow(clippy::large_enum_variant)] +#[derive(Debug)] +pub(crate) enum BroadcastResponse { + Broadcast(RawRbMsg), + Deliver(Hash), + Drop(Hash), +} + +pub(crate) struct Broadcaster { + /// Local peer id + local_peer_id: PeerId, + /// We keep a context for each block we are processing. + contexts: LruCache, + /// Current cluster size + cluster_size: usize, +} + +impl Broadcaster { + pub fn new(peer_id: PeerId) -> Broadcaster { + Broadcaster { + //At any given time we are processing in parallel about n messages, where n is the number of peers in the group. + //This is just large enough buffer. + contexts: LruCache::new(NonZeroUsize::new(1000).unwrap()), + cluster_size: 0, + local_peer_id: peer_id, + } + } + + pub(crate) fn new_broadcast(&mut self, block: Block) -> anyhow::Result { + debug!("Starting broadcast for new block {:?}", block.get_hash()); + self.handle(&RawRbMsg::new(block, self.local_peer_id)) + } + + pub(crate) fn handle(&mut self, rb_msg: &RawRbMsg) -> anyhow::Result { + trace!("Processing new broadcast message: {:?}", rb_msg); + + let block = rb_msg.block(); + let hash = block.hash_with_default_hasher()?; + + let ctx = self.contexts.get_or_insert(hash, || { + ProtocolContext::new(hash, self.local_peer_id, Quorum::new(self.cluster_size)) + }); + + if ctx.delivered { + trace!("Block {hash:?} already delivered"); + return Ok(BroadcastResponse::Drop(hash)); + } + + match rb_msg.message_type { + Echo(_) => { + trace!("Processing ECHO {:?}", rb_msg.id); + Ok(self.process_echo(rb_msg, hash)) + } + Vote(_) => { + trace!("Processing VOTE {:?}", rb_msg.id); + Ok(self.process_vote(rb_msg, hash)) + } + } + } + + fn process_echo(&mut self, rb_msg: &RawRbMsg, hash: Hash) -> BroadcastResponse { + let ctx = self.contexts.get_mut(&hash).expect("Context not found"); + + if self.local_peer_id != rb_msg.original_sender { + trace!("Adding echo from {:?}", rb_msg.original_sender); + ctx.add_echo(rb_msg.original_sender); + } + + if !ctx.echoed() { + ctx.add_echo(self.local_peer_id); + + trace!("Sending echo reply for {hash:?}",); + return BroadcastResponse::Broadcast( + rb_msg.echo_reply(self.local_peer_id, rb_msg.block()), + ); + } + + if !ctx.voted() + && ctx + .quorum + .check_threshold(ctx, BrachaMessageType::Echo) + .is_vote() + { + ctx.add_vote(self.local_peer_id); + + trace!("Sending vote reply for {hash:?}",); + return BroadcastResponse::Broadcast( + rb_msg.vote_reply(self.local_peer_id, rb_msg.block()), + ); + } + + BroadcastResponse::Drop(hash) + } + + fn process_vote(&mut self, rb_msg: &RawRbMsg, hash: Hash) -> BroadcastResponse { + let block = rb_msg.block(); + let ctx = self.contexts.get_mut(&hash).expect("Context not found"); + + if self.local_peer_id != rb_msg.original_sender { + trace!("Adding vote from {:?}", rb_msg.original_sender); + ctx.add_vote(rb_msg.original_sender); + } + + if ctx + .quorum + .check_threshold(ctx, BrachaMessageType::Vote) + .is_vote() + { + ctx.add_vote(self.local_peer_id); + + trace!("Sending vote reply for {hash:?}",); + return BroadcastResponse::Broadcast(rb_msg.vote_reply(self.local_peer_id, block)); + } + + if ctx + .quorum + .check_threshold(ctx, BrachaMessageType::Vote) + .is_deliver() + { + trace!("Commit complete for {:?}", rb_msg.id); + + ctx.delivered = true; + + return BroadcastResponse::Deliver(hash); + } + + BroadcastResponse::Drop(hash) + } + + pub(crate) fn group_updated(&mut self, size: usize) { + self.cluster_size = size; + } +} + +#[cfg(test)] +mod tests { + + //1.make sure before voting enough echo messages are received + //2.make sure before delivering enough vote messages are received + //a)Either f + 1 + //b)Or n - f + + //3.make sure that duplicate messages doesn't have impact + + //4. "Ideally" make sure that when group changes, the ongoing broadcast can deal with it + + use std::iter; + + use assert_matches::assert_matches; + + use crate::broadcast::bracha::broadcast::BroadcastResponse; + use crate::peer::PeerId; + use crate::utilities::hash::Hash; + use crate::{ + block::types::block::{Block, RawBlock, RawBlockHeader}, + broadcast::{self, bracha::broadcast::Broadcaster, RawRbMsg}, + }; + + #[test] + fn test_state_transitions_from_start_to_end() { + let peers: Vec = iter::repeat_with(PeerId::random).take(10).collect(); + let local_peer_id = peers[0]; + let block_creator_peer_id = peers[1]; + + let mut broadcaster = Broadcaster::new(local_peer_id); + broadcaster.group_updated(peers.len()); + + let (block_hash, block) = create_block(block_creator_peer_id); + + //After this echo set contains local and block creator(msg sender) + receive_echo_first_message(&mut broadcaster, &block, block_creator_peer_id); + + let ctx = broadcaster.contexts.get(&block_hash).unwrap(); + assert_eq!(ctx.echo.len(), 2); + assert!(ctx.echoed()); + assert!(!ctx.voted()); + + receive_nr_of_echo_messages_below_vote_threshold(&mut broadcaster, &block, &peers[2..6]); + + let ctx = broadcaster.contexts.get(&block_hash).unwrap(); + assert_eq!(ctx.echo.len(), 6); + assert!(ctx.echoed()); + assert!(!ctx.voted()); + + receive_echo_threshold_message(&mut broadcaster, &block, *peers.get(7).unwrap()); + + let ctx = broadcaster.contexts.get(&block_hash).unwrap(); + assert_eq!(ctx.echo.len(), 7); + assert_eq!(ctx.vote.len(), 1); + assert!(ctx.echoed()); + assert!(ctx.voted()); + + receive_nr_of_vote_messages_below_deliver_threshold(&mut broadcaster, &block, &peers[2..7]); + + let ctx = broadcaster.contexts.get(&block_hash).unwrap(); + assert_eq!(ctx.echo.len(), 7); + assert_eq!(ctx.vote.len(), 6); + assert!(ctx.echoed()); + assert!(ctx.voted()); + + receive_threshold_vote_message_for_deliver( + &mut broadcaster, + &block, + *peers.get(8).unwrap(), + ); + } + + fn receive_threshold_vote_message_for_deliver( + broadcaster: &mut Broadcaster, + block: &Block, + peer_id: PeerId, + ) { + let rb_msg = RawRbMsg::new(block.clone(), PeerId::random()); + let rb_msg = rb_msg.vote_reply(peer_id, block.clone()); + + let response = handle_double(broadcaster, &rb_msg); + + assert_matches!(response, BroadcastResponse::Deliver(_)); + } + + fn receive_nr_of_echo_messages_below_vote_threshold( + broadcaster: &mut Broadcaster, + block: &Block, + peers: &[PeerId], + ) { + for peer_id in peers { + let rb_msg = RawRbMsg::new(block.clone(), *peer_id); + + let response = handle_double(broadcaster, &rb_msg); + + assert_matches!(response, BroadcastResponse::Drop(_)); + } + } + + fn receive_nr_of_vote_messages_below_deliver_threshold( + broadcaster: &mut Broadcaster, + block: &Block, + peers: &[PeerId], + ) { + for peer_id in peers { + let rb_msg = RawRbMsg::new(block.clone(), PeerId::random()); + let rb_msg = rb_msg.vote_reply(*peer_id, block.clone()); + + let response = handle_double(broadcaster, &rb_msg); + assert_matches!(response, BroadcastResponse::Drop(_)); + } + } + + fn receive_echo_first_message( + broadcaster: &mut Broadcaster, + block: &Block, + block_creator: PeerId, + ) { + let rb_msg = RawRbMsg::new(block.clone(), block_creator); + let response = handle_double(broadcaster, &rb_msg); + + assert_matches!( + response, + BroadcastResponse::Broadcast(RawRbMsg { + id: _, + request_id: _, + original_sender: _, + timestamp: _, + message_type: broadcast::MessageType::Echo(_), + }) + ); + } + + fn receive_echo_threshold_message( + broadcaster: &mut Broadcaster, + block: &Block, + peer_id: PeerId, + ) { + let rb_msg = RawRbMsg::new(block.clone(), peer_id); + + let response = handle_double(broadcaster, &rb_msg); + assert_matches!( + response, + BroadcastResponse::Broadcast(RawRbMsg { + id: _, + request_id: _, + original_sender: _, + timestamp: _, + message_type: broadcast::MessageType::Vote(_), + }) + ); + } + + fn create_block(block_creator_peer_id: PeerId) -> (Hash, Block) { + let header = RawBlockHeader::new(block_creator_peer_id, 0); + let raw_block = RawBlock::new(header, vec![]); + let block_hash = raw_block.hash_with_default_hasher().unwrap(); + let block = Block::new(raw_block, block_hash); + (block_hash, block) + } + + //make sure that duplicate messages doesn't have impact + fn handle_double(broadcaster: &mut Broadcaster, rb_msg: &RawRbMsg) -> BroadcastResponse { + let response = broadcaster.handle(rb_msg).unwrap(); + broadcaster.handle(rb_msg).unwrap(); + response + } +} diff --git a/ephemera/src/broadcast/bracha/mod.rs b/ephemera/src/broadcast/bracha/mod.rs new file mode 100644 index 0000000000..ddbf36eb64 --- /dev/null +++ b/ephemera/src/broadcast/bracha/mod.rs @@ -0,0 +1,2 @@ +pub(crate) mod broadcast; +pub(crate) mod quorum; diff --git a/ephemera/src/broadcast/bracha/quorum.rs b/ephemera/src/broadcast/bracha/quorum.rs new file mode 100644 index 0000000000..35bb935ae6 --- /dev/null +++ b/ephemera/src/broadcast/bracha/quorum.rs @@ -0,0 +1,254 @@ +use log::trace; + +use crate::broadcast::{MessageType, ProtocolContext}; + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub(crate) enum BrachaMessageType { + Echo, + Vote, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub(crate) enum BrachaAction { + Vote, + Deliver, + Ignore, +} + +impl BrachaAction { + pub(crate) fn is_vote(self) -> bool { + matches!(self, BrachaAction::Vote) + } + + pub(crate) fn is_deliver(self) -> bool { + matches!(self, BrachaAction::Deliver) + } +} + +impl From for BrachaMessageType { + fn from(message_type: MessageType) -> Self { + match message_type { + MessageType::Echo(_) => BrachaMessageType::Echo, + MessageType::Vote(_) => BrachaMessageType::Vote, + } + } +} + +const MAX_FAULTY_RATIO: f64 = 1.0 / 3.0; + +#[derive(Debug, Clone, Copy)] +pub(crate) struct Quorum { + pub(crate) cluster_size: usize, + pub(crate) max_faulty_nodes: usize, +} + +impl Quorum { + pub fn new(cluster_size: usize) -> Self { + let max_faulty_nodes = Quorum::max_faulty_nodes(cluster_size); + Self { + cluster_size, + max_faulty_nodes, + } + } + + pub(crate) fn check_threshold( + &self, + ctx: &ProtocolContext, + phase: BrachaMessageType, + ) -> BrachaAction { + if self.cluster_size == 0 { + return BrachaAction::Ignore; + } + + match phase { + BrachaMessageType::Echo => { + if ctx.echo.len() >= self.cluster_size - self.max_faulty_nodes { + trace!( + "Echo threshold reached: Echoed:{} / Threshold:{} for Block:{}", + ctx.echo.len(), + self.cluster_size - self.max_faulty_nodes, + ctx.hash + ); + BrachaAction::Vote + } else { + trace!( + "Echo threshold not reached: Echoed:{} / Threshold:{} for Block:{}", + ctx.echo.len(), + self.cluster_size - self.max_faulty_nodes, + ctx.hash + ); + BrachaAction::Ignore + } + } + BrachaMessageType::Vote => { + if !ctx.voted() { + // f + 1 votes are enough to send our vote + if ctx.vote.len() >= self.max_faulty_nodes { + trace!( + "Vote send threshold reached: Voted:{} / Threshold:{} for Block:{}", + ctx.vote.len(), + self.max_faulty_nodes + 1, + ctx.hash + ); + return BrachaAction::Vote; + } + } + + if ctx.voted() { + // n-f votes are enough to deliver the value + if ctx.vote.len() >= self.cluster_size - self.max_faulty_nodes { + trace!( + "Deliver threshold reached: Voted:{} / Threshold:{} for Block:{}", + ctx.vote.len(), + self.cluster_size - self.max_faulty_nodes, + ctx.hash + ); + return BrachaAction::Deliver; + } + } + + trace!( + "Vote threshold not reached: Voted:{} / Threshold:{} for Block:{}", + ctx.vote.len(), + self.max_faulty_nodes + 1, + ctx.hash + ); + BrachaAction::Ignore + } + } + } + + pub(crate) fn cluster_size_info(cluster_size: usize) -> String { + let max_faulty_nodes = Quorum::max_faulty_nodes(cluster_size); + format!("Cluster size: {cluster_size} / Max faulty nodes: {max_faulty_nodes}",) + } + + #[allow( + clippy::cast_precision_loss, + clippy::cast_sign_loss, + clippy::cast_possible_truncation + )] + fn max_faulty_nodes(cluster_size: usize) -> usize { + (cluster_size as f64 * MAX_FAULTY_RATIO).floor() as usize + } +} + +#[cfg(test)] +mod test { + use std::collections::HashSet; + + use crate::broadcast::{ + bracha::quorum::{BrachaAction, BrachaMessageType, Quorum}, + ProtocolContext, + }; + use crate::peer::PeerId; + + #[test] + fn test_max_faulty_nodes() { + let quorum = Quorum::new(10); + assert_eq!(quorum.max_faulty_nodes, 3); + } + + #[test] + fn test_vote_threshold_from_n_minus_f_peers() { + let quorum = Quorum::new(10); + + let ctx = ctx_with_nr_echoes(0); + assert_eq!( + quorum.check_threshold(&ctx, BrachaMessageType::Echo), + BrachaAction::Ignore + ); + + let ctx = ctx_with_nr_echoes(3); + assert_eq!( + quorum.check_threshold(&ctx, BrachaMessageType::Echo), + BrachaAction::Ignore + ); + + let ctx = ctx_with_nr_echoes(8); + assert_eq!( + quorum.check_threshold(&ctx, BrachaMessageType::Echo), + BrachaAction::Vote + ); + } + + #[test] + fn test_vote_threshold_from_f_plus_one_peers() { + let quorum = Quorum::new(10); + + let ctx = ctx_with_nr_votes(0, None); + assert_eq!( + quorum.check_threshold(&ctx, BrachaMessageType::Vote), + BrachaAction::Ignore + ); + + let ctx = ctx_with_nr_votes(2, None); + assert_eq!( + quorum.check_threshold(&ctx, BrachaMessageType::Vote), + BrachaAction::Ignore + ); + + let ctx = ctx_with_nr_votes(5, None); + assert_eq!( + quorum.check_threshold(&ctx, BrachaMessageType::Vote), + BrachaAction::Vote + ); + } + + #[test] + fn test_deliver_threshold_from_n_minus_f_peers() { + let quorum = Quorum::new(10); + + let local_peer_id = PeerId::random(); + let ctx = ctx_with_nr_votes(0, local_peer_id.into()); + assert_eq!( + quorum.check_threshold(&ctx, BrachaMessageType::Vote), + BrachaAction::Ignore + ); + + let ctx = ctx_with_nr_votes(3, local_peer_id.into()); + assert_eq!( + quorum.check_threshold(&ctx, BrachaMessageType::Vote), + BrachaAction::Ignore + ); + + let ctx = ctx_with_nr_votes(7, local_peer_id.into()); + assert_eq!( + quorum.check_threshold(&ctx, BrachaMessageType::Vote), + BrachaAction::Deliver + ); + } + + fn ctx_with_nr_echoes(n: usize) -> ProtocolContext { + let mut ctx = ProtocolContext { + local_peer_id: PeerId::random(), + hash: [0; 32].into(), + echo: HashSet::default(), + vote: HashSet::default(), + quorum: Quorum::new(10), + delivered: false, + }; + for _ in 0..n { + ctx.echo.insert(PeerId::random()); + } + ctx + } + + fn ctx_with_nr_votes(n: usize, local_peer_id: Option) -> ProtocolContext { + let mut ctx = ProtocolContext { + local_peer_id: local_peer_id.unwrap_or(PeerId::random()), + hash: [0; 32].into(), + echo: HashSet::default(), + vote: HashSet::default(), + quorum: Quorum::new(10), + delivered: false, + }; + for _ in 0..n { + ctx.vote.insert(PeerId::random()); + } + if let Some(id) = local_peer_id { + ctx.vote.insert(id); + } + ctx + } +} diff --git a/ephemera/src/broadcast/group.rs b/ephemera/src/broadcast/group.rs new file mode 100644 index 0000000000..d8483b8aaf --- /dev/null +++ b/ephemera/src/broadcast/group.rs @@ -0,0 +1,249 @@ +use std::collections::HashSet; +use std::num::NonZeroUsize; + +use log::warn; +use lru::LruCache; + +use crate::peer::PeerId; +use crate::utilities::hash::Hash; + +pub(crate) struct BroadcastGroup { + /// The id of current group. Incremented every time a new snapshot is added. + pub(crate) current_id: u64, + /// A cache of the group snapshots. + pub(crate) snapshots: LruCache>, + /// A cache of the groups for each block. + pub(crate) broadcast_groups: LruCache, +} + +impl BroadcastGroup { + pub(crate) fn new() -> BroadcastGroup { + let mut snapshots = LruCache::new(NonZeroUsize::new(100).unwrap()); + snapshots.put(0, HashSet::new()); + BroadcastGroup { + current_id: 0, + snapshots, + broadcast_groups: LruCache::new(NonZeroUsize::new(100).unwrap()), + } + } + + pub(crate) fn add_snapshot(&mut self, snapshot: HashSet) { + self.current_id += 1; + self.snapshots.put(self.current_id, snapshot); + } + + pub(crate) fn is_member(&mut self, id: u64, peer_id: &PeerId) -> bool { + self.snapshots + .get(&id) + .map_or(false, |s| s.contains(peer_id)) + } + + pub(crate) fn is_empty(&mut self) -> bool { + self.snapshots + .get(&self.current_id) + .map_or(true, HashSet::is_empty) + } + + // Returns empty snapshots(inserted in 'new' fn) if we haven't received any yet. + pub(crate) fn current(&mut self) -> &HashSet { + self.snapshots + .get(&self.current_id) + .expect("Current group should always exist") + } + + // Checks if creator and sender are part of the expected group. + // If we see hash first time, it checks against the current group. And if check passes, it + // associates the hash with the current group. + pub(crate) fn check_membership( + &mut self, + hash: Hash, + block_creator: &PeerId, + message_sender: &PeerId, + ) -> bool { + //We see this block first time + if !self.broadcast_groups.contains(&hash) { + //This can happen at startup for example when node is not ready yet(caught up with the network) + if self.is_empty() { + warn!( + "Received new block {:?} but current group is empty, rejecting the block", + hash + ); + return false; + } + } + + //Make sure that the sender peer_id and block peer_id are part of the block initial group + //1. If the block is new, the group is the current one + //2. If the block is old, the group is the one that was used when the block was created + + //It's needed to make sure that + //1. The peer is authenticated(part of the network) + //2. Block processing is consistent regarding the group across rounds + + let membership_id = *self.broadcast_groups.get(&hash).unwrap_or(&self.current_id); + + //Node is excluded from group for some reason(for example health checks failed) + if !self.is_member(membership_id, message_sender) { + warn!( + "Received new block {} but sender {} is not part of the current group", + hash, message_sender + ); + return false; + } + + //Node is excluded from group for some reason(for example health checks failed) + if !self.is_member(membership_id, block_creator) { + warn!( + "Received new block {} but sender {} is not part of the current group", + hash, message_sender + ); + return false; + } + + self.broadcast_groups.put(hash, membership_id); + + true + } + + pub(crate) fn get_group_by_block_hash(&mut self, hash: Hash) -> Option<&HashSet> { + let membership_id = *self.broadcast_groups.get(&hash)?; + self.snapshots.get(&membership_id) + } +} + +#[cfg(test)] +mod test { + use std::collections::HashSet; + + use crate::broadcast::group::BroadcastGroup; + use crate::peer::PeerId; + use crate::utilities::hash::Hash; + + #[test] + fn test_no_snapshot() { + let group = BroadcastGroup::new(); + assert_eq!(group.current_id, 0); + //Including initial default snapshot + assert_eq!(group.snapshots.len(), 1); + } + + #[test] + fn test_multiple_snapshots() { + let (mut group, snapshots) = group_with_snapshots(10); + assert_eq!(group.current_id, 10); + //Including initial default snapshot + assert_eq!(group.snapshots.len(), 11); + + for (i, sn) in snapshots + .iter() + .enumerate() + .map(|(i, sn)| ((i + 1) as u64, sn)) + { + let gsn = group.snapshots.get(&i).unwrap(); + assert_eq!(sn, gsn); + } + } + + #[test] + fn check_membership_empty_group() { + let mut group = BroadcastGroup::new(); + let hash = Hash::new([0; 32]); + assert!(!group.check_membership(hash, &PeerId::random(), &PeerId::random())); + assert!(!group.broadcast_groups.contains(&hash)); + } + + #[test] + fn check_membership_creator_nor_sender_not_member() { + let (mut group, _snapshots) = group_with_snapshots(1); + assert!(!group.check_membership(Hash::new([0; 32]), &PeerId::random(), &PeerId::random())); + assert!(!group.broadcast_groups.contains(&Hash::new([0; 32]))); + } + + #[test] + fn check_membership_creator_not_member() { + let (mut group, snapshots) = group_with_snapshots(1); + let sender = snapshots[0].clone().into_iter().next().unwrap(); + + let hash = Hash::new([0; 32]); + assert!(!group.check_membership(hash, &PeerId::random(), &sender)); + assert!(!group.broadcast_groups.contains(&hash)); + } + + #[test] + fn check_membership_sender_not_member() { + let (mut group, snapshots) = group_with_snapshots(1); + let creator = snapshots[0].clone().into_iter().next().unwrap(); + let hash = Hash::new([0; 32]); + assert!(!group.check_membership(hash, &creator, &PeerId::random())); + assert!(!group.broadcast_groups.contains(&hash)); + } + + #[test] + fn check_snapshot_membership_both_are_members() { + let (mut group, snapshots) = group_with_snapshots(1); + let creator = snapshots[0].clone().into_iter().next().unwrap(); + let sender = creator; + let hash = Hash::new([0; 32]); + assert!(group.check_membership(hash, &creator, &sender)); + assert!(group.broadcast_groups.contains(&hash)); + } + + #[test] + fn check_snapshot_membership_of_current_snapshot() { + let (mut group, snapshots) = group_with_snapshots(2); + let current_snapshot = snapshots[1].clone(); + let creator = current_snapshot.into_iter().next().unwrap(); + let sender = creator; + + let hash = Hash::new([0; 32]); + assert!(group.check_membership(hash, &creator, &sender)); + assert!(group.broadcast_groups.contains(&hash)); + + //Remove the current snapshot + group.snapshots.pop(&group.current_id); + + //Membership should fail + assert!(!group.check_membership(hash, &creator, &sender)); + } + + #[test] + fn check_snapshot_membership_of_previous_snapshot() { + let mut group = BroadcastGroup::new(); + let first_snapshot = create_snapshot(); + group.add_snapshot(first_snapshot.clone()); + + let creator = first_snapshot.into_iter().next().unwrap(); + let sender = creator; + let hash = Hash::new([0; 32]); + assert!(group.check_membership(hash, &creator, &sender)); + assert!(group.broadcast_groups.contains(&hash)); + + //Add second snapshot + group.add_snapshot(create_snapshot()); + + //Remove the current snapshot + group.snapshots.pop(&group.current_id); + + //Membership should still pass + assert!(group.broadcast_groups.contains(&hash)); + assert!(group.check_membership(hash, &creator, &sender)); + } + + fn group_with_snapshots(count: usize) -> (BroadcastGroup, Vec>) { + let mut group = BroadcastGroup::new(); + let mut snapshots = Vec::new(); + for _ in 0..count { + let snapshot = create_snapshot(); + snapshots.push(snapshot.clone()); + group.add_snapshot(snapshot); + } + (group, snapshots) + } + + fn create_snapshot() -> HashSet { + let mut snapshot = HashSet::new(); + let peer_id = PeerId::random(); + snapshot.insert(peer_id); + snapshot + } +} diff --git a/ephemera/src/broadcast/mod.rs b/ephemera/src/broadcast/mod.rs new file mode 100644 index 0000000000..ca930c451d --- /dev/null +++ b/ephemera/src/broadcast/mod.rs @@ -0,0 +1,191 @@ +//! Simple reliable broadcast protocol(Bracha) implementation +//! +use std::collections::HashSet; +use std::fmt::{Debug, Display}; + +use serde_derive::{Deserialize, Serialize}; + +use crate::broadcast::bracha::quorum::Quorum; +use crate::{ + block::types::block::Block, + peer::PeerId, + utilities::{ + crypto::Certificate, + hash::Hash, + id::{EphemeraId, EphemeraIdentifier}, + time::EphemeraTime, + }, +}; + +pub(crate) mod bracha; +pub(crate) mod group; +pub(crate) mod signing; + +/// Context keeps the broadcast state for a block +#[derive(Debug, Clone)] +pub(crate) struct ProtocolContext { + pub(crate) local_peer_id: PeerId, + /// Block hash + pub(crate) hash: Hash, + /// Peers that sent prepare message(this peer included) + pub(crate) echo: HashSet, + /// Peers that sent commit message(this peer included) + pub(crate) vote: HashSet, + /// Quorum logic for Bracha protocol + pub(crate) quorum: Quorum, + /// Flag indicating if the message was delivered to the client + pub(crate) delivered: bool, +} + +impl ProtocolContext { + pub(crate) fn new(hash: Hash, local_peer_id: PeerId, quorum: Quorum) -> ProtocolContext { + ProtocolContext { + local_peer_id, + hash, + echo: HashSet::new(), + vote: HashSet::new(), + quorum, + delivered: false, + } + } + + fn add_echo(&mut self, peer: PeerId) { + self.echo.insert(peer); + } + + fn add_vote(&mut self, peer: PeerId) { + self.vote.insert(peer); + } + + fn echoed(&self) -> bool { + self.echo.contains(&self.local_peer_id) + } + + fn voted(&self) -> bool { + self.vote.contains(&self.local_peer_id) + } +} + +#[derive(Clone, PartialEq, Eq, Deserialize, Serialize)] +pub(crate) struct RbMsg { + ///Unique id of the message which stays the same throughout the protocol + pub(crate) id: EphemeraId, + ///Distinct id of the message which changes when the message is rebroadcast + pub(crate) request_id: EphemeraId, + ///Id of the peer that CREATED the message(not necessarily the one that sent it, with gossip it can come through a different peer) + pub(crate) original_sender: PeerId, + ///When the message was created by the sender. + pub(crate) timestamp: u64, + ///Current phase of the protocol(Echo, Vote) + pub(crate) phase: MessageType, + ///Signature of the message + pub(crate) certificate: Certificate, +} + +impl RbMsg { + pub(crate) fn new(raw: RawRbMsg, signature: Certificate) -> RbMsg { + RbMsg { + id: raw.id, + request_id: raw.request_id, + original_sender: raw.original_sender, + timestamp: raw.timestamp, + phase: raw.message_type, + certificate: signature, + } + } + + pub(crate) fn block(&self) -> &Block { + match &self.phase { + MessageType::Echo(block) | MessageType::Vote(block) => block, + } + } +} + +#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)] +pub(crate) struct RawRbMsg { + pub(crate) id: EphemeraId, + pub(crate) request_id: EphemeraId, + pub(crate) original_sender: PeerId, + pub(crate) timestamp: u64, + pub(crate) message_type: MessageType, +} + +impl RawRbMsg { + pub(crate) fn new(block: Block, original_sender: PeerId) -> RawRbMsg { + RawRbMsg { + id: EphemeraId::generate(), + request_id: EphemeraId::generate(), + original_sender, + timestamp: EphemeraTime::now(), + message_type: MessageType::Echo(block), + } + } + + pub(crate) fn block(&self) -> Block { + match &self.message_type { + MessageType::Echo(block) | MessageType::Vote(block) => block.clone(), + } + } + + pub(crate) fn reply(&self, local_id: PeerId, phase: MessageType) -> Self { + RawRbMsg { + id: self.id.clone(), + request_id: EphemeraId::generate(), + original_sender: local_id, + timestamp: EphemeraTime::now(), + message_type: phase, + } + } + + pub(crate) fn echo_reply(&self, local_id: PeerId, data: Block) -> Self { + self.reply(local_id, MessageType::Echo(data)) + } + + pub(crate) fn vote_reply(&self, local_id: PeerId, data: Block) -> Self { + self.reply(local_id, MessageType::Vote(data)) + } +} + +impl From for RawRbMsg { + fn from(msg: RbMsg) -> Self { + RawRbMsg { + id: msg.id, + request_id: msg.request_id, + original_sender: msg.original_sender, + timestamp: msg.timestamp, + message_type: msg.phase, + } + } +} + +impl Display for RbMsg { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, + "[id: {}, peer: {}, block: {}, phase: {:?}]", + self.id, + self.original_sender, + self.block().get_hash(), + self.phase + ) + } +} + +impl Debug for RbMsg { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, + "[id: {}, peer: {}, block: {}, phase: {:?}]", + self.id, + self.original_sender, + self.block().get_hash(), + self.phase + ) + } +} + +#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)] +pub(crate) enum MessageType { + Echo(Block), + Vote(Block), +} diff --git a/ephemera/src/broadcast/signing.rs b/ephemera/src/broadcast/signing.rs new file mode 100644 index 0000000000..c3003365f3 --- /dev/null +++ b/ephemera/src/broadcast/signing.rs @@ -0,0 +1,150 @@ +use std::collections::HashSet; +use std::num::NonZeroUsize; +use std::sync::Arc; + +use log::trace; +use lru::LruCache; + +use crate::{ + block::types::block::{Block, RawBlock}, + crypto::Keypair, + utilities::{codec::Encode, crypto::Certificate, crypto::EphemeraPublicKey, hash::Hash}, +}; + +pub(crate) struct BlockSigner { + /// All signatures of the last blocks that we received from the network(+ our own) + verified_signatures: LruCache>, + /// Our own keypair + signing_keypair: Arc, +} + +impl BlockSigner { + pub fn new(keypair: Arc) -> Self { + Self { + verified_signatures: LruCache::new(NonZeroUsize::new(1000).unwrap()), + signing_keypair: keypair, + } + } + + pub(crate) fn get_block_certificates( + &mut self, + block_id: &Hash, + ) -> Option<&HashSet> { + self.verified_signatures.get(block_id) + } + + pub(crate) fn sign_block(&mut self, block: &Block, hash: &Hash) -> anyhow::Result { + trace!("Signing block: {:?}", block); + + let certificate = block.sign(self.signing_keypair.as_ref())?; + self.add_certificate(hash, certificate.clone()); + Ok(certificate) + } + + /// This verification is part of reliable broadcast and verifies only the + /// signature of the sender. + pub(crate) fn verify_block( + &mut self, + block: &Block, + certificate: &Certificate, + ) -> anyhow::Result<()> { + trace!("Verifying block: {block:?} against certificate {certificate:?}"); + + let raw_block: RawBlock = (*block).clone().into(); + let raw_block = raw_block.encode()?; + + if certificate + .public_key + .verify(&raw_block, &certificate.signature) + { + self.add_certificate(&block.header.hash, certificate.clone()); + Ok(()) + } else { + anyhow::bail!("Invalid block certificate"); + } + } + + fn add_certificate(&mut self, hash: &Hash, certificate: Certificate) { + trace!("Adding certificate to block: {}", hash); + self.verified_signatures + .get_or_insert_mut(*hash, HashSet::new) + .insert(certificate); + } +} + +#[cfg(test)] +mod test { + use crate::block::types::block::RawBlockHeader; + use crate::block::types::message::{EphemeraMessage, RawEphemeraMessage}; + use crate::crypto::EphemeraKeypair; + use crate::peer::ToPeerId; + + use super::*; + + #[test] + fn test_sign_verify_block_ok() { + let mut signer = BlockSigner::new(Arc::new(Keypair::generate(None))); + + let message_signing_keypair = Keypair::generate(None); + + let block = new_block(&message_signing_keypair, "label1"); + let hash = block.hash_with_default_hasher().unwrap(); + + let certificate = signer.sign_block(&block, &hash).unwrap(); + + assert!(signer.verify_block(&block, &certificate).is_ok()); + } + + #[test] + fn test_sign_signatures_cached_correctly() { + let mut signer = BlockSigner::new(Arc::new(Keypair::generate(None))); + + let block = new_block(&Keypair::generate(None), "label1"); + let hash = block.hash_with_default_hasher().unwrap(); + + //Signed by node 1 + let certificate1 = block.sign(&Keypair::generate(None)).unwrap(); + signer.verify_block(&block, &certificate1).unwrap(); + //Signed by node 2 + let certificate2 = block.sign(&Keypair::generate(None)).unwrap(); + signer.verify_block(&block, &certificate2).unwrap(); + + let block_certificates = signer.get_block_certificates(&hash).unwrap(); + assert_eq!(block_certificates.len(), 2); + } + + #[test] + fn test_sign_verify_block_fail() { + let mut signer = BlockSigner::new(Arc::new(Keypair::generate(None))); + let message_signing_keypair = Keypair::generate(None); + + let block = new_block(&message_signing_keypair, "label1"); + let certificate = block.sign(&message_signing_keypair).unwrap(); + + let modified_block = new_block(&message_signing_keypair, "label2"); + + assert!(signer.verify_block(&modified_block, &certificate).is_err()); + } + + fn new_block(keypair: &Keypair, message_label: &str) -> Block { + let peer_id = keypair.public_key().peer_id(); + + let raw_ephemera_message = + RawEphemeraMessage::new(message_label.to_string(), "payload".as_bytes().to_vec()); + + let message_certificate = Certificate::prepare(keypair, &raw_ephemera_message).unwrap(); + let messages = vec![EphemeraMessage::new( + raw_ephemera_message, + message_certificate, + )]; + + let raw_block_header = RawBlockHeader::new(peer_id, 0); + let raw_block = RawBlock::new(raw_block_header, messages); + + let block_hash = raw_block + .hash_with_default_hasher() + .expect("Hashing failed"); + + Block::new(raw_block, block_hash) + } +} diff --git a/ephemera/src/cli/config.rs b/ephemera/src/cli/config.rs new file mode 100644 index 0000000000..1fca81afcc --- /dev/null +++ b/ephemera/src/cli/config.rs @@ -0,0 +1,132 @@ +use std::fs; +use std::path::PathBuf; +use std::str::FromStr; + +use clap::Parser; +use log::{error, info}; +use toml::{Table, Value}; + +use crate::config::Configuration; + +#[derive(Debug, Clone, Parser)] +pub struct UpdateConfigCmd { + #[clap(long)] + pub config_path: String, + #[clap(long)] + pub property: String, + #[clap(long)] + pub value: String, +} + +impl UpdateConfigCmd { + /// # Panics + /// Panics if the config file does not exist. + pub fn execute(self) { + let path: PathBuf = self.config_path.clone().into(); + match Configuration::try_load(path.clone()) { + Ok(_) => { + info!("Updating config: {:?}", self); + + let toml_str = fs::read_to_string(path.clone()).unwrap(); + let table = toml_str.parse::().unwrap(); + + let keys = self.property.split('.').collect::>(); + + if !table.contains_key(keys[0]) { + println!("Key '{}' does not exist", keys[0]); + return; + } + + let mut visitor = ConfigVisitor::new(keys, self.value); + let table = visitor.process(table); + + let toml_str = toml::to_string(&table).unwrap(); + fs::write(path, toml_str).unwrap(); + } + Err(err) => { + error!("Error loading configuration file: {err:?}"); + } + } + } +} + +struct ConfigVisitor<'a> { + keys: Vec<&'a str>, + value: String, + in_array: bool, + data_in_array: Vec, +} + +impl<'a> ConfigVisitor<'a> { + fn new(keys: Vec<&'a str>, value: String) -> Self { + Self { + keys, + value, + in_array: false, + data_in_array: vec![], + } + } + + #[allow(clippy::needless_pass_by_value)] + fn process(&mut self, table: Table) -> Table { + let key = self.keys[0]; + let value = table.get(key).unwrap(); + self.process_value(table.clone(), value.clone(), 0) + } + + fn process_value(&mut self, mut table: Table, value: Value, key_index: usize) -> Table { + let root_key = self.keys[key_index]; + match value { + Value::String(str) => { + println!("Old value: {str}",); + let value = String::from(&self.value); + println!("New value: {value}",); + table.insert(root_key.to_string(), Value::String(value)); + } + Value::Integer(i) => { + println!("Old value: {i}",); + let value = i64::from_str(&self.value).unwrap(); + println!("New value: {value}",); + table.insert(root_key.to_string(), Value::Integer(value)); + } + Value::Float(f) => { + println!("Old value: {f}",); + let value = f64::from_str(&self.value).unwrap(); + println!("New value: {value}",); + table.insert(root_key.to_string(), Value::Float(value)); + } + Value::Boolean(b) => { + println!("Old value: {b}",); + let value = bool::from_str(&self.value).unwrap(); + println!("New value: {value}",); + table.insert(root_key.to_string(), Value::Boolean(value)); + } + Value::Datetime(_) => { + println!("Datetime not supported"); + } + Value::Array(ar) => { + self.in_array = true; + for value in ar { + self.process_value(table.clone(), value, key_index); + } + table.remove(root_key); + table.insert( + root_key.to_string(), + Value::Array(self.data_in_array.clone()), + ); + self.data_in_array.clear(); + self.in_array = false; + } + Value::Table(t) => { + let value = t.get(self.keys[key_index + 1]).cloned().unwrap(); + let updated_table = self.process_value(t, value, key_index + 1); + if self.in_array { + self.data_in_array.push(updated_table.into()); + return table.clone(); + } + table.insert(root_key.to_string(), updated_table.into()); + } + } + table + } +} diff --git a/ephemera/src/cli/crypto.rs b/ephemera/src/cli/crypto.rs new file mode 100644 index 0000000000..fc6f3e989b --- /dev/null +++ b/ephemera/src/cli/crypto.rs @@ -0,0 +1,15 @@ +use crate::crypto::{EphemeraKeypair, Keypair}; +use clap::Parser; + +use crate::utilities::crypto::EphemeraPublicKey; + +#[derive(Debug, Clone, Parser)] +pub struct GenerateKeypairCmd; + +impl GenerateKeypairCmd { + pub fn execute() { + let keypair = Keypair::generate(None); + println!("Keypair: {:>5}", keypair.to_base58()); + println!("Public key: {:>5}", keypair.public_key().to_base58()); + } +} diff --git a/ephemera/src/cli/init.rs b/ephemera/src/cli/init.rs new file mode 100644 index 0000000000..60d245d279 --- /dev/null +++ b/ephemera/src/cli/init.rs @@ -0,0 +1,165 @@ +use clap::{Args, Parser}; +use serde::{Deserialize, Serialize}; + +use crate::config::{ + BlockManagerConfiguration, Configuration, DatabaseConfiguration, HttpConfiguration, + Libp2pConfiguration, MembershipKind as ConfigMembershipKind, NodeConfiguration, + WebsocketConfiguration, +}; +use crate::crypto::{EphemeraKeypair, Keypair}; + +//network settings +const DEFAULT_LISTEN_ADDRESS: &str = "127.0.0.1"; +const DEFAULT_PROT_LISTEN_PORT: u16 = 3000; +const DEFAULT_WS_LISTEN_PORT: u16 = 3001; +const DEFAULT_HTTP_LISTEN_PORT: u16 = 3002; + +//libp2p settings +const DEFAULT_MESSAGES_TOPIC_NAME: &str = "nym-ephemera-proposed"; +const DEFAULT_HEARTBEAT_INTERVAL_SEC: u64 = 1; + +#[derive(Args, Debug, Clone, Eq, PartialEq, Serialize, Deserialize)] +#[group(required = true, multiple = false)] +pub struct MembershipKind { + /// Requires the threshold of peers returned by membership provider to be online + #[clap(long)] + threshold: Option, + /// Requires that all peers returned by membership provider peers to be online + #[clap(long)] + all: bool, + /// Membership is just all online peers from the set returned by membership provider + #[clap(long)] + any: bool, +} + +impl Default for MembershipKind { + fn default() -> Self { + MembershipKind { + threshold: None, + all: false, + any: true, + } + } +} + +impl From for ConfigMembershipKind { + fn from(kind: MembershipKind) -> Self { + match (kind.threshold, kind.all, kind.any) { + //FIXME: use threshold value + (Some(_), false, false) => ConfigMembershipKind::Threshold, + (None, true, false) => ConfigMembershipKind::AllOnline, + (None, false, true) => ConfigMembershipKind::AnyOnline, + _ => panic!("Invalid membership kind"), + } + } +} + +#[derive(Parser, Debug, Clone, Eq, PartialEq, Serialize, Deserialize)] +pub struct Cmd { + #[clap(long)] + /// The IP address to listen on + pub ephemera_ip: Option, + /// The port which Ephemera uses for peer to peer communication + #[clap(long)] + pub ephemera_protocol_port: Option, + /// The port which Ephemera listens on for websocket subscriptions + #[clap(long)] + pub ephemera_websocket_port: Option, + /// The port which Ephemera listens on for http api + #[clap(long)] + pub ephemera_http_api_port: Option, + /// Either this node produces blocks or not + #[clap(skip)] + pub block_producer: bool, + /// At which interval to produce blocks + #[clap(skip)] + pub block_creation_interval_sec: u64, + /// When next block is created before previous one is finished, should we repeat it with the same messages + #[clap(skip)] + pub repeat_last_block_messages: bool, + /// The interval at which Ephemera requests the list of members + #[clap(skip)] + pub members_provider_delay_sec: u64, + /// A rule how to choose members based on their online status + #[clap(skip)] + pub membership_kind: MembershipKind, +} + +impl Default for Cmd { + fn default() -> Self { + Cmd { + ephemera_ip: Some(String::from(DEFAULT_LISTEN_ADDRESS)), + ephemera_protocol_port: Some(DEFAULT_PROT_LISTEN_PORT), + ephemera_websocket_port: Some(DEFAULT_WS_LISTEN_PORT), + ephemera_http_api_port: Some(DEFAULT_HTTP_LISTEN_PORT), + block_producer: true, + block_creation_interval_sec: 30, + repeat_last_block_messages: false, + members_provider_delay_sec: 60 * 60, + membership_kind: MembershipKind::default(), + } + } +} + +impl Cmd { + /// # Panics + /// Panics if the config file already exists. + pub fn execute(self, id: Option<&str>) { + assert!( + Configuration::try_load_from_home_dir().is_err(), + "Configuration file already exists", + ); + + let path = Configuration::ephemera_root_dir(id).unwrap(); + println!("Creating ephemera node configuration in: {path:?}",); + + let db_dir = path.join("db"); + let rocksdb_path = db_dir.join("rocksdb"); + let sqlite_path = db_dir.join("ephemera.sqlite"); + std::fs::create_dir_all(&rocksdb_path).unwrap(); + std::fs::File::create(&sqlite_path).unwrap(); + + let keypair = Keypair::generate(None); + let private_key = keypair.to_base58(); + + let default_cfg = Self::default(); + + let configuration = Configuration { + node: NodeConfiguration { + ip: self.ephemera_ip.unwrap_or(default_cfg.ephemera_ip.unwrap()), + private_key, + }, + libp2p: Libp2pConfiguration { + port: self + .ephemera_protocol_port + .unwrap_or(default_cfg.ephemera_protocol_port.unwrap()), + ephemera_msg_topic_name: DEFAULT_MESSAGES_TOPIC_NAME.to_string(), + heartbeat_interval_sec: DEFAULT_HEARTBEAT_INTERVAL_SEC, + members_provider_delay_sec: self.members_provider_delay_sec, + membership_kind: self.membership_kind.into(), + }, + storage: DatabaseConfiguration { + rocksdb_path: rocksdb_path.as_os_str().to_str().unwrap().to_string(), + sqlite_path: sqlite_path.as_os_str().to_str().unwrap().to_string(), + create_if_not_exists: true, + }, + websocket: WebsocketConfiguration { + port: self + .ephemera_websocket_port + .unwrap_or(default_cfg.ephemera_websocket_port.unwrap()), + }, + http: HttpConfiguration { + port: self + .ephemera_http_api_port + .unwrap_or(default_cfg.ephemera_http_api_port.unwrap()), + }, + block_manager: BlockManagerConfiguration { + producer: self.block_producer, + creation_interval_sec: self.block_creation_interval_sec, + repeat_last_block_messages: self.repeat_last_block_messages, + }, + }; + + configuration.try_write_home_dir(id).ok(); + } +} diff --git a/ephemera/src/cli/mod.rs b/ephemera/src/cli/mod.rs new file mode 100644 index 0000000000..1828d89347 --- /dev/null +++ b/ephemera/src/cli/mod.rs @@ -0,0 +1,49 @@ +use crate::cli::crypto::GenerateKeypairCmd; +use clap::Parser; + +pub mod config; +mod crypto; +pub mod init; +pub mod peers; +pub mod run_node; + +pub const PEERS_CONFIG_FILE: &str = "peers.toml"; + +#[derive(Parser)] +#[command()] +pub struct Cli { + #[command(subcommand)] + pub subcommand: Subcommand, +} + +#[derive(clap::Subcommand)] +pub enum Subcommand { + InitConfig(init::Cmd), + InitLocalPeersConfig(peers::CreateLocalPeersConfiguration), + RunNode(run_node::RunExternalNodeCmd), + GenerateKeypair(crypto::GenerateKeypairCmd), + UpdateConfig(config::UpdateConfigCmd), +} + +impl Cli { + /// # Errors + /// Returns an error if the subcommand fails. + pub async fn execute(self) -> anyhow::Result<()> { + match self.subcommand { + Subcommand::InitConfig(init) => { + init.execute(None); + } + Subcommand::InitLocalPeersConfig(add_local_peers) => { + add_local_peers.execute(); + } + Subcommand::RunNode(run_node) => run_node.execute().await?, + Subcommand::GenerateKeypair(_) => { + GenerateKeypairCmd::execute(); + } + Subcommand::UpdateConfig(update_config) => { + update_config.execute(); + } + } + Ok(()) + } +} diff --git a/ephemera/src/cli/peers.rs b/ephemera/src/cli/peers.rs new file mode 100644 index 0000000000..c77f4974e2 --- /dev/null +++ b/ephemera/src/cli/peers.rs @@ -0,0 +1,65 @@ +//! This is used to create local peers configuration file. Useful for local cluster development. + +use crate::cli::PEERS_CONFIG_FILE; +use clap::Parser; + +use crate::config::Configuration; +use crate::crypto::{EphemeraKeypair, EphemeraPublicKey, Keypair}; +use crate::membership::PeerSetting; +use crate::network::members::ConfigPeers; + +#[derive(Debug, Clone, Parser)] +pub struct CreateLocalPeersConfiguration; + +impl CreateLocalPeersConfiguration { + /// # Panics + /// Panics if the configuration file cannot be written. + pub fn execute(self) { + let peers = Self::from_ephemera_dev_cluster_conf().unwrap(); + let config_peers = ConfigPeers::new(peers); + + let peers_conf_path = Configuration::ephemera_root_dir(None) + .unwrap() + .join(PEERS_CONFIG_FILE); + + config_peers.try_write(peers_conf_path).unwrap(); + } + + //LOCAL DEV CLUSTER ONLY + //Get peers from dev Ephemera cluster config files + pub(crate) fn from_ephemera_dev_cluster_conf() -> anyhow::Result> { + let ephemera_root_dir = Configuration::ephemera_root_dir(None).unwrap(); + + let mut peers = vec![]; + + let home_dir = std::fs::read_dir(ephemera_root_dir)?; + for entry in home_dir { + let path = entry?.path(); + if path.is_dir() { + let cosmos_address = path.file_name().unwrap().to_str().unwrap(); + + println!("Reading peer info config from node {cosmos_address}",); + + let conf = Configuration::try_load_from_home_dir().unwrap_or_else(|_| { + panic!("Error loading configuration for node {cosmos_address}") + }); + + let node_info = conf.node; + + let keypair = bs58::decode(&node_info.private_key).into_vec().unwrap(); + let keypair = Keypair::from_bytes(&keypair).unwrap(); + + let peer = PeerSetting { + cosmos_address: cosmos_address.to_string(), + address: format!("/ip4/{}/tcp/{}", node_info.ip, conf.libp2p.port), + public_key: keypair.public_key().to_base58(), + }; + peers.push(peer); + + println!("Loaded config for node {cosmos_address}",); + } + } + + Ok(peers) + } +} diff --git a/ephemera/src/cli/run_node.rs b/ephemera/src/cli/run_node.rs new file mode 100644 index 0000000000..a33854f52d --- /dev/null +++ b/ephemera/src/cli/run_node.rs @@ -0,0 +1,140 @@ +use anyhow::anyhow; +use std::str::FromStr; +use std::sync::Arc; + +use clap::Parser; +use log::trace; +use nym_task::TaskManager; +use reqwest::Url; + +use crate::ephemera_api::ApplicationResult; +use crate::utilities::codec::{Codec, EphemeraCodec}; +use crate::{ + api::application::CheckBlockResult, + cli::PEERS_CONFIG_FILE, + config::Configuration, + crypto::EphemeraKeypair, + crypto::Keypair, + ephemera_api::{ApiBlock, ApiEphemeraMessage, Application, Dummy, RawApiEphemeraMessage}, + network::members::ConfigMembersProvider, + EphemeraStarterInit, +}; + +#[derive(Clone, Debug)] +pub struct HttpMembersProviderArg { + pub url: Url, +} + +impl FromStr for HttpMembersProviderArg { + type Err = anyhow::Error; + + fn from_str(s: &str) -> Result { + Ok(HttpMembersProviderArg { url: s.parse()? }) + } +} + +#[derive(Parser)] +pub struct RunExternalNodeCmd { + #[clap(short, long)] + pub config_file: String, + #[clap(short, long)] + pub peers_config: String, +} + +impl RunExternalNodeCmd { + /// # Errors + /// If the members provider cannot be created. + /// + /// # Panics + /// If the ephemera cannot be created. + pub async fn execute(&self) -> anyhow::Result<()> { + let ephemera_conf = match Configuration::try_load(self.config_file.clone()) { + Ok(conf) => conf, + Err(err) => anyhow::bail!("Error loading configuration file: {err:?}"), + }; + + let members_provider = Self::config_members_provider_with_path(self.peers_config.clone())?; + let ephemera = EphemeraStarterInit::new(ephemera_conf.clone()) + .unwrap() + .with_application(Dummy) + .with_members_provider(members_provider)? + .build(); + + let shutdown = TaskManager::new(10); + + let shutdown_listener = shutdown.subscribe(); + tokio::spawn(ephemera.run(shutdown_listener)); + + if let Err(err) = shutdown.catch_interrupt().await { + Err(anyhow!("Shutdown error {:?}", err)) + } else { + Ok(()) + } + } + + #[allow(dead_code)] + fn config_members_provider() -> anyhow::Result { + let peers_conf_path = Configuration::ephemera_root_dir(None) + .unwrap() + .join(PEERS_CONFIG_FILE); + + let peers_conf = match ConfigMembersProvider::init(peers_conf_path) { + Ok(conf) => conf, + Err(err) => anyhow::bail!("Error loading peers file: {err:?}"), + }; + Ok(peers_conf) + } + + #[allow(dead_code)] + fn config_members_provider_with_path( + peers_conf_path: String, + ) -> anyhow::Result { + let peers_conf = match ConfigMembersProvider::init(peers_conf_path) { + Ok(conf) => conf, + Err(err) => anyhow::bail!("Error loading peers file: {err:?}"), + }; + Ok(peers_conf) + } +} + +pub struct SignatureVerificationApplication { + keypair: Arc, +} + +impl SignatureVerificationApplication { + #[must_use] + pub fn new(keypair: Arc) -> Self { + Self { keypair } + } + + pub(crate) fn verify_message(&self, msg: ApiEphemeraMessage) -> anyhow::Result<()> { + let signature = msg.certificate.clone(); + let raw_message: RawApiEphemeraMessage = msg.into(); + let encoded_message = Codec::encode(&raw_message)?; + if self + .keypair + .verify(&encoded_message, &signature.signature.into()) + { + Ok(()) + } else { + anyhow::bail!("Invalid signature") + } + } +} + +impl Application for SignatureVerificationApplication { + fn check_tx(&self, tx: ApiEphemeraMessage) -> ApplicationResult { + trace!("SignatureVerificationApplicationHook::check_tx"); + self.verify_message(tx)?; + Ok(true) + } + + fn check_block(&self, _block: &ApiBlock) -> ApplicationResult { + Ok(CheckBlockResult::Accept) + } + + fn deliver_block(&self, _block: ApiBlock) -> ApplicationResult<()> { + trace!("SignatureVerificationApplicationHook::deliver_block"); + Ok(()) + } +} diff --git a/ephemera/src/config/mod.rs b/ephemera/src/config/mod.rs new file mode 100644 index 0000000000..bf30850928 --- /dev/null +++ b/ephemera/src/config/mod.rs @@ -0,0 +1,334 @@ +//! Configuration for Ephemeris node. It contains mandatory settings for a node to start. +//! +//! Default location for the configuration file is `~/.nym/ephemera/ephemera.toml`. +//! Or relative to a node specific directory `~/.nym/ephemera//ephemera.toml`. + +use std::io::Write; +use std::path::PathBuf; + +use config::ConfigError; +use log::{error, info}; +use nym_config::{DEFAULT_NYM_APIS_DIR, NYM_DIR}; +use serde_derive::{Deserialize, Serialize}; +use thiserror::Error; + +//TODO - validate configuration at load time +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)] +pub struct Configuration { + /// Configuration related to node instance identity + pub node: NodeConfiguration, + /// Configuration for libp2p network + pub libp2p: Libp2pConfiguration, + /// Configuration for Ephemera embedded database + pub storage: DatabaseConfiguration, + /// Configuration for websocket + pub websocket: WebsocketConfiguration, + /// Configuration for embedded http API server + pub http: HttpConfiguration, + /// Configuration related to block creation + pub block_manager: BlockManagerConfiguration, +} + +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)] +pub struct NodeConfiguration { + /// Node IP shared by all Ephemera services like libp2p, websocket, http. + /// If separate IP/DNS is needed for each service, it should be configured outside of Ephemera. + pub ip: String, + //FIXME: dev only + /// If private key is stored in configuration as plain text, read it from here. + /// Private key is mandatory for a node to be able to function in the network. + /// It is used to signe protocol messages and identify node in the network. + pub private_key: String, +} + +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)] +#[serde(rename_all = "lowercase")] +pub enum MembershipKind { + /// Mandatory minimum membership size is defined by threshold of all peers returned by membership provider. + /// Threshold value is defined the ratio of peers that need to be available. + /// For example, if the threshold is 0.5, then at least 50% of the peers need to be available. + Threshold, + /// Mandatory minimum membership size is all peers who are online. + AnyOnline, + /// Mandatory minimum membership size is all peers returned by membership provider. + AllOnline, +} + +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)] +pub struct Libp2pConfiguration { + /// Port to listen on for libp2p internal connections + pub port: u16, + /// Gossipsub topic to gossip Ephemera messages between peers. Ephemera listens messages + /// only from this topic. Invalid topic configuration means that Ephemera is not able to + /// reach messages from other peers. + pub ephemera_msg_topic_name: String, + /// Gossipsub interval to check its mesh health. + pub heartbeat_interval_sec: u64, + /// How often Ephemera checks its membership rendezvous endpoint. It's configurable with second granularity. + /// But in general it's up to the user and depends how rendezvous endpoint is configured. + /// + /// Ephemera uses rendezvous endpoint as authority to tell which nodes are authorized to participate. + /// So it should be configured and implemented in a manner that nodes always have the most up to date and + /// accurate information. + pub members_provider_delay_sec: u64, + /// Defines how the actual membership is decided. See `[ephemera:]` for more details. + pub membership_kind: MembershipKind, +} + +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)] +pub struct DatabaseConfiguration { + /// Path to the RocksDb database directory + pub rocksdb_path: String, + /// Path to the SQLite database file + pub sqlite_path: String, + /// If to create database if it does not exist + pub create_if_not_exists: bool, +} + +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)] +pub struct WebsocketConfiguration { + /// Port to listen on for WebSocket subscriptions. + pub port: u16, +} + +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)] +pub struct HttpConfiguration { + /// Port to listen on for HTTP API requests + pub port: u16, +} + +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)] +pub struct BlockManagerConfiguration { + /// By default every node is block producer. + /// + /// But in trusted settings it might be useful configure only one or selected nodes to be block producer. + /// The rest of nodes still participate in the message gossiping and reliable broadcast. + pub producer: bool, + /// Interval in seconds between block creation + /// Blocks are "proposed" at this interval. + pub creation_interval_sec: u64, + /// Ephemera creates blocks at fixed interval and doesn't have any consensus algorithm to make progress + /// if the most recent block fails to go through reliable broadcast. + /// This flag tells what to do when at next interval previous block is not yet delivered. + /// + /// If true, Ephemera will repeat messages from the previous block. Otherwise it will take all messages + /// from mempool as normally. + pub repeat_last_block_messages: bool, +} + +impl BlockManagerConfiguration { + pub fn new(producer: bool, creation_interval_sec: u64, repeat_last_block: bool) -> Self { + BlockManagerConfiguration { + producer, + creation_interval_sec, + repeat_last_block_messages: repeat_last_block, + } + } +} + +#[derive(Debug, Error)] +pub enum Error { + /// This is returned if configuration file exists and user tries to create new one. + #[error("Configuration file exists: '{0}'")] + Exists(String), + /// This is returned if configuration file does not exist and user tries to load it. + #[error("Configuration file does not exists: '{0}'")] + NotFound(String), + #[error("Configuration file does not exist")] + /// This is returned if IoError happens during configuration file read/write. + IoError(#[from] std::io::Error), + /// This is returned if configuration file is invalid. + #[error("Configuration file is invalid: '{0}'")] + InvalidFormat(String), + /// This is returned if configuration file path is invalid. + #[error("Configuration file path is invalid: '{0}'")] + InvalidPath(String), + /// Technical error happens during parsing. + #[error("{}", .0)] + Other(String), +} + +impl From for Error { + fn from(err: ConfigError) -> Self { + match err { + ConfigError::NotFound(err) => Error::NotFound(err), + ConfigError::PathParse(err) => { + Error::InvalidPath(format!("Invalid path to configuration file: {err:?}",)) + } + ConfigError::FileParse { uri, cause } => { + Error::InvalidFormat(format!("Invalid configuration file: {uri:?}: {cause:?}",)) + } + _ => Error::Other(err.to_string()), + } + } +} + +const EPHEMERA_DIR_NAME: &str = "ephemera"; +const EPHEMERA_CONFIG_FILE: &str = "ephemera.toml"; + +type Result = std::result::Result; + +impl Configuration { + /// Tries to read Ephemera node configuration file (`ephemera.toml`) from the given path. + /// + /// # Arguments + /// * `path` - Path to the configuration file. + /// + /// # Errors + /// Returns an error if the configuration file does not exist or is invalid. + /// + /// # Example + /// ```no_run + /// use ephemera::configuration::Configuration; + /// + /// let config = Configuration::try_load("/ephemera/ephemera.toml"); + /// ``` + pub fn try_load>(path: P) -> Result { + let buf = path.into(); + log::debug!("Loading configuration from {:?}", buf); + let config = config::Config::builder() + .add_source(config::File::from(buf)) + .build() + .map_err(Error::from)?; + + config.try_deserialize().map_err(Error::from) + } + + /// Tries to read Ephemera node configuration from default + /// default Ephemera configuration directory(`~.nym/ephemera`). Full path resolves + /// as `~/.nym/ephemera//`. + /// + /// # Arguments + /// * `node_name` - Name of the node. + /// * `file` - Name of the configuration file. + /// + /// # Errors + /// Returns an error if the configuration file does not exist or is invalid. + pub fn try_load_node_from_home_dir(file: &str) -> Result { + let file_path = Self::ephemera_node_dir(None)?.join(file); + Configuration::try_load(file_path) + } + + /// Tries to read Ephemera node configuration from default Ephemera configuration directory(`~.nym/ephemera`). + /// Full path resolves as `~/.nym/ephemera//ephemera.toml`. + /// + /// # Arguments + /// * `node_name` - Name of the node. + /// + /// # Errors + /// Returns an error if the configuration file does not exist or is invalid. + pub fn try_load_from_home_dir() -> Result { + let file_path = Configuration::ephemera_config_file_home(None)?; + let config = config::Config::builder() + .add_source(config::File::from(file_path)) + .build() + .map_err(Error::from)?; + + config.try_deserialize().map_err(Error::from) + } + + /// Tries to write(create) Ephemera node configuration file (`ephemera.toml`) relative to default + /// Ephemera configuration directory(`~.nym/ephemera`). Full path resolves as `~/.nym/ephemera//ephemera.toml`. + /// + /// # Arguments + /// * `id` - Id of the node. + /// + /// # Errors + /// Returns an error if the configuration file already exists. + /// + /// # Panics + /// Panics if the configuration file cannot be written. + pub fn try_write_home_dir(&self, id: Option<&str>) -> Result<()> { + let conf_path = Configuration::ephemera_node_dir(id)?; + if !conf_path.exists() { + std::fs::create_dir_all(conf_path)?; + } + + let file_path = Configuration::ephemera_config_file_home(id)?; + if file_path.exists() { + return Err(Error::Exists(file_path.to_str().unwrap().to_string())); + } + + self.write(&file_path)?; + Ok(()) + } + + /// Tries to write(update) Ephemera node configuration file (`ephemera.toml`) relative to default + /// Ephemera configuration directory(`~.nym/ephemera`). Full path resolves as `~/.nym/ephemera//ephemera.toml`. + /// If the file does not exist, update will be refused. + /// + /// # Arguments + /// * `node_name` - Name of the node. + /// + /// # Errors + /// Returns an error if the configuration file does not exist. + /// + /// # Panics + /// Panics if the configuration file cannot be written. + pub fn try_update_home_dir(&self) -> Result<()> { + let file_path = Configuration::ephemera_config_file_home(None)?; + if !file_path.exists() { + error!( + "Configuration file does not exist {}", + file_path.to_str().unwrap() + ); + return Err(Error::NotFound(file_path.to_str().unwrap().to_string())); + } + self.write(&file_path)?; + Ok(()) + } + + /// Returns node configuration file path relative to default Ephemera configuration directory(`~.nym/ephemera`). + /// Full path resolves as `~/.nym/ephemera//ephemera.toml`. + /// + /// # Arguments + /// * `id` - Id of the node. + /// + /// # Errors + /// Returns an error if the configuration file path cannot be resolved. + pub fn ephemera_config_file_home(id: Option<&str>) -> Result { + Ok(Self::ephemera_node_dir(id)?.join(EPHEMERA_CONFIG_FILE)) + } + + /// Returns default Ephemera configuration directory(`~.nym/ephemera`). + /// + /// # Errors + /// Returns an error if the configuration directory cannot be resolved. + pub fn ephemera_root_dir(id: Option<&str>) -> Result { + let id = id.unwrap_or_default(); + dirs::home_dir() + .map(|home| { + home.join(NYM_DIR) + .join(DEFAULT_NYM_APIS_DIR) + .join(id) + .join(EPHEMERA_DIR_NAME) + }) + .ok_or(Error::Other("Could not find home directory".to_string())) + } + + pub(crate) fn ephemera_node_dir(id: Option<&str>) -> Result { + Self::ephemera_root_dir(id) + } + + fn write(&self, file_path: &PathBuf) -> Result<()> { + //TODO: use toml or config crate, not both + let config = toml::to_string(&self).map_err(|e| { + Error::InvalidFormat(format!("Failed to serialize configuration: {e}",)) + })?; + + let config = format!( + "#This file is generated by cli and automatically overwritten every time when cli is run\n{config}", + ); + + if file_path.exists() { + info!("Updating configuration file: '{}'", file_path.display()); + } else { + info!("Writing configuration to file: '{}'", file_path.display()); + } + + let mut file = std::fs::File::create(file_path)?; + file.write_all(config.as_bytes())?; + + Ok(()) + } +} diff --git a/ephemera/src/core/api_cmd.rs b/ephemera/src/core/api_cmd.rs new file mode 100644 index 0000000000..2d5647adec --- /dev/null +++ b/ephemera/src/core/api_cmd.rs @@ -0,0 +1,390 @@ +use std::num::NonZeroUsize; + +use log::{debug, error, trace}; +use lru::LruCache; +use tokio::sync::oneshot::Sender; + +use crate::api::types::{ApiBlockBroadcastInfo, ApiBroadcastInfo}; +use crate::api::{DhtKV, DhtKey, DhtValue}; +use crate::ephemera_api::ApiEphemeraMessage; +use crate::peer::ToPeerId; +use crate::{ + api::{ + self, + application::Application, + types::{ApiBlock, ApiCertificate, ApiError}, + ToEphemeraApiCmd, + }, + block::{manager::BlockManagerError, types::message}, + crypto::EphemeraKeypair, + ephemera_api::ApiEphemeraConfig, + network::libp2p::ephemera_sender::EphemeraEvent, + Ephemera, +}; + +type DhtPendingQueryReply = Sender, Vec)>, ApiError>>; + +pub(crate) struct ApiCmdProcessor { + pub(crate) dht_query_cache: LruCache, Vec>, +} + +impl ApiCmdProcessor { + pub(crate) fn new() -> Self { + Self { + dht_query_cache: LruCache::new(NonZeroUsize::new(1000).unwrap()), + } + } + + pub(crate) async fn process_api_requests( + ephemera: &mut Ephemera, + cmd: ToEphemeraApiCmd, + ) -> api::Result<()> { + trace!("Processing API request: {:?}", cmd); + match cmd { + ToEphemeraApiCmd::SubmitEphemeraMessage(api_msg, reply) => { + // Ask application to decide if we should accept this message. + Self::submit_message(ephemera, api_msg, reply).await?; + } + + ToEphemeraApiCmd::QueryBlockByHash(block_hash, reply) => { + Self::query_block_by_hash(ephemera, &block_hash, reply).await; + } + + ToEphemeraApiCmd::QueryBlockByHeight(height, reply) => { + Self::query_block_by_height(ephemera, height, reply).await; + } + + ToEphemeraApiCmd::QueryLastBlock(reply) => { + Self::query_last_block(ephemera, reply).await; + } + + ToEphemeraApiCmd::QueryBlockCertificates(block_id, reply) => { + Self::query_block_certificates(ephemera, &block_id, reply).await; + } + + ToEphemeraApiCmd::QueryDht(key, reply) => { + Self::query_dht(ephemera, key, reply).await; + } + + ToEphemeraApiCmd::StoreInDht(key, value, reply) => { + Self::store_in_dht(ephemera, key, value, reply).await; + } + + ToEphemeraApiCmd::QueryEphemeraConfig(reply) => { + Self::ephemera_config(ephemera, reply); + } + + ToEphemeraApiCmd::QueryBroadcastGroup(reply) => { + Self::broadcast_group(ephemera, reply); + } + ToEphemeraApiCmd::QueryBlockBroadcastInfo(hash, reply) => { + Self::query_block_broadcast_info(ephemera, &hash, reply).await; + } + ToEphemeraApiCmd::VerifyMessageInBlock(block_hash, message_hash, index, reply) => { + Self::verify_message_in_block(ephemera, block_hash, message_hash, index, reply) + .await; + } + } + Ok(()) + } + + fn broadcast_group( + ephemera: &mut Ephemera, + reply: Sender>, + ) { + let group_peers = ephemera.broadcast_group.current(); + + let bc = ApiBroadcastInfo::new(group_peers.clone(), ephemera.node_info.peer_id); + reply + .send(Ok(bc)) + .expect("Error sending BroadcastGroup response to api"); + } + + fn ephemera_config( + ephemera: &mut Ephemera, + reply: Sender>, + ) { + let node_info = ephemera.node_info.clone(); + let api_config = ApiEphemeraConfig { + protocol_address: node_info.protocol_address(), + api_address: node_info.api_address_http(), + websocket_address: node_info.ws_address_ws(), + public_key: node_info.keypair.public_key().to_string(), + block_producer: node_info.initial_config.block_manager.producer, + block_creation_interval_sec: node_info + .initial_config + .block_manager + .creation_interval_sec, + }; + reply + .send(Ok(api_config)) + .expect("Error sending EphemeraConfig response to api"); + } + + async fn store_in_dht( + ephemera: &mut Ephemera, + key: DhtKey, + value: DhtValue, + reply: Sender>, + ) { + let response = match ephemera + .to_network + .send_ephemera_event(EphemeraEvent::StoreInDht { key, value }) + .await + { + Ok(_) => Ok(()), + Err(err) => { + error!("Error sending StoreInDht to network: {:?}", err); + Err(ApiError::Internal("Failed to store in DHT".to_string())) + } + }; + reply + .send(response) + .expect("Error sending StoreInDht response to api"); + } + + async fn query_dht( + ephemera: &mut Ephemera, + key: DhtKey, + reply: Sender>>, + ) { + match ephemera + .to_network + .send_ephemera_event(EphemeraEvent::QueryDht { key: key.clone() }) + .await + { + Ok(_) => { + //Save the reply channel in a map and send the reply when we get the response from the network + ephemera + .api_cmd_processor + .dht_query_cache + .get_or_insert_mut(key, Vec::new) + .push(reply); + } + Err(err) => { + error!("Error sending QueryDht to network: {:?}", err); + reply + .send(Err(ApiError::Internal("Failed to query DHT".to_string()))) + .expect("Error sending QueryDht response to api"); + } + }; + } + + async fn query_block_certificates( + ephemera: &mut Ephemera, + block_id: &str, + reply: Sender>>>, + ) { + let response = match ephemera + .storage + .lock() + .await + .get_block_certificates(block_id) + { + Ok(signatures) => { + let certificates = signatures.map(|s| { + s.into_iter() + .map(Into::into) + .collect::>() + }); + Ok(certificates) + } + Err(err) => { + error!("Error querying block certificates: {:?}", err); + Err(ApiError::Internal( + "Failed to query block certificates".to_string(), + )) + } + }; + reply + .send(response) + .expect("Error sending QueryBlockSignatures response to api"); + } + + async fn query_last_block( + ephemera: &mut Ephemera, + reply: Sender>, + ) { + let response = match ephemera.storage.lock().await.get_last_block() { + Ok(Some(block)) => Ok(block.into()), + Ok(None) => Err(ApiError::Internal( + "No blocks found, this is a bug!".to_string(), + )), + Err(err) => { + error!("Error querying last block: {:?}", err); + Err(ApiError::Internal("Failed to query last block".to_string())) + } + }; + reply + .send(response) + .expect("Error sending QueryLastBlock response to api"); + } + + async fn query_block_by_height( + ephemera: &mut Ephemera, + height: u64, + reply: Sender>>, + ) { + let response = match ephemera.storage.lock().await.get_block_by_height(height) { + Ok(Some(block)) => { + let api_block: ApiBlock = block.into(); + Ok(api_block.into()) + } + Ok(None) => Ok(None), + Err(err) => { + error!("Error querying block by height: {:?}", err); + Err(ApiError::Internal( + "Failed to query block by height".to_string(), + )) + } + }; + reply + .send(response) + .expect("Error sending QueryBlockByHeight response to api"); + } + + async fn query_block_by_hash( + ephemera: &mut Ephemera, + block_hash: &str, + reply: Sender>>, + ) { + let response = match ephemera.storage.lock().await.get_block_by_hash(block_hash) { + Ok(Some(block)) => { + let api_block: ApiBlock = block.into(); + Ok(api_block.into()) + } + Ok(None) => Ok(None), + Err(err) => { + error!("Error querying block by id: {:?}", err); + Err(ApiError::Internal( + "Failed to query block by id".to_string(), + )) + } + }; + reply + .send(response) + .expect("Error sending QueryBlockByHash response to api"); + } + + async fn submit_message( + ephemera: &mut Ephemera, + api_msg: Box, + reply: Sender>, + ) -> api::Result<()> { + let response = match ephemera.application.check_tx(*api_msg.clone()) { + Ok(true) => { + trace!("Application accepted ephemera message: {:?}", api_msg); + + // Send to BlockManager to verify it and put into memory pool + let ephemera_msg: message::EphemeraMessage = (*api_msg).into(); + match ephemera.block_manager.on_new_message(ephemera_msg.clone()) { + Ok(_) => { + //Gossip to network for other nodes to receive + match ephemera + .to_network + .send_ephemera_event(EphemeraEvent::EphemeraMessage( + ephemera_msg.into(), + )) + .await + { + Ok(_) => Ok(()), + Err(err) => { + error!("Error sending EphemeraMessage to network: {:?}", err); + Err(ApiError::Internal("Failed to submit message".to_string())) + } + } + } + Err(err) => match err { + BlockManagerError::DuplicateMessage(_) => Err(ApiError::DuplicateMessage), + BlockManagerError::BlockManager(err) => { + error!("Error submitting message to block manager: {:?}", err); + Err(ApiError::Internal("Failed to submit message".to_string())) + } + }, + } + } + Ok(false) => { + debug!("Application rejected ephemera message: {:?}", api_msg); + Err(ApiError::ApplicationRejectedMessage) + } + Err(err) => { + error!("Application rejected ephemera message: {:?}", err); + Err(ApiError::Application(err)) + } + }; + reply + .send(response) + .expect("Error sending SubmitEphemeraMessage response to api"); + Ok(()) + } + + async fn query_block_broadcast_info( + ephemera: &mut Ephemera, + block_id: &str, + reply: Sender>>, + ) { + let response = match ephemera + .storage + .lock() + .await + .get_block_broadcast_group(block_id) + { + Ok(Some(peers)) => { + let local_peer = ephemera.node_info.keypair.peer_id(); + Ok(Some(ApiBlockBroadcastInfo::new(local_peer, peers))) + } + Ok(None) => Ok(None), + Err(err) => { + error!("Error querying block broadcast info: {:?}", err); + Err(ApiError::Internal( + "Failed to query block broadcast info".to_string(), + )) + } + }; + reply + .send(response) + .expect("Error sending QueryBlockBroadcastGroup response to api"); + } + async fn verify_message_in_block( + ephemera: &mut Ephemera, + block_hash: String, + message_hash: String, + index: usize, + reply: Sender>, + ) { + let message_hash_hash = message_hash.parse(); + if message_hash_hash.is_err() { + reply + .send(Err(ApiError::InvalidHash( + "Failed to parse message hash".to_string(), + ))) + .expect("Error sending VerifyMessageInBlock response to api"); + return; + } + + let message_hash_hash = message_hash_hash.unwrap(); + + let storage = ephemera.storage.lock().await; + match storage.get_block_merkle_tree(&block_hash) { + Ok(Some(tree)) => { + let result = tree.verify_leaf_at_index(message_hash_hash, index); + reply + .send(Ok(result)) + .expect("Error sending VerifyMessageInBlock response to api"); + } + Ok(None) => { + reply + .send(Ok(false)) + .expect("Error sending VerifyMessageInBlock response to api"); + } + Err(err) => { + error!("Error querying block merkle tree: {:?}", err); + reply + .send(Err(ApiError::Internal( + "Failed to verify message".to_string(), + ))) + .expect("Error sending VerifyMessageInBlock response to api"); + } + } + } +} diff --git a/ephemera/src/core/builder.rs b/ephemera/src/core/builder.rs new file mode 100644 index 0000000000..db9b3e8254 --- /dev/null +++ b/ephemera/src/core/builder.rs @@ -0,0 +1,413 @@ +use std::fmt::Display; +use std::future::Future; +use std::sync::Arc; + +use futures_util::future::BoxFuture; +use futures_util::FutureExt; +use log::{debug, error, info}; +use tokio::sync::Mutex; + +use crate::core::shutdown::Shutdown; +#[cfg(feature = "rocksdb_storage")] +use crate::storage::rocksdb::RocksDbStorage; +#[cfg(feature = "sqlite_storage")] +use crate::storage::sqlite::SqliteStorage; +use crate::{ + api::{application::Application, http, ApiListener, CommandExecutor}, + block::{builder::BlockManagerBuilder, manager::BlockManager}, + broadcast::bracha::broadcast::Broadcaster, + broadcast::group::BroadcastGroup, + config::Configuration, + core::{ + api_cmd::ApiCmdProcessor, + shutdown::{Handle, ShutdownManager}, + }, + crypto::Keypair, + membership, + membership::PeerInfo, + network::libp2p::{ + ephemera_sender::EphemeraToNetworkSender, network_sender::NetCommunicationReceiver, + swarm_network::SwarmNetwork, + }, + peer::{PeerId, ToPeerId}, + storage::EphemeraDatabase, + utilities::crypto::key_manager::KeyManager, + websocket::ws_manager::{WsManager, WsMessageBroadcaster}, + Ephemera, +}; + +#[derive(Clone)] +pub(crate) struct NodeInfo { + pub(crate) ip: String, + pub(crate) protocol_port: u16, + pub(crate) http_port: u16, + pub(crate) ws_port: u16, + pub(crate) peer_id: PeerId, + pub(crate) keypair: Arc, + pub(crate) initial_config: Configuration, +} + +impl NodeInfo { + pub(crate) fn new(config: Configuration) -> anyhow::Result { + let keypair = KeyManager::read_keypair_from_str(&config.node.private_key)?; + let info = Self { + ip: config.node.ip.clone(), + protocol_port: config.libp2p.port, + http_port: config.http.port, + ws_port: config.websocket.port, + peer_id: keypair.peer_id(), + keypair, + initial_config: config, + }; + Ok(info) + } + + pub(crate) fn protocol_address(&self) -> String { + format!("/ip4/{}/tcp/{}", self.ip, self.protocol_port) + } + + pub(crate) fn api_address_http(&self) -> String { + format!("http://{}:{}", self.ip, self.http_port) + } + + pub(crate) fn ws_address_ws(&self) -> String { + format!("ws://{}:{}", self.ip, self.ws_port) + } + + pub(crate) fn ws_address_ip_port(&self) -> String { + format!("{}:{}", self.ip, self.ws_port) + } +} + +impl Display for NodeInfo { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, + "NodeInfo {{ ip: {}, protocol_port: {}, http_port: {}, ws_port: {}, peer_id: {}, keypair: {} }}", + self.ip, + self.protocol_port, + self.http_port, + self.ws_port, + self.peer_id, + self.keypair + ) + } +} + +#[derive(Clone)] +pub struct EphemeraHandle { + /// Ephemera API + pub api: CommandExecutor, + /// Allows to send shutdown signal to the node + pub shutdown: Handle, +} + +pub struct EphemeraStarterInit { + config: Configuration, + node_info: NodeInfo, + broadcaster: Broadcaster, + api_listener: ApiListener, + api: CommandExecutor, +} + +impl EphemeraStarterInit { + /// Initialize Ephemera builder + /// + /// # Arguments + /// * `config` - [Configuration] + /// + /// # Returns + /// [`EphemeraStarterInit`] + /// + /// # Errors + /// * If the node configuration is invalid + pub fn new(config: Configuration) -> anyhow::Result { + let instance_info = NodeInfo::new(config.clone())?; + let broadcaster = Broadcaster::new(instance_info.peer_id); + let (api, api_listener) = CommandExecutor::new(); + + let builder = EphemeraStarterInit { + config, + node_info: instance_info, + broadcaster, + api_listener, + api, + }; + Ok(builder) + } + + pub fn with_application( + self, + application: A, + ) -> EphemeraStarterWithApplication { + EphemeraStarterWithApplication { + init: self, + application, + } + } +} + +#[derive(Default)] +struct ServiceInfo { + ws_message_broadcast: Option, + from_network: Option, + to_network: Option, +} + +pub struct EphemeraStarterWithApplication { + init: EphemeraStarterInit, + application: A, +} + +impl EphemeraStarterWithApplication { + /// Initialize Ephemera with the given application. + /// It also tries to open the database connection. + /// + /// # Arguments + /// * `application` - [Application] to be used + /// + /// # Returns + /// [`EphemeraStarterWithApplication`] + /// + /// # Errors + /// * If the node configuration is invalid or the database connection cannot be opened + pub fn with_members_provider< + P: Future>> + Send + Unpin + 'static, + >( + mut self, + provider: P, + ) -> anyhow::Result> { + cfg_if::cfg_if! { + if #[cfg(feature = "sqlite_storage")] { + let mut storage = self.connect_sqlite()?; + debug!("Connected to sqlite database") + } else if #[cfg(feature = "rocksdb_storage")] { + let mut storage = self.connect_rocksdb()?; + debug!("Connected to rocksdb database") + } else { + compile_error!("Must enable either sqlite or rocksdb feature"); + } + } + + let block_manager = self.init_block_manager(&mut storage)?; + + let (mut shutdown_manager, shutdown_handle) = ShutdownManager::init(); + + let mut service_data = ServiceInfo::default(); + let services = self.init_services(&mut service_data, &mut shutdown_manager, provider)?; + + Ok(EphemeraStarterWithProvider { + with_application: self, + block_manager: Some(block_manager), + service_data, + services, + storage: Some(Box::new(storage)), + shutdown_manager: Some(shutdown_manager), + shutdown_handle: Some(shutdown_handle), + }) + } + + //allocate database connection + #[cfg(feature = "rocksdb_storage")] + fn connect_rocksdb(&self) -> anyhow::Result { + info!("Opening database..."); + RocksDbStorage::open(self.init.config.storage.clone()) + .map_err(|e| anyhow::anyhow!("Failed to open database: {}", e)) + } + + #[cfg(feature = "sqlite_storage")] + fn connect_sqlite(&mut self) -> anyhow::Result { + info!("Opening database..."); + SqliteStorage::open(self.init.config.storage.clone()) + .map_err(|e| anyhow::anyhow!("Failed to open database: {}", e)) + } + + fn init_block_manager( + &mut self, + db: &mut D, + ) -> anyhow::Result { + let block_manager_configuration = self.init.config.block_manager.clone(); + let keypair = self.init.node_info.keypair.clone(); + let builder = BlockManagerBuilder::new(block_manager_configuration, keypair); + builder.build(db) + } + + fn init_services< + P: Future>> + Send + Unpin + 'static, + >( + &mut self, + service_data: &mut ServiceInfo, + shutdown_manager: &mut ShutdownManager, + provider: P, + ) -> anyhow::Result>>> { + let services = vec![ + self.init_libp2p(service_data, shutdown_manager.subscribe(), provider)?, + self.init_http(shutdown_manager.subscribe())?, + self.init_websocket(service_data, shutdown_manager.subscribe()), + ]; + Ok(services) + } + + fn init_websocket( + &mut self, + service_data: &mut ServiceInfo, + mut shutdown: Shutdown, + ) -> BoxFuture<'static, anyhow::Result<()>> { + let (mut websocket, ws_message_broadcast) = + WsManager::new(self.init.node_info.ws_address_ip_port()); + + service_data.ws_message_broadcast = Some(ws_message_broadcast); + + async move { + websocket.listen().await?; + + tokio::select! { + _ = shutdown.shutdown_signal_rcv.recv() => { + info!("Shutting down websocket manager"); + } + ws_stopped = websocket.run() => { + match ws_stopped { + Ok(_) => info!("Websocket stopped unexpectedly"), + Err(e) => error!("Websocket stopped with error: {}", e), + } + } + } + info!("Websocket task finished"); + Ok(()) + } + .boxed() + } + + fn init_http( + &mut self, + mut shutdown: Shutdown, + ) -> anyhow::Result>> { + let http = http::init(&self.init.node_info, self.init.api.clone())?; + + let fut = async move { + let server_handle = http.handle(); + tokio::select! { + _ = shutdown.shutdown_signal_rcv.recv() => { + info!("Shutting down http server"); + server_handle.stop(true).await; + } + http_stopped = http => { + match http_stopped { + Ok(_) => info!("Http server stopped unexpectedly"), + Err(e) => error!("Http server stopped with error: {}", e), + } + } + } + info!("Http task finished"); + Ok(()) + } + .boxed(); + Ok(fut) + } + + fn init_libp2p< + P: Future>> + Send + Unpin + 'static, + >( + &mut self, + service_data: &mut ServiceInfo, + mut shutdown: Shutdown, + provider: P, + ) -> anyhow::Result>> { + info!("Starting network...",); + + let (mut network, from_network, to_network) = + SwarmNetwork::new(self.init.node_info.clone(), provider)?; + + service_data.from_network = Some(from_network); + service_data.to_network = Some(to_network); + + let libp2p = async move { + network.listen()?; + + tokio::select! { + _ = shutdown.shutdown_signal_rcv.recv() => { + info!("Shutting down network"); + } + nw_stopped = network.start() => { + match nw_stopped { + Ok(_) => info!("Network stopped unexpectedly"), + Err(e) => error!("Network stopped with error: {e}",), + } + } + } + info!("Network task finished"); + Ok(()) + } + .boxed(); + Ok(libp2p) + } +} + +pub struct EphemeraStarterWithProvider +where + A: Application + 'static, +{ + with_application: EphemeraStarterWithApplication, + block_manager: Option, + service_data: ServiceInfo, + storage: Option>, + services: Vec>>, + shutdown_manager: Option, + shutdown_handle: Option, +} + +impl EphemeraStarterWithProvider +where + A: Application + 'static, +{ + pub fn build(self) -> Ephemera { + self.ephemera() + } + + fn ephemera(mut self) -> Ephemera { + let ephemera_handle = EphemeraHandle { + api: self.with_application.init.api, + shutdown: self.shutdown_handle.take().unwrap(), + }; + + let node_info = self.with_application.init.node_info; + let application = self.with_application.application; + let block_manager = self.block_manager.expect("Block manager not initialized"); + let broadcaster = self.with_application.init.broadcaster; + let from_network = self + .service_data + .from_network + .expect("From network not initialized"); + let to_network = self + .service_data + .to_network + .expect("To network not initialized"); + let storage = self.storage.expect("Storage not initialized"); + let ws_message_broadcast = self + .service_data + .ws_message_broadcast + .expect("WS message broadcast not initialized"); + let api_listener = self.with_application.init.api_listener; + let shutdown_manager = self + .shutdown_manager + .expect("Shutdown manager not initialized"); + let services = self.services; + + Ephemera { + node_info, + block_manager, + broadcaster, + from_network, + to_network, + broadcast_group: BroadcastGroup::new(), + storage: Arc::new(Mutex::new(storage)), + ws_message_broadcast, + api_listener, + api_cmd_processor: ApiCmdProcessor::new(), + application: application.into(), + ephemera_handle, + shutdown_manager, + services, + } + } +} diff --git a/ephemera/src/core/ephemera.rs b/ephemera/src/core/ephemera.rs new file mode 100644 index 0000000000..9c970b9962 --- /dev/null +++ b/ephemera/src/core/ephemera.rs @@ -0,0 +1,421 @@ +use std::sync::Arc; + +use anyhow::anyhow; +use futures_util::future::BoxFuture; +use futures_util::StreamExt; +use log::{debug, error, info, trace}; +use nym_task::TaskClient; +use thiserror::Error; +use tokio::sync::Mutex; + +use crate::broadcast::bracha::quorum::Quorum; +use crate::storage::DatabaseError; +use crate::{ + api::{application::Application, application::CheckBlockResult, ApiListener}, + block::{manager::BlockManager, types::block::Block}, + broadcast::{ + bracha::broadcast::BroadcastResponse, bracha::broadcast::Broadcaster, + group::BroadcastGroup, RbMsg, + }, + core::{ + api_cmd::ApiCmdProcessor, + builder::{EphemeraHandle, NodeInfo}, + shutdown::ShutdownManager, + }, + network::{ + libp2p::network_sender::GroupChangeEvent, + libp2p::{ + ephemera_sender::{EphemeraEvent, EphemeraToNetworkSender}, + network_sender::{NetCommunicationReceiver, NetworkEvent}, + }, + }, + storage::EphemeraDatabase, + utilities::crypto::Certificate, + websocket::ws_manager::WsMessageBroadcaster, +}; + +#[derive(Error, Debug)] +enum EphemeraCoreError { + #[error("DatabaseFailure: {0}")] + DatabaseFailure(DatabaseError), + //Just a placeholder now + #[error("EphemeraCore: {0}")] + EphemeraCore(#[from] anyhow::Error), +} + +type Result = std::result::Result; + +pub struct Ephemera { + /// Node info + pub(crate) node_info: NodeInfo, + + /// Block manager responsibility includes: + /// - Block production and signing + /// - Block verification for externally received blocks + /// - Message verification sent by clients and gossiped other nodes + pub(crate) block_manager: BlockManager, + + /// Broadcaster is making sure that blocks are deterministically agreed by all nodes. + pub(crate) broadcaster: Broadcaster, + + /// A component which receives messages from network. + pub(crate) from_network: NetCommunicationReceiver, + + /// A component which sends messages to network. + pub(crate) to_network: EphemeraToNetworkSender, + + /// A component which keeps track of broadcast group over time. + pub(crate) broadcast_group: BroadcastGroup, + + /// A component which has mutable access to database. + pub(crate) storage: Arc>>, + + /// A component which broadcasts messages to websocket clients. + pub(crate) ws_message_broadcast: WsMessageBroadcaster, + + /// A component which listens API requests. + pub(crate) api_listener: ApiListener, + + /// A component which processes API requests. + pub(crate) api_cmd_processor: ApiCmdProcessor, + + /// An implementation of Application trait. Provides callbacks to broadcast. + pub(crate) application: Arc, + + ///Interface to external Rust code + pub(crate) ephemera_handle: EphemeraHandle, + + /// A component which handles shutdown. + pub(crate) shutdown_manager: ShutdownManager, + + /// A list of services which are running in background. + pub(crate) services: Vec>>, +} + +impl Ephemera { + ///Provides external api for Rust code to interact with ephemera node. + #[must_use] + pub fn handle(&self) -> EphemeraHandle { + self.ephemera_handle.clone() + } + + /// Main loop of ephemera node. + /// 1. Block manager generates new blocks or receives blocks from network. + /// 2. Run reliable broadcast. + /// 3. Process reliable broadcast result. + /// 4. Process http api request + /// 5. Process rust api request + /// 6. Publish(gossip) messages to network + /// 7. Publish blocks to network + /// 8. Broadcast messages to websocket clients + pub async fn run(mut self, mut shutdown: TaskClient) { + info!("Starting ephemera services"); + for service in self.services.drain(..) { + let handle = tokio::spawn(service); + self.shutdown_manager.add_handle(handle); + } + + info!("Starting ephemera main loop"); + + while !shutdown.is_shutdown() { + tokio::select! { + biased; + _ = shutdown.recv() => { + trace!("UpdateHandler: Received shutdown"); + self.shutdown_manager.stop().await; + break; + } + + // GENERATING NEW BLOCKS + Some((new_block, certificate)) = self.block_manager.next() => { + if let Err(err) = self.process_new_local_block(new_block, certificate).await{ + error!("Error processing new block: {:?}", err); + } + } + + // PROCESSING NETWORK EVENTS + Some(net_event) = self.from_network.net_event_rcv.recv() => { + if let Err(err) = self.process_network_event(net_event).await{ + error!("Error processing network event: {:?}", err); + if let EphemeraCoreError::DatabaseFailure(_) = err { + info!("Database failure. Shutting down ephemera"); + self.shutdown_manager.stop().await; + break; + } + } + } + + //PROCESSING EXTERNAL API REQUESTS + api = self.api_listener.messages_rcv.recv() => { + match api { + Some(api_msg) => { + if let Err(err) = ApiCmdProcessor::process_api_requests(&mut self, api_msg).await{ + error!("Error processing api request: {:?}", err); + } + } + None => { + error!("Error: Api listener channel closed"); + //TODO: handle shutdown + } + } + } + } + } + info!("Ephemera main loop finished"); + } + + async fn process_network_event(&mut self, net_event: NetworkEvent) -> Result<()> { + trace!("New network event: {:?}", net_event); + + match net_event { + NetworkEvent::EphemeraMessage(em) => { + let api_msg = (*em.clone()).into(); + trace!("New ephemera message from network: {:?}", api_msg); + + //Only Application checks if messages are valid(possibly message origin). + //For messages we don't check if sender belongs to group. + + // Ask application to decide if we should accept this message. + match self.application.check_tx(api_msg) { + Ok(true) => { + trace!("Application accepted message: {:?}", em); + + // Send to BlockManager to store in mempool. + if let Err(err) = self.block_manager.on_new_message(*em) { + error!("Error sending signed message to block manager: {:?}", err); + } + } + Ok(false) => { + trace!("Application rejected message: {:?}", em); + } + Err(err) => { + error!("Application check_tx failed: {:?}", err); + } + } + } + NetworkEvent::BroadcastMessage(rb_msg) => { + self.process_block_from_network(*rb_msg).await?; + } + NetworkEvent::GroupUpdate(event) => { + self.process_group_update(event); + } + NetworkEvent::QueryDhtResponse { key, value } => { + match self.api_cmd_processor.dht_query_cache.pop(&key) { + Some(replies) => { + for reply in replies { + let response = Ok(Some((key.clone(), value.clone()))); + if let Err(err) = reply.send(response) { + error!("Error sending dht query response: {:?}", err); + } + } + } + None => { + trace!( + "No dht query cache found for key: {:?}", + String::from_utf8(key) + ); + } + } + } + } + Ok(()) + } + + fn process_group_update(&mut self, event: GroupChangeEvent) { + match event { + GroupChangeEvent::PeersUpdated(peers) => { + info!("New group: {:?}", peers); + info!("{}", Quorum::cluster_size_info(peers.len())); + self.broadcaster.group_updated(peers.len()); + self.broadcast_group.add_snapshot(peers); + self.block_manager.start(); + } + GroupChangeEvent::LocalPeerRemoved(peers) | GroupChangeEvent::NotEnoughPeers(peers) => { + info!("New group: {:?}", peers); + info!("Group update: Local peer removed or not enough peers"); + self.broadcaster.group_updated(0); + self.broadcast_group.add_snapshot(peers); + self.block_manager.stop(); + } + } + } + + async fn process_new_local_block( + &mut self, + new_block: Block, + certificate: Certificate, + ) -> Result<()> { + debug!("New block from block manager: {:?}", new_block.get_hash()); + + let hash = new_block.header.hash; + let block_creator = &self.node_info.peer_id; + let sender = &self.node_info.peer_id; + + // Check if block matches group membership. + if !self + .broadcast_group + .check_membership(hash, block_creator, sender) + { + debug!("Membership check rejected block: {:?}", new_block); + return Ok(()); + } + + //Ephemera ABCI + match self.application.check_block(&new_block.clone().into()) { + Ok(response) => match response { + CheckBlockResult::Accept => { + debug!("Application accepted new block: {hash:?}",); + } + CheckBlockResult::Reject => { + debug!("Application rejected block: {hash:?}",); + return Ok(()); + } + CheckBlockResult::RejectAndRemoveMessages(messages_to_remove) => { + debug!("Application rejected block: {:?}", messages_to_remove); + self.block_manager + .on_application_rejected_block(messages_to_remove) + .map_err(|err| { + anyhow!("Error rejecting block from block manager: {:?}", err) + })?; + } + }, + Err(err) => { + return Err(anyhow!("Application check_block failed: {:?}", err).into()); + } + } + + //Block manager generated new block that nobody hasn't seen yet. + //We start reliable broadcaster protocol to broadcaster it to other nodes. + match self.broadcaster.new_broadcast(new_block) { + Ok(resp) => { + if let BroadcastResponse::Broadcast(msg) = resp { + trace!("Broadcasting new block: {:?}", msg); + + let rb_msg = RbMsg::new(msg, certificate); + self.to_network + .send_ephemera_event(EphemeraEvent::ProtocolMessage(rb_msg.into())) + .await?; + } + } + Err(err) => { + error!("Error starting new broadcast: {:?}", err); + } + } + Ok(()) + } + + //TODO: should we accept more blocks(certificates) from peers after its committed? + async fn process_block_from_network(&mut self, msg: RbMsg) -> Result<()> { + let msg_id = msg.id.clone(); + let block = msg.block(); + let block_creator = &block.header.creator; + let sender = &msg.original_sender; + let hash = block.header.hash; + let certificate = msg.certificate.clone(); + + trace!("New broadcast message from network: {:?}", msg); + + if !self + .broadcast_group + .check_membership(hash, block_creator, sender) + { + return Err(anyhow!("Block doesn't match broacast group").into()); + } + + if let Err(err) = self.block_manager.on_block(sender, block, &certificate) { + return Err(anyhow!("Error sending block to block manager: {:?}", err).into()); + } + let raw_mgs = msg.into(); + match self.broadcaster.handle(&raw_mgs) { + Ok(resp) => { + match resp { + BroadcastResponse::Broadcast(msg) => { + trace!("Broadcasting block to network: {:?}", msg); + + match self.block_manager.sign_block(&msg.block()) { + Ok(certificate) => { + let rb_msg = RbMsg::new(msg, certificate); + self.to_network + .send_ephemera_event(EphemeraEvent::ProtocolMessage( + rb_msg.into(), + )) + .await?; + } + Err(err) => { + return Err(anyhow!("Error signing block: {:?}", err).into()); + } + } + } + BroadcastResponse::Deliver(hash) => { + trace!("Block broadcast complete: {hash:?}",); + let block = self.block_manager.get_block_by_hash(&hash); + match block { + Some(block) => { + if block.header.creator == self.node_info.peer_id { + info!("Block committed, ready to deliver...: {hash:?}",); + + //BlockManager + self.block_manager.on_block_committed(&block).map_err(|e| { + anyhow!( + "Error: BlockManager failed to process block: {e:?}", + ) + })?; + + //Save to database + let certificates = self + .block_manager + .get_block_certificates(&block.header.hash) + .ok_or(anyhow!( + "Error: Block certificates not found for block: {hash:?}" + ))?; + let members = self + .broadcast_group + .get_group_by_block_hash(block.get_hash()) + .ok_or(anyhow!( + "Error: Group not found for block: {hash:?}" + ))?; + + if let Err(e) = self.storage.lock().await.store_block( + &block, + certificates.clone(), + members.clone(), + ) { + return Err(EphemeraCoreError::DatabaseFailure(e)); + } + + // It is open question how much Application `deliver_block` failure should affect + // continuing with next block. + //Application(ABCI) + self.application + .deliver_block(Into::into(block.clone())) + .map_err(|e| { + anyhow!( + "Error: Deliver block to Application failed: {e:?}", + ) + })?; + + //WS + self.ws_message_broadcast.send_block(&block)?; + info!("Block broadcast complete: {hash:?}",); + } + } + None => { + return Err( + anyhow!("Error: Block not found in block manager").into() + ); + } + } + } + BroadcastResponse::Drop(hash) => { + trace!("Ignoring broadcast message {:?}[block {:?}]", msg_id, hash); + return Ok(()); + } + } + } + Err(err) => { + error!("Error handling broadcast message: {:?}", err); + } + } + Ok(()) + } +} diff --git a/ephemera/src/core/mod.rs b/ephemera/src/core/mod.rs new file mode 100644 index 0000000000..f6bd82aa49 --- /dev/null +++ b/ephemera/src/core/mod.rs @@ -0,0 +1,4 @@ +pub(crate) mod api_cmd; +pub(crate) mod builder; +pub(crate) mod ephemera; +pub(crate) mod shutdown; diff --git a/ephemera/src/core/shutdown.rs b/ephemera/src/core/shutdown.rs new file mode 100644 index 0000000000..ee2e9c3c70 --- /dev/null +++ b/ephemera/src/core/shutdown.rs @@ -0,0 +1,77 @@ +use log::info; + +use tokio::sync::broadcast; +use tokio::task::JoinHandle; + +pub(crate) struct ShutdownManager { + pub(crate) shutdown_tx: broadcast::Sender<()>, + pub(crate) _shutdown_rcv: broadcast::Receiver<()>, + handles: Vec>>, +} + +pub(crate) struct Shutdown { + pub(crate) shutdown_signal_rcv: broadcast::Receiver<()>, +} + +#[derive(Clone)] +pub struct Handle { + pub(crate) shutdown_started: bool, +} + +impl Handle { + /// Shutdown the node. + /// This will send a shutdown signal to all tasks and wait for them to finish. + /// + /// # Errors + /// This will return an error if shutdown signal can't be sent. + /// + /// # Panics + /// This will panic if shutdown signal can't be sent. + pub fn shutdown(&mut self) { + self.shutdown_started = true; + } +} + +impl ShutdownManager { + pub(crate) fn init() -> (ShutdownManager, Handle) { + let (shutdown_tx, shutdown_rcv) = broadcast::channel(1); + let shutdown_handle = Handle { + shutdown_started: false, + }; + let manager = Self { + shutdown_tx, + _shutdown_rcv: shutdown_rcv, + handles: vec![], + }; + (manager, shutdown_handle) + } + + pub async fn stop(self) { + info!("Starting Ephemera shutdown"); + self.shutdown_tx.send(()).unwrap(); + info!("Waiting for tasks to finish"); + for (i, handle) in self + .handles + .into_iter() + .enumerate() + .map(|(i, h)| (i + 1, h)) + { + match handle.await.unwrap() { + Ok(_) => info!("Task {i} finished successfully"), + Err(e) => info!("Task {i} finished with error: {e}",), + } + } + info!("All tasks finished"); + } + + pub(crate) fn subscribe(&self) -> Shutdown { + let shutdown = self.shutdown_tx.subscribe(); + Shutdown { + shutdown_signal_rcv: shutdown, + } + } + + pub(crate) fn add_handle(&mut self, handle: JoinHandle>) { + self.handles.push(handle); + } +} diff --git a/ephemera/src/lib.rs b/ephemera/src/lib.rs new file mode 100644 index 0000000000..db506a96b3 --- /dev/null +++ b/ephemera/src/lib.rs @@ -0,0 +1,118 @@ +//! # Ephemera Node + +//! An Ephemera node does reliable broadcast of inbound messages to all other Ephemera nodes in the cluster. +//! +//! Each node has a unique ID, and each message is signed by the node that first received it. +//! Messages are then re-broadcast and re-signed by all other nodes in the cluster. +//! +//! At the end of the process, each message is signed by every node in the cluster, and each node has also +//! signed all messages that were broadcast by other nodes. This means that nodes are unable to repudiate messages +//! once they are seen and signed, so there is a strong guarantee of message integrity within the cluster. +//! +//! # Why would I want this? +//! +//! Let's say you have blockchain system that needs to ship large amounts of information around, but the information +//! is relatively short-lived. You could use a blockchain to store the information, but that would be expensive, +//! slow, and wasteful. Instead, you could use Ephemera to broadcast the information to all nodes in the cluster, +//! and then store only a cryptographic commitment in the blockchain's data store. +//! +//! Ephemera nodes then keep messages around for inspection in a data availability layer (accessible over HTTP) +//! so that interested parties can verify correctness. Ephemeral information can then be automatically discarded +//! once it's no longer useful. +//! +//! This gives very similar guarantees to a blockchain, but without incurring the permanent storage costs. +//! +//! Note that it *requires* a blockchain to be present. + +//'Denying' everything and allowing exceptions seems better than other way around. +#![deny(clippy::pedantic)] + +// PUBLIC MODULES + +pub use crate::core::builder::{ + EphemeraStarterInit, EphemeraStarterWithApplication, EphemeraStarterWithProvider, +}; +pub use crate::core::ephemera::Ephemera; +pub use crate::core::shutdown::Handle as ShutdownHandle; + +/// Ephemera API. Public interface and types. +pub mod ephemera_api { + pub use crate::api::{ + application::{ + Application, CheckBlockResult, Dummy, Error as ApplicationError, RemoveMessages, + Result as ApplicationResult, + }, + http::client::{Client, Error as HttpClientError, Result as HttpClientResult}, + types::{ + ApiBlock, ApiBlockBroadcastInfo, ApiBroadcastInfo, ApiCertificate, ApiDhtQueryRequest, + ApiDhtQueryResponse, ApiDhtStoreRequest, ApiEphemeraConfig, ApiEphemeraMessage, + ApiError, ApiHealth, ApiVerifyMessageInBlock, RawApiEphemeraMessage, + }, + CommandExecutor, + }; +} + +/// Peer identification +#[allow(clippy::module_name_repetitions)] +pub mod peer { + pub use super::network::{PeerId, PeerIdError, ToPeerId}; +} + +/// Ephemera membership. How to find other nodes in the cluster. +pub mod membership { + pub use super::network::members::{ + ConfigMembersProvider, DummyMembersProvider, PeerInfo, PeerSetting, ProviderError, Result, + }; +} + +/// Ephemera keypair and public key +pub mod crypto { + pub use super::utilities::crypto::{ + EphemeraKeypair, EphemeraPublicKey, KeyPairError, Keypair, PublicKey, + }; +} + +/// Ephemera codec to encode and decode messages +pub mod codec { + pub use super::utilities::codec::{Decode, Encode}; +} + +/// Ephemera node configuration +pub mod configuration { + pub use super::config::Configuration; +} + +/// Ephemera CLI. Helpers for creating configuration, running node, etc. +pub mod cli; + +/// Utilities to set up logging. +pub mod logging; + +// PRIVATE MODULES + +/// External interface for Ephemera +mod api; + +/// Block creation code +mod block; + +/// Ephemera reliable broadcast +mod broadcast; + +/// Ephemera configuration +mod config; + +/// Ephemera core. Ephemera builder and instance. +mod core; + +/// Ephemera networking with peers +mod network; + +/// Ephemera storage. Block storage and certificate storage. +mod storage; + +/// Ephemera utilities. Crypto, codec, etc. +mod utilities; + +/// Ephemera websocket. Websocket server where external clients can subscribe. +mod websocket; diff --git a/ephemera/src/logging/mod.rs b/ephemera/src/logging/mod.rs new file mode 100644 index 0000000000..79968347b0 --- /dev/null +++ b/ephemera/src/logging/mod.rs @@ -0,0 +1,19 @@ +pub fn init() { + if let Ok(directives) = ::std::env::var("RUST_LOG") { + println!("Logging enabled with directives: {directives}",); + pretty_env_logger::formatted_timed_builder() + .parse_filters(&directives) + .format_timestamp_millis() + .init(); + } else { + println!("Logging disabled"); + } +} + +pub fn init_with_directives(directives: &str) { + println!("Logging enabled with directives: {directives}",); + pretty_env_logger::formatted_timed_builder() + .parse_filters(directives) + .format_timestamp_millis() + .init(); +} diff --git a/ephemera/src/network/libp2p/behaviours/membership/behaviour.rs b/ephemera/src/network/libp2p/behaviours/membership/behaviour.rs new file mode 100644 index 0000000000..65c3a36dc8 --- /dev/null +++ b/ephemera/src/network/libp2p/behaviours/membership/behaviour.rs @@ -0,0 +1,556 @@ +//! # Membership behaviour. +//! +//! Ephemera `reliable broadcast` needs to know the list of peers who participate in the protocol. +//! Also it's not enough to have just the list but to make sure that they are actually online. +//! When the list of available peers changes, `reliable broadcast` needs to be notified so that it can adjust accordingly. +//! +//! This behaviour is responsible for keeping membership up to date. +//! +//! `MembersProviderFuture`: `Future>> + Send + 'static`. +//! +//! User provides a [`MembersProviderFuture`] implementation to the [Behaviour] which is responsible for fetching the list of peers. +//! +//! [Behaviour] accepts only peers that are actually online. +//! +//! When peers become available or unavailable, [Behaviour] adjusts the list of connected peers accordingly and notifies `reliable broadcast` +//! about the membership change. +//! +//! It is configurable what `threshold` of peers(from the total list provided by [`MembersProviderFuture`]) should be available at any given time. +//! Or if just to use all peers who are online. See [`MembershipKind`] for more details. +//! +//! Ideally [`MembersProviderFuture`] can depend on a resource that gives reliable results. Some kind of registry which itself keeps track of actually online nodes. +//! As Ephemera uses only peers provided by [`MembersProviderFuture`], it depends on its accuracy. +//! At the same time it tries to be flexible and robust to handle less reliable [`MembersProviderFuture`] implementations. + +// When peer gets disconnected, we try to dial it and if that fails, we update group. +// (it may connect us meanwhile). +// Although we can retry to connect to disconnected peers, it's simpler if we just assume that when +// they come online again, they will connect us. + +//So the main goal is to remove offline peers from the group so that rb can make progress. + +//a)when peer disconnects, we try to dial it and if that fails, we update group. +//b)when peer connects, we will update the group. + +use std::future::Future; +use std::time::Duration; +use std::{ + collections::HashMap, + collections::HashSet, + fmt::Debug, + task::{Context, Poll}, +}; + +use futures_util::FutureExt; +use libp2p::core::Endpoint; +use libp2p::swarm::{ConnectionDenied, NotifyHandler, THandler}; +use libp2p::{ + swarm::ToSwarm, + swarm::{ + behaviour::ConnectionEstablished, + dial_opts::{DialOpts, PeerCondition}, + ConnectionClosed, ConnectionId, DialFailure, FromSwarm, NetworkBehaviour, PollParameters, + THandlerInEvent, THandlerOutEvent, + }, + Multiaddr, +}; +use libp2p_identity::PeerId; +use log::{debug, error, trace, warn}; +use tokio::time; +use tokio::time::{Instant, Interval}; + +use crate::network::libp2p::behaviours::membership::handler::ToHandler; +use crate::network::libp2p::behaviours::membership::{Membership, MEMBERSHIP_SYNC_INTERVAL_SEC}; +use crate::network::Peer; +use crate::{ + membership, + network::{ + libp2p::behaviours::{ + membership::connections::ConnectedPeers, + membership::protocol::ProtocolMessage, + membership::{handler::Handler, MAX_DIAL_ATTEMPT_ROUNDS}, + membership::{MembershipKind, Memberships}, + }, + members::PeerInfo, + }, +}; + +/// [`MembersProviderFuture`] state when we are trying to connect to new peers. +/// +/// We try to connect few times before giving up. Generally speaking an another peer is either online or offline +/// at any given time. But it has been helpful for testing when whole cluster comes up around the same time. +#[derive(Debug, Default)] +struct PendingPeersUpdate { + /// Peers that we are haven't tried to connect to yet. + waiting_to_dial: HashSet, + /// Number of dial attempts per round. + dial_attempts: usize, + /// How long we wait between dial attempts. + interval_between_dial_attempts: Option, +} + +#[derive(Debug, Default)] +struct SyncPeers { + pending_peers: Vec, +} + +impl SyncPeers { + fn new(pending_peers: Vec) -> Self { + Self { pending_peers } + } +} + +/// Behaviour states. +enum State { + /// Waiting for new peers from the members provider trait. + WaitingPeers, + /// Trying to connect to new peers. + WaitingDial(PendingPeersUpdate), + /// We have finished trying to connect to new peers and going to report it. + NotifyPeersUpdated, + /// Notify other members that we have updated members. + SyncPeers(SyncPeers), +} + +/// Events that can be emitted by the `Behaviour`. +pub(crate) enum Event { + /// We have received new peers from the members provider trait. + /// We are going to try to connect to them. + PeerUpdatePending, + /// We have finished trying to connect to new peers and going to report it. + PeersUpdated(HashSet), + /// MembersProviderFuture reported us new peers and this set doesn't contain our local peer. + LocalRemoved(HashSet), + /// MembersProviderFuture reported us new peers and we failed to connect to enough of them. + NotEnoughPeers(HashSet), +} + +pub(crate) struct Behaviour

    +where + P: Future>> + Send + 'static, +{ + /// All peers that are part of the current group. + memberships: Memberships, + /// Local peer id. + local_peer_id: PeerId, + /// Future that provides new peers. + members_provider: P, + /// Interval between requesting new peers from the members provider. + members_provider_interval: Option, + /// Delay between dial attempts. + members_provider_delay: Duration, + /// Current behaviour state. + state: State, + /// Current state of all incoming and outgoing connections. + all_connections: ConnectedPeers, + /// Membership kind. + membership_kind: MembershipKind, + /// Last time we broadcast SYNC + last_sync_time: Instant, + /// Minimum time between members provider updates. + minimum_time_between_sync: Duration, +} + +impl

    Behaviour

    +where + P: Future>> + Send + Unpin + 'static, +{ + pub(crate) fn new( + members_provider: P, + members_provider_delay: Duration, + local_peer_id: PeerId, + membership_kind: MembershipKind, + ) -> Self { + let initial_delay = Instant::now() + Duration::from_secs(5); + let delay = tokio::time::interval_at(initial_delay, members_provider_delay); + Behaviour { + memberships: Memberships::new(), + local_peer_id, + members_provider, + members_provider_interval: Some(delay), + members_provider_delay, + state: State::WaitingPeers, + all_connections: ConnectedPeers::default(), + membership_kind, + last_sync_time: Instant::now(), + minimum_time_between_sync: Duration::from_secs(MEMBERSHIP_SYNC_INTERVAL_SEC), + } + } + + /// Returns the list of peers that are part of current group. + pub(crate) fn active_peer_ids(&mut self) -> &HashSet { + self.memberships.current().connected_peers() + } + + pub(crate) fn active_peer_ids_with_local(&mut self) -> HashSet { + self.memberships.current().connected_peer_ids_with_local() + } + + fn waiting_peers(&mut self, cx: &mut Context) -> Poll> { + if let Some(mut tick) = self.members_provider_interval.take() { + if !tick.poll_tick(cx).is_ready() { + self.members_provider_interval = Some(tick); + return Poll::Pending; + } + } + let peers = match self.members_provider.poll_unpin(cx) { + Poll::Ready(peers) => { + let wait_time = Instant::now() + self.members_provider_delay; + self.members_provider_interval = + time::interval_at(wait_time, self.members_provider_delay).into(); + self.last_sync_time = Instant::now(); + peers + } + Poll::Pending => { + return Poll::Pending; + } + }; + + match peers { + Ok(peers) => { + if peers.is_empty() { + //Not sure what to do here. Tempted to think that if this happens + //we should ignore it and assume that this is probably a bug in the membership service. + + warn!("Received empty peers from provider. To try again before preconfigured interval, please restart the node."); + return Poll::Ready(ToSwarm::GenerateEvent(Event::NotEnoughPeers( + HashSet::default(), + ))); + } + + let mut new_peers = HashMap::new(); + + for peer_info in peers { + match >::try_into(peer_info) { + Ok(peer) => { + debug!( + "Received peer: {:?}, {:?}", + peer.peer_id.inner(), + peer.cosmos_address + ); + new_peers.insert(*peer.peer_id.inner(), peer); + } + Err(err) => { + error!("Error while converting peer info to peer: {}", err); + } + } + } + + //If we are not part of the new membership, notify immediately + if !new_peers.contains_key(&self.local_peer_id) { + debug!( + "Local peer {:?} is not part of the new membership. Notifying immediately.", + self.local_peer_id + ); + let pending_membership = Membership::new(new_peers.clone()); + self.memberships.set_pending(pending_membership); + self.state = State::NotifyPeersUpdated; + return Poll::Pending; + } + + let mut pending_membership = + Membership::new_with_local(new_peers.clone(), self.local_peer_id); + let mut pending_update = PendingPeersUpdate::default(); + + for peer_id in new_peers.keys() { + if self.all_connections.is_peer_connected(peer_id) { + pending_membership.peer_connected(*peer_id); + } else { + pending_update.waiting_to_dial.insert(*peer_id); + } + } + + self.memberships.set_pending(pending_membership); + + //It seems that all peers from updated membership set are already connected + if pending_update.waiting_to_dial.is_empty() { + self.state = State::NotifyPeersUpdated; + Poll::Pending + } else { + self.state = State::WaitingDial(pending_update); + + //Just let the rest of the system to know that we are in the middle of updating membership + Poll::Ready(ToSwarm::GenerateEvent(Event::PeerUpdatePending)) + } + } + Err(err) => { + error!("Error while getting peers from provider: {:?}", err); + Poll::Ready(ToSwarm::GenerateEvent(Event::NotEnoughPeers( + HashSet::default(), + ))) + } + } + } + + fn waiting_dial( + &mut self, + cx: &mut Context<'_>, + ) -> Poll>> { + if let State::WaitingDial(PendingPeersUpdate { + waiting_to_dial, + dial_attempts, + interval_between_dial_attempts, + }) = &mut self.state + { + //Refresh the list of connected peers + for peer_id in self.all_connections.all_connected_peers_ref() { + waiting_to_dial.remove(peer_id); + } + + //FIXME + waiting_to_dial.remove(&self.local_peer_id); + + let pending_membership = self + .memberships + .pending() + .expect("Pending membership should be set"); + + //With each 'poll' we can tell Swarm to dial one peer + let next_waiting = waiting_to_dial.iter().next().copied(); + + if let Some(peer_id) = next_waiting { + waiting_to_dial.remove(&peer_id); + + let address = pending_membership + .peer_address(&peer_id) + .expect("Peer should exist"); + + trace!("Dialing peer: {:?} {:?}", peer_id, address); + + let opts = DialOpts::peer_id(peer_id) + .condition(PeerCondition::NotDialing) + .addresses(vec![address.clone()]) + .build(); + + Poll::Ready(ToSwarm::Dial { opts }) + } else { + let all_peers = pending_membership.all_peer_ids(); + let connected_peers = pending_membership.connected_peers(); + + //Exclude local peer + let all_connected = connected_peers.len() == all_peers.len() - 1; + + if all_connected || *dial_attempts >= MAX_DIAL_ATTEMPT_ROUNDS { + interval_between_dial_attempts.take(); + self.state = State::NotifyPeersUpdated; + return Poll::Pending; + } + //Try again few times before notifying the rest of the system about membership update. + //Dialing attempt + if let Some(interval) = interval_between_dial_attempts { + if interval.poll_tick(cx) == Poll::Pending { + return Poll::Pending; + } + *dial_attempts += 1; + trace!("Next attempt({dial_attempts:?}) to dial failed peers"); + } else { + let start_at = Instant::now() + Duration::from_secs(5); + *interval_between_dial_attempts = + Some(time::interval_at(start_at, Duration::from_secs(10))); + } + if *dial_attempts > 0 { + waiting_to_dial.extend(all_peers.difference(connected_peers).copied()); + } + + Poll::Pending + } + } else { + unreachable!() + } + } + + fn notify_peers_updated(&mut self) -> Poll> { + if let Some(membership) = self.memberships.remove_pending() { + self.memberships.update(membership); + } + + let membership = self.memberships.current(); + let membership_connected_peers = membership.connected_peer_ids(); + + let event = if membership.includes_local() { + if self.membership_kind.accept(membership) { + debug!("Membership accepted by kind: {:?}", self.membership_kind); + Event::PeersUpdated(membership_connected_peers) + } else { + debug!("Membership rejected by kind: {:?}", self.membership_kind); + Event::NotEnoughPeers(membership_connected_peers) + } + } else { + debug!("Membership does not include local peer"); + Event::LocalRemoved(membership_connected_peers) + }; + + //TODO: this list should also include "old" peers(peers who aren't part of new membership). + let connected_peers = membership + .connected_peer_ids() + .into_iter() + .collect::>(); + self.state = State::SyncPeers(SyncPeers::new(connected_peers)); + Poll::Ready(ToSwarm::GenerateEvent(event)) + } + + fn sync_peers(&mut self) -> Poll> { + if let State::SyncPeers(SyncPeers { pending_peers }) = &mut self.state { + match pending_peers.pop() { + None => { + self.state = State::WaitingPeers; + Poll::Pending + } + Some(peer_id) => { + debug!("Notifying {peer_id:?} about membership update",); + Poll::Ready(ToSwarm::NotifyHandler { + peer_id, + handler: NotifyHandler::Any, + event: ToHandler::Message(ProtocolMessage::Sync), + }) + } + } + } else { + unreachable!("State should be SyncPeers") + } + } +} + +impl

    NetworkBehaviour for Behaviour

    +where + P: Future>> + Send + Unpin + 'static, +{ + type ConnectionHandler = Handler; + type OutEvent = Event; + + fn handle_pending_inbound_connection( + &mut self, + _connection_id: ConnectionId, + _local_addr: &Multiaddr, + _remote_addr: &Multiaddr, + ) -> Result<(), ConnectionDenied> { + //TODO: we can refuse connections from peers that are not part of the current membership. + Ok(()) + } + + fn handle_established_inbound_connection( + &mut self, + _connection_id: ConnectionId, + peer: PeerId, + _local_addr: &Multiaddr, + _remote_addr: &Multiaddr, + ) -> Result, ConnectionDenied> { + trace!("Established inbound connection with peer: {:?}", peer); + Ok(Handler::new()) + } + + fn handle_pending_outbound_connection( + &mut self, + _connection_id: ConnectionId, + maybe_peer: Option, + _addresses: &[Multiaddr], + _effective_role: Endpoint, + ) -> Result, ConnectionDenied> { + //FIXME: deprecated + #[allow(deprecated)] + match maybe_peer { + Some(peer_id) => Ok(self.addresses_of_peer(&peer_id)), + None => Ok(vec![]), + } + } + + fn handle_established_outbound_connection( + &mut self, + _connection_id: ConnectionId, + peer: PeerId, + addr: &Multiaddr, + _role_override: Endpoint, + ) -> Result, ConnectionDenied> { + trace!( + "Established outbound connection with peer: {:?} {:?}", + peer, + addr + ); + Ok(Handler::new()) + } + + ///Membership behaviour is responsible for providing addresses to another Swarm behaviours. + fn addresses_of_peer(&mut self, peer_id: &PeerId) -> Vec { + self.memberships + .current() + .peer_address(peer_id) + .cloned() + .map_or(vec![], |addr| vec![addr]) + } + + fn on_swarm_event(&mut self, event: FromSwarm) { + match event { + FromSwarm::ConnectionEstablished(ConnectionEstablished { + peer_id, + connection_id: _, + endpoint, + failed_addresses: _, + other_established: _, + }) => { + self.all_connections + .insert(peer_id, endpoint.clone().into()); + if let Some(pending) = self.memberships.pending_mut() { + pending.peer_connected(peer_id); + } + trace!("{:?}", self.all_connections); + } + + FromSwarm::ConnectionClosed(ConnectionClosed { + peer_id, + connection_id: _, + endpoint, + handler: _h, + remaining_established: _, + }) => { + self.all_connections + .remove(&peer_id, &endpoint.clone().into()); + if let Some(pending) = self.memberships.pending_mut() { + pending.peer_disconnected(&peer_id); + } + debug!("{:?}", self.all_connections); + } + FromSwarm::DialFailure(DialFailure { + peer_id: Some(peer_id), + error, + connection_id: _, + }) => { + trace!("Dial failure: {:?} {:?}", peer_id, error); + } + _ => {} + } + } + + fn on_connection_handler_event( + &mut self, + peer_id: PeerId, + _connection_id: ConnectionId, + event: THandlerOutEvent, + ) { + trace!( + "Received event from connection handler: {:?} from peer: {:?}", + event, + peer_id + ); + + //TODO: we may need to check who sent the update: probably we should accept only updates from members who we already know + if let State::WaitingPeers = self.state { + if self.last_sync_time + self.minimum_time_between_sync < Instant::now() { + self.members_provider_interval = None; + debug!("Received sync notification from peer {peer_id:?}, requesting membership update"); + } + } + } + + fn poll( + &mut self, + cx: &mut Context<'_>, + _params: &mut impl PollParameters, + ) -> Poll>> { + match &mut self.state { + State::WaitingPeers => self.waiting_peers(cx), + State::WaitingDial(_) => self.waiting_dial(cx), + State::NotifyPeersUpdated => self.notify_peers_updated(), + State::SyncPeers(_) => self.sync_peers(), + } + } +} diff --git a/ephemera/src/network/libp2p/behaviours/membership/connections.rs b/ephemera/src/network/libp2p/behaviours/membership/connections.rs new file mode 100644 index 0000000000..1e5bab908d --- /dev/null +++ b/ephemera/src/network/libp2p/behaviours/membership/connections.rs @@ -0,0 +1,88 @@ +use std::collections::{HashMap, HashSet}; +use std::fmt::Debug; + +use libp2p::core::ConnectedPoint; +use libp2p::Multiaddr; +use serde::Serialize; + +#[derive(PartialEq, Eq, Debug, Clone, Hash, Serialize)] +pub(crate) enum Endpoint { + Dialer { + address: Multiaddr, + }, + Listener { + local_address: Multiaddr, + remote_address: Multiaddr, + }, +} + +impl From for Endpoint { + fn from(connected_point: ConnectedPoint) -> Self { + match connected_point { + ConnectedPoint::Dialer { address, .. } => Endpoint::Dialer { address }, + ConnectedPoint::Listener { + local_addr, + send_back_addr, + } => Endpoint::Listener { + local_address: local_addr, + remote_address: send_back_addr, + }, + } + } +} + +#[derive(Debug, Default, Serialize)] +pub(crate) struct Connections { + dialer: HashSet, + listener: HashSet, +} + +impl Connections { + fn insert(&mut self, connected_point: Endpoint) { + match connected_point { + Endpoint::Dialer { .. } => { + self.dialer.insert(connected_point); + } + Endpoint::Listener { .. } => { + self.listener.insert(connected_point); + } + } + } + + fn remove(&mut self, connected_point: &Endpoint) { + match connected_point { + Endpoint::Dialer { .. } => { + self.dialer.remove(connected_point); + } + Endpoint::Listener { .. } => { + self.listener.remove(connected_point); + } + } + } +} + +#[derive(Debug, Default, Serialize)] +pub(crate) struct ConnectedPeers { + connections: HashMap, +} + +impl ConnectedPeers { + pub(crate) fn is_peer_connected(&self, peer_id: &libp2p_identity::PeerId) -> bool { + self.connections.contains_key(peer_id) + } + + pub(crate) fn all_connected_peers_ref(&self) -> Vec<&libp2p_identity::PeerId> { + self.connections.keys().collect() + } + + pub(crate) fn insert(&mut self, peer_id: libp2p_identity::PeerId, connected_point: Endpoint) { + let connections = self.connections.entry(peer_id).or_default(); + connections.insert(connected_point); + } + + pub(crate) fn remove(&mut self, peer_id: &libp2p_identity::PeerId, connected_point: &Endpoint) { + if let Some(connections) = self.connections.get_mut(peer_id) { + connections.remove(connected_point); + } + } +} diff --git a/ephemera/src/network/libp2p/behaviours/membership/handler.rs b/ephemera/src/network/libp2p/behaviours/membership/handler.rs new file mode 100644 index 0000000000..9735d5cea9 --- /dev/null +++ b/ephemera/src/network/libp2p/behaviours/membership/handler.rs @@ -0,0 +1,340 @@ +//! Because each Ephemera instance requests peers at arbitrary time, a node needs to notify other +//! peers when it has just requested an update. That helps to keep the whole cluster in sync and avoid +//! nodes' membership diverging. +//! +//! Overall this synchronizes all nodes' view of the membership. +//! +//! Current approach is a bit 'burst'. It makes all nodes to request membership info at the same time. +//! +//! TODO +//! Because we actually can verify peers' membership, it would be possible that one peer(or subset of peers) requests the +//! peers from a rendezvous point and then sends the list to the other peers. Or possibly only the difference. +use std::pin::Pin; +use std::task::{Context, Poll}; + +use asynchronous_codec::Framed; +use futures::Sink; +use futures_util::StreamExt; +use libp2p::{ + swarm::handler::{ + DialUpgradeError, FullyNegotiatedInbound, FullyNegotiatedOutbound, ListenUpgradeError, + }, + swarm::NegotiatedSubstream, + swarm::{ + handler::ConnectionEvent, ConnectionHandler, ConnectionHandlerEvent, KeepAlive, + SubstreamProtocol, + }, +}; +use log::{debug, error}; +use thiserror::Error; + +use crate::network::libp2p::behaviours::membership::protocol::{ + MembershipCodec, Protocol, ProtocolMessage, +}; + +#[derive(Error, Debug)] +pub(crate) enum Error { + #[error("HandlerError: {0}")] + Handler(#[from] anyhow::Error), +} + +//Because we keep long lived connections, we need to restrict number of substream attempts. +//Here we don't need more than 1 because it's a 'quiet' protocol. +const MAX_SUBSTREAM_ATTEMPTS: usize = 1; + +enum InboundSubstreamState { + WaitingInput(Framed), + Closing(Framed), +} + +enum OutboundSubstreamState { + WaitingOutput(Framed), + PendingSend( + Framed, + ProtocolMessage, + ), + PendingFlush(Framed), +} + +pub(crate) struct Handler { + outbound_substream: Option, + inbound_substream: Option, + send_queue: Vec, + outbound_substream_establishing: bool, + outbound_substream_attempts: usize, + inbound_substream_attempts: usize, +} + +impl Handler { + pub(crate) fn new() -> Self { + Self { + outbound_substream: None, + inbound_substream: None, + send_queue: vec![], + outbound_substream_establishing: false, + outbound_substream_attempts: 0, + inbound_substream_attempts: 0, + } + } + + //Process inbound stream messages + //WAITING_INPUT + // - if message received, send it to behaviour + // - if receive error or None, close substream + // + //CLOSING + // - Wait buffer to be flushed + // - Close substream + fn process_inbound_stream( + &mut self, + cx: &mut Context, + ) -> Option>> { + loop { + match std::mem::take(&mut self.inbound_substream) { + // inbound idle state + Some(InboundSubstreamState::WaitingInput(mut substream)) => { + match substream.poll_next_unpin(cx) { + Poll::Ready(Some(Ok(message))) => { + self.inbound_substream = + Some(InboundSubstreamState::WaitingInput(substream)); + + let from_handler = FromHandler::Message(message); + + return Poll::Ready(ConnectionHandlerEvent::Custom(from_handler)) + .into(); + } + Poll::Ready(Some(Err(err))) => { + error!("Failed to read from substream: {err}",); + self.inbound_substream = + Some(InboundSubstreamState::Closing(substream)); + } + Poll::Ready(None) => { + debug!("Inbound stream closed by remote"); + self.inbound_substream = + Some(InboundSubstreamState::Closing(substream)); + } + Poll::Pending => { + self.inbound_substream = + Some(InboundSubstreamState::WaitingInput(substream)); + break; + } + } + } + Some(InboundSubstreamState::Closing(mut substream)) => { + match Sink::poll_close(Pin::new(&mut substream), cx) { + Poll::Ready(res) => { + if let Err(e) = res { + error!("Inbound substream error while closing: {e}"); + } + self.inbound_substream = None; + break; + } + Poll::Pending => { + self.inbound_substream = + Some(InboundSubstreamState::Closing(substream)); + break; + } + } + } + None => { + self.inbound_substream = None; + break; + } + } + } + None + } + + //Process outbound stream messages + //WAITING_OUTPUT + // - if send queue is not empty, go to PENDING_SEND + // + //PENDING_SEND + // - send message to substream + // - if send error, mark substream as Closing + // + //PENDING_FLUSH + // - flush substream + // - if flush error, mark substream as Closing + fn process_outbound_stream(&mut self, cx: &mut Context) { + loop { + match std::mem::take(&mut self.outbound_substream) { + // outbound idle state + Some(OutboundSubstreamState::WaitingOutput(substream)) => { + if let Some(message) = self.send_queue.pop() { + self.outbound_substream = + Some(OutboundSubstreamState::PendingSend(substream, message)); + continue; + } + + self.outbound_substream = + Some(OutboundSubstreamState::WaitingOutput(substream)); + break; + } + Some(OutboundSubstreamState::PendingSend(mut substream, message)) => { + match Sink::poll_ready(Pin::new(&mut substream), cx) { + Poll::Ready(Ok(())) => { + match Sink::start_send(Pin::new(&mut substream), message) { + Ok(()) => { + self.outbound_substream = + Some(OutboundSubstreamState::PendingFlush(substream)); + } + Err(e) => { + debug!("Failed to send message on outbound stream: {e}"); + self.outbound_substream = None; + break; + } + } + } + Poll::Ready(Err(e)) => { + debug!("Failed to send message on outbound stream: {e}"); + self.outbound_substream = None; + break; + } + Poll::Pending => { + self.outbound_substream = + Some(OutboundSubstreamState::PendingSend(substream, message)); + break; + } + } + } + Some(OutboundSubstreamState::PendingFlush(mut substream)) => { + match Sink::poll_flush(Pin::new(&mut substream), cx) { + Poll::Ready(Ok(())) => { + self.outbound_substream = + Some(OutboundSubstreamState::WaitingOutput(substream)); + } + Poll::Ready(Err(e)) => { + debug!("Failed to flush outbound stream: {e}"); + self.outbound_substream = None; + break; + } + Poll::Pending => { + self.outbound_substream = + Some(OutboundSubstreamState::PendingFlush(substream)); + break; + } + } + } + None => { + self.outbound_substream = None; + break; + } + } + } + } +} + +#[derive(Debug)] +pub(crate) enum FromHandler { + Message(ProtocolMessage), +} + +#[derive(Debug)] +pub(crate) enum ToHandler { + Message(ProtocolMessage), +} + +impl ConnectionHandler for Handler { + type InEvent = ToHandler; + type OutEvent = FromHandler; + type Error = Error; + type InboundProtocol = Protocol; + type OutboundProtocol = Protocol; + type InboundOpenInfo = (); + type OutboundOpenInfo = (); + + fn listen_protocol(&self) -> SubstreamProtocol { + SubstreamProtocol::new(Protocol, ()) + } + + fn connection_keep_alive(&self) -> KeepAlive { + //we could add idle timeout here + KeepAlive::Yes + } + + fn poll( + &mut self, + cx: &mut Context<'_>, + ) -> Poll< + ConnectionHandlerEvent< + Self::OutboundProtocol, + Self::OutboundOpenInfo, + Self::OutEvent, + Self::Error, + >, + > { + // poll STATE_MACHINE + //- Request outbound substream if neccessary + //- poll inbound substream + //- poll outbound substream + + //Establish new connection when behaviour wants to send a message and we don't have an outbound substream yet + if !self.send_queue.is_empty() + && self.outbound_substream.is_none() + && !self.outbound_substream_establishing + { + self.outbound_substream_establishing = true; + return Poll::Ready(ConnectionHandlerEvent::OutboundSubstreamRequest { + protocol: SubstreamProtocol::new(Protocol, ()), + }); + } + + if let Some(res) = self.process_inbound_stream(cx) { + return res; + } + + self.process_outbound_stream(cx); + + Poll::Pending + } + + fn on_behaviour_event(&mut self, event: Self::InEvent) { + match event { + ToHandler::Message(message) => { + self.send_queue.push(message); + } + } + } + + fn on_connection_event( + &mut self, + event: ConnectionEvent< + Self::InboundProtocol, + Self::OutboundProtocol, + Self::InboundOpenInfo, + Self::OutboundOpenInfo, + >, + ) { + match event { + ConnectionEvent::FullyNegotiatedInbound(FullyNegotiatedInbound { + protocol: stream, + info: _, + }) => { + if self.inbound_substream_attempts > MAX_SUBSTREAM_ATTEMPTS { + log::warn!("Too many inbound substream attempts, refusing stream"); + return; + } + self.inbound_substream_attempts += 1; + self.inbound_substream = Some(InboundSubstreamState::WaitingInput(stream)); + } + ConnectionEvent::FullyNegotiatedOutbound(FullyNegotiatedOutbound { + protocol, + info: _, + }) => { + if self.outbound_substream_attempts > MAX_SUBSTREAM_ATTEMPTS { + log::warn!("Too many outbound substream attempts, refusing stream"); + return; + } + self.outbound_substream = Some(OutboundSubstreamState::WaitingOutput(protocol)); + } + ConnectionEvent::DialUpgradeError(DialUpgradeError { info, error }) => { + error!("DialUpgradeError: info: {:?}, error: {:?}", info, error); + } + ConnectionEvent::ListenUpgradeError(ListenUpgradeError { info, error }) => { + error!("ListenUpgradeError: info: {:?}, error: {:?}", info, error); + } + ConnectionEvent::AddressChange(_) => {} + } + } +} diff --git a/ephemera/src/network/libp2p/behaviours/membership/mod.rs b/ephemera/src/network/libp2p/behaviours/membership/mod.rs new file mode 100644 index 0000000000..6a4f6e6178 --- /dev/null +++ b/ephemera/src/network/libp2p/behaviours/membership/mod.rs @@ -0,0 +1,190 @@ +//! In Ephemera, membership of reliable broadcast protocol is decided by membership provider. +//! Only peers who are returned by [`crate::membership::MembersProviderFut`] are allowed to participate. + +use std::collections::{HashMap, HashSet}; +use std::num::NonZeroUsize; + +use libp2p_identity::PeerId; +use lru::LruCache; + +use crate::network::Peer; + +pub(crate) mod behaviour; +mod connections; +mod handler; +mod protocol; + +const MAX_DIAL_ATTEMPT_ROUNDS: usize = 6; + +/// Minimum percentage of available nodes to consider the network healthy. +//TODO: make this configurable +const MEMBERSHIP_MINIMUM_AVAILABLE_NODES_RATIO: f64 = 0.8; + +/// Minimum time between syncs of membership. +const MEMBERSHIP_SYNC_INTERVAL_SEC: u64 = 60; + +/// Maximum percentage of nodes that can change in a single membership update. +/// In general it should be considered a security risk if it has changed too much. +/// //TODO: make this configurable +const _MEMBERSHIP_MAXIMUM_ALLOWED_CHANGE_RATIO: f64 = 0.2; + +/// Membership provider returns list of peers. But it is up to the Ephemera user to decide +/// how reliable the list is. For example, it can contain peers who are offline. + +/// This enum defines how the actual membership is decided. +#[derive(Debug)] +pub(crate) enum MembershipKind { + /// Mandatory minimum membership size is defined by threshold of all peers returned by membership provider. + Threshold(f64), + /// Mandatory minimum membership size is all peers who are online. + AnyOnline, + /// Mandatory minimum membership size is all peers returned by membership provider. + AllOnline, +} + +impl MembershipKind { + #[allow( + clippy::cast_precision_loss, + clippy::cast_sign_loss, + clippy::cast_possible_truncation + )] + pub(crate) fn accept(&self, membership: &Membership) -> bool { + let total_number_of_peers = membership.all_members.len(); + let connected_peers = membership.connected_peers_ids.len(); + match self { + MembershipKind::Threshold(threshold) => { + let minimum_available_nodes = (total_number_of_peers as f64 * threshold) as usize; + connected_peers >= minimum_available_nodes + } + MembershipKind::AnyOnline => connected_peers > 0, + MembershipKind::AllOnline => connected_peers == total_number_of_peers, + } + } +} + +pub(crate) struct Memberships { + snapshots: LruCache, + current: u64, + /// This is set when we get new peers set from [crate::membership::MembersProviderFut] + /// but haven't yet activated it. + pending_membership: Option, +} + +impl Memberships { + pub(crate) fn new() -> Self { + let mut snapshots = LruCache::new(NonZeroUsize::new(1000).unwrap()); + snapshots.put(0, Membership::new(HashMap::default())); + Self { + snapshots, + current: 0, + pending_membership: None, + } + } + + pub(crate) fn current(&mut self) -> &Membership { + //Unwrap is safe because we always have current membership + self.snapshots.get(&self.current).unwrap() + } + + pub(crate) fn update(&mut self, membership: Membership) { + self.current += 1; + self.snapshots.put(self.current, membership); + } + + pub(crate) fn set_pending(&mut self, membership: Membership) { + self.pending_membership = Some(membership); + } + + pub(crate) fn remove_pending(&mut self) -> Option { + self.pending_membership.take() + } + + pub(crate) fn pending(&self) -> Option<&Membership> { + self.pending_membership.as_ref() + } + + pub(crate) fn pending_mut(&mut self) -> Option<&mut Membership> { + self.pending_membership.as_mut() + } +} + +#[derive(Debug)] +pub(crate) struct Membership { + local_peer_id: PeerId, + all_members: HashMap, + all_peers_ids: HashSet, + connected_peers_ids: HashSet, +} + +impl Membership { + pub(crate) fn new_with_local( + all_members: HashMap, + local_peer_id: PeerId, + ) -> Self { + let all_peers_ids = all_members.keys().copied().collect(); + Self { + local_peer_id, + all_members, + all_peers_ids, + connected_peers_ids: HashSet::new(), + } + } + + pub(crate) fn new(all_members: HashMap) -> Self { + let all_peers_ids = all_members.keys().copied().collect(); + Self { + local_peer_id: PeerId::random(), + all_members, + all_peers_ids, + connected_peers_ids: HashSet::new(), + } + } + + pub(crate) fn includes_local(&self) -> bool { + self.all_members.contains_key(&self.local_peer_id) + } + + pub(crate) fn peer_connected(&mut self, peer_id: PeerId) { + self.connected_peers_ids.insert(peer_id); + } + + pub(crate) fn peer_disconnected(&mut self, peer_id: &PeerId) { + self.connected_peers_ids.remove(peer_id); + } + + pub(crate) fn all_peer_ids(&self) -> &HashSet { + &self.all_peers_ids + } + + pub(crate) fn connected_peer_ids(&self) -> HashSet { + self.connected_peers_ids.clone() + } + + pub(crate) fn connected_peer_ids_with_local(&self) -> HashSet { + let mut active_peers = self.connected_peers_ids.clone(); + active_peers.insert(self.local_peer_id); + active_peers + } + + pub(crate) fn connected_peers(&self) -> &HashSet { + &self.connected_peers_ids + } + + pub(crate) fn peer_address(&self, peer_id: &PeerId) -> Option<&libp2p::Multiaddr> { + self.all_members + .get(peer_id) + .map(|peer| peer.address.inner()) + } +} + +impl From for MembershipKind { + fn from(kind: crate::config::MembershipKind) -> Self { + match kind { + crate::config::MembershipKind::Threshold => { + MembershipKind::Threshold(MEMBERSHIP_MINIMUM_AVAILABLE_NODES_RATIO) + } + crate::config::MembershipKind::AnyOnline => MembershipKind::AnyOnline, + crate::config::MembershipKind::AllOnline => MembershipKind::AllOnline, + } + } +} diff --git a/ephemera/src/network/libp2p/behaviours/membership/protocol.rs b/ephemera/src/network/libp2p/behaviours/membership/protocol.rs new file mode 100644 index 0000000000..cb2c1d631c --- /dev/null +++ b/ephemera/src/network/libp2p/behaviours/membership/protocol.rs @@ -0,0 +1,100 @@ +use std::future::Future; +use std::iter; +use std::pin::Pin; + +use asynchronous_codec::{Decoder, Encoder, Framed}; +use bytes::BytesMut; +use futures::{AsyncRead, AsyncWrite}; +use futures_util::future; +use libp2p::core::UpgradeInfo; +use libp2p::{InboundUpgrade, OutboundUpgrade}; +use log::trace; +use serde::{Deserialize, Serialize}; + +use crate::utilities::codec::varint_bytes::{read_length_prefixed, write_length_prefixed}; + +pub const PROTOCOL_NAME: &[u8] = b"/ephemera/membership/1.0.0"; + +pub(crate) struct Protocol; + +impl UpgradeInfo for Protocol { + type Info = &'static [u8]; + type InfoIter = iter::Once; + + fn protocol_info(&self) -> Self::InfoIter { + iter::once(PROTOCOL_NAME) + } +} + +impl InboundUpgrade for Protocol +where + C: AsyncRead + AsyncWrite + Unpin + Send + 'static, +{ + type Output = Framed; + type Error = anyhow::Error; + type Future = Pin> + Send>>; + + fn upgrade_inbound(self, socket: C, _: Self::Info) -> Self::Future { + trace!( + "Inbound upgrade for protocol: {}", + String::from_utf8_lossy(PROTOCOL_NAME) + ); + Box::pin(future::ok(Framed::new(socket, MembershipCodec {}))) + } +} + +impl OutboundUpgrade for Protocol +where + C: AsyncRead + AsyncWrite + Unpin + Send + 'static, +{ + type Output = Framed; + type Error = anyhow::Error; + type Future = Pin> + Send>>; + + fn upgrade_outbound(self, socket: C, _: Self::Info) -> Self::Future { + trace!( + "Outbound upgrade for protocol: {}", + String::from_utf8_lossy(PROTOCOL_NAME) + ); + Box::pin(future::ok(Framed::new(socket, MembershipCodec {}))) + } +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub(crate) enum ProtocolMessage { + Sync, +} + +pub(crate) struct MembershipCodec {} + +impl Encoder for MembershipCodec { + type Item = ProtocolMessage; + type Error = anyhow::Error; + + fn encode(&mut self, item: Self::Item, dst: &mut BytesMut) -> Result<(), Self::Error> { + //FIXME: switch to binary + let data = serde_json::to_vec(&item).unwrap(); + write_length_prefixed(dst, data); + Ok(()) + } +} + +impl Decoder for MembershipCodec { + type Item = ProtocolMessage; + type Error = anyhow::Error; + + fn decode(&mut self, src: &mut BytesMut) -> Result, Self::Error> { + if src.is_empty() { + return Ok(None); + } + let data = read_length_prefixed(src, 1024 * 1024)?; + match data { + None => Ok(None), + Some(data) => { + //FIXME: switch to binary + let msg = serde_json::from_slice(&data)?; + Ok(msg) + } + } + } +} diff --git a/ephemera/src/network/libp2p/behaviours/mod.rs b/ephemera/src/network/libp2p/behaviours/mod.rs new file mode 100644 index 0000000000..fdd1b4bfa5 --- /dev/null +++ b/ephemera/src/network/libp2p/behaviours/mod.rs @@ -0,0 +1,183 @@ +use std::future::Future; +use std::{iter, sync::Arc, time::Duration}; + +use libp2p::{ + core::{muxing::StreamMuxerBox, transport::Boxed}, + dns, gossipsub, + gossipsub::{IdentTopic as Topic, MessageAuthenticity, ValidationMode}, + kad, noise, request_response as libp2p_request_response, + swarm::NetworkBehaviour, + tcp::{tokio::Transport as TokioTransport, Config as TokioConfig}, + yamux, PeerId as Libp2pPeerId, Transport, +}; +use log::info; + +use crate::membership::PeerInfo; +use crate::network::libp2p::behaviours::membership::MembershipKind; +use crate::{ + broadcast::RbMsg, + crypto::Keypair, + network::libp2p::behaviours::request_response::{ + RbMsgMessagesCodec, RbMsgProtocol, RbMsgResponse, + }, + peer::{PeerId, ToPeerId}, + utilities::hash::{EphemeraHasher, Hasher}, +}; + +pub(crate) mod membership; +pub(crate) mod request_response; + +#[derive(NetworkBehaviour)] +#[behaviour(out_event = "GroupBehaviourEvent")] +pub(crate) struct GroupNetworkBehaviour

    +where + P: Future>> + Send + 'static, +{ + pub(crate) members_provider: membership::behaviour::Behaviour

    , + pub(crate) gossipsub: gossipsub::Behaviour, + pub(crate) request_response: libp2p_request_response::Behaviour, + pub(crate) kademlia: kad::Kademlia, +} + +#[allow(clippy::large_enum_variant)] +pub(crate) enum GroupBehaviourEvent { + Gossipsub(gossipsub::Event), + RequestResponse(libp2p_request_response::Event), + Membership(membership::behaviour::Event), + Kademlia(kad::KademliaEvent), +} + +impl From for GroupBehaviourEvent { + fn from(event: gossipsub::Event) -> Self { + GroupBehaviourEvent::Gossipsub(event) + } +} + +impl From> for GroupBehaviourEvent { + fn from(event: libp2p_request_response::Event) -> Self { + GroupBehaviourEvent::RequestResponse(event) + } +} + +impl From for GroupBehaviourEvent { + fn from(event: membership::behaviour::Event) -> Self { + GroupBehaviourEvent::Membership(event) + } +} + +impl From for GroupBehaviourEvent { + fn from(event: kad::KademliaEvent) -> Self { + GroupBehaviourEvent::Kademlia(event) + } +} + +//Create combined behaviour. +//Gossipsub takes care of message delivery semantics +//Membership takes care of providing peers who are part of the reliable broadcast group +//Kademlia takes provides closest neighbours and general DHT functionality +pub(crate) fn create_behaviour

    ( + keypair: &Arc, + ephemera_msg_topic: &Topic, + members_provider: P, + members_provider_delay: Duration, + membership_kind: MembershipKind, +) -> GroupNetworkBehaviour

    +where + P: Future>> + Send + Unpin + 'static, +{ + //TODO: review behaviours config(eg. gossipsub minimum peers, kademlia ttl, request-response timeouts etc.) + let local_peer_id = keypair.peer_id(); + let gossipsub = create_gossipsub(keypair, ephemera_msg_topic); + let request_response = create_request_response(); + let rendezvous_behaviour = create_membership( + members_provider, + members_provider_delay, + membership_kind, + local_peer_id, + ); + let kademlia = create_kademlia(keypair); + + GroupNetworkBehaviour { + members_provider: rendezvous_behaviour, + gossipsub, + request_response, + kademlia, + } +} + +// Configure networking messaging stack(Gossipsub) +pub(crate) fn create_gossipsub(local_key: &Arc, topic: &Topic) -> gossipsub::Behaviour { + let gossipsub_config = gossipsub::ConfigBuilder::default() + //TODO: settings from config + .heartbeat_interval(Duration::from_secs(5)) + .message_id_fn(|msg: &gossipsub::Message| Hasher::digest(&msg.data).into()) + .validation_mode(ValidationMode::Strict) + .build() + .expect("Valid config"); + + let mut behaviour = gossipsub::Behaviour::new( + MessageAuthenticity::Signed(local_key.inner().clone()), + gossipsub_config, + ) + .expect("Correct configuration"); + + info!("Subscribing to topic: {}", topic); + behaviour.subscribe(topic).expect("Valid topic"); + behaviour +} + +pub(crate) fn create_request_response() -> libp2p_request_response::Behaviour { + let config = libp2p_request_response::Config::default(); + libp2p_request_response::Behaviour::new( + RbMsgMessagesCodec, + iter::once(( + RbMsgProtocol, + libp2p_request_response::ProtocolSupport::Full, + )), + config, + ) +} + +pub(crate) fn create_membership

    ( + members_provider: P, + members_provider_delay: Duration, + membership_kind: MembershipKind, + local_peer_id: PeerId, +) -> membership::behaviour::Behaviour

    +where + P: Future>> + Send + Unpin + 'static, +{ + membership::behaviour::Behaviour::new( + members_provider, + members_provider_delay, + local_peer_id.into(), + membership_kind, + ) +} + +pub(super) fn create_kademlia(local_key: &Arc) -> kad::Kademlia { + let peer_id = local_key.peer_id(); + let mut cfg = kad::KademliaConfig::default(); + cfg.set_query_timeout(Duration::from_secs(5 * 60)); + let store = kad::store::MemoryStore::new(peer_id.0); + kad::Kademlia::with_config(*peer_id.inner(), store, cfg) +} + +//Configure networking connection stack(Tcp, Noise, Yamux) +//Tcp protocol for networking +//Noise protocol for encryption +//Yamux protocol for multiplexing +pub(crate) fn create_transport( + local_key: &Arc, +) -> anyhow::Result> { + let transport = TokioTransport::new(TokioConfig::default().nodelay(true)); + let transport = dns::TokioDnsConfig::system(transport)?; + + let noise_config = noise::Config::new(local_key.inner())?; + Ok(transport + .upgrade(libp2p::core::upgrade::Version::V1) + .authenticate(noise_config) + .multiplex(yamux::Config::default()) + .timeout(Duration::from_secs(20)) + .boxed()) +} diff --git a/ephemera/src/network/libp2p/behaviours/request_response/mod.rs b/ephemera/src/network/libp2p/behaviours/request_response/mod.rs new file mode 100644 index 0000000000..ca70b7b788 --- /dev/null +++ b/ephemera/src/network/libp2p/behaviours/request_response/mod.rs @@ -0,0 +1,101 @@ +use async_trait::async_trait; +use futures::{AsyncRead, AsyncWrite}; +use libp2p::request_response; +use log::trace; +use serde::{Deserialize, Serialize}; + +use crate::broadcast::RbMsg; +use crate::utilities::codec::varint_async::{read_length_prefixed, write_length_prefixed}; +use crate::utilities::id::EphemeraId; + +#[derive(Clone)] +pub(crate) struct RbMsgMessagesCodec; + +impl RbMsgMessagesCodec {} + +#[derive(Clone)] +pub(crate) struct RbMsgProtocol; + +impl request_response::ProtocolName for RbMsgProtocol { + fn protocol_name(&self) -> &[u8] { + "/ephemera/reliable_broadcast/1.0.0".as_bytes() + } +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub(crate) struct RbMsgResponse { + pub(crate) id: EphemeraId, +} + +impl RbMsgResponse { + pub(crate) fn new(id: EphemeraId) -> Self { + Self { id } + } +} + +#[async_trait] +impl request_response::Codec for RbMsgMessagesCodec { + type Protocol = RbMsgProtocol; + type Request = RbMsg; + type Response = RbMsgResponse; + + async fn read_request( + &mut self, + _: &Self::Protocol, + io: &mut T, + ) -> Result + where + T: AsyncRead + Unpin + Send, + { + //FIXME: max size + let data = read_length_prefixed(io, 1024 * 1024).await?; + //FIXME: switch to binary + let msg = serde_json::from_slice(&data)?; + trace!("Received request {:?}", msg); + Ok(msg) + } + + async fn read_response( + &mut self, + _: &Self::Protocol, + io: &mut T, + ) -> std::io::Result + where + T: AsyncRead + Unpin + Send, + { + //FIXME: max size + let response = read_length_prefixed(io, 1024 * 1024).await?; + //FIXME: switch to binary + let response = serde_json::from_slice(&response)?; + trace!("Received response {:?}", response); + Ok(response) + } + + async fn write_request( + &mut self, + _: &Self::Protocol, + io: &mut T, + req: Self::Request, + ) -> Result<(), std::io::Error> + where + T: AsyncWrite + Unpin + Send, + { + let data = serde_json::to_vec(&req).unwrap(); + write_length_prefixed(io, data).await?; + Ok(()) + } + + async fn write_response( + &mut self, + _: &Self::Protocol, + io: &mut T, + response: Self::Response, + ) -> std::io::Result<()> + where + T: AsyncWrite + Unpin + Send, + { + let response = serde_json::to_vec(&response).unwrap(); + write_length_prefixed(io, response).await?; + Ok(()) + } +} diff --git a/ephemera/src/network/libp2p/ephemera_sender.rs b/ephemera/src/network/libp2p/ephemera_sender.rs new file mode 100644 index 0000000000..c117a15539 --- /dev/null +++ b/ephemera/src/network/libp2p/ephemera_sender.rs @@ -0,0 +1,58 @@ +use log::trace; +use tokio::sync::mpsc; + +use crate::block::types::message::EphemeraMessage; +use crate::broadcast::RbMsg; + +#[derive(Debug, Clone, PartialEq)] +pub(crate) enum EphemeraEvent { + EphemeraMessage(Box), + ProtocolMessage(Box), + StoreInDht { key: Vec, value: Vec }, + QueryDht { key: Vec }, +} + +pub(crate) struct EphemeraToNetwork; + +impl EphemeraToNetwork { + pub(crate) fn init() -> (EphemeraToNetworkSender, EphemeraToNetworkReceiver) { + let (net_event_tx, net_event_rcv) = mpsc::channel(1000); + + let receiver = EphemeraToNetworkReceiver::new(net_event_rcv); + let sender = EphemeraToNetworkSender::new(net_event_tx); + + (sender, receiver) + } +} + +//Receives messages from the network +pub(crate) struct EphemeraToNetworkReceiver { + pub(crate) net_event_rcv: mpsc::Receiver, +} + +impl EphemeraToNetworkReceiver { + pub(crate) fn new(net_event_rcv: mpsc::Receiver) -> Self { + Self { net_event_rcv } + } +} + +//Sends messages to the network +pub(crate) struct EphemeraToNetworkSender { + pub(crate) network_event_sender_tx: mpsc::Sender, +} + +impl EphemeraToNetworkSender { + pub(crate) fn new(network_event_sender_tx: mpsc::Sender) -> Self { + Self { + network_event_sender_tx, + } + } + + pub(crate) async fn send_ephemera_event(&mut self, event: EphemeraEvent) -> anyhow::Result<()> { + trace!("Network event: {:?}", event); + self.network_event_sender_tx + .send(event) + .await + .map_err(|e| anyhow::anyhow!(e)) + } +} diff --git a/ephemera/src/network/libp2p/mod.rs b/ephemera/src/network/libp2p/mod.rs new file mode 100644 index 0000000000..bbd1c5b5ad --- /dev/null +++ b/ephemera/src/network/libp2p/mod.rs @@ -0,0 +1,4 @@ +mod behaviours; +pub(crate) mod ephemera_sender; +pub(crate) mod network_sender; +pub(crate) mod swarm_network; diff --git a/ephemera/src/network/libp2p/network_sender.rs b/ephemera/src/network/libp2p/network_sender.rs new file mode 100644 index 0000000000..e00782f6a0 --- /dev/null +++ b/ephemera/src/network/libp2p/network_sender.rs @@ -0,0 +1,67 @@ +use log::trace; +use std::collections::HashSet; +use tokio::sync::mpsc; + +use crate::block::types::message::EphemeraMessage; +use crate::broadcast::RbMsg; +use crate::peer::PeerId; + +#[derive(Debug, Clone, PartialEq)] +pub(crate) enum GroupChangeEvent { + PeersUpdated(HashSet), + LocalPeerRemoved(HashSet), + NotEnoughPeers(HashSet), +} + +#[derive(Debug, Clone, PartialEq)] +pub(crate) enum NetworkEvent { + EphemeraMessage(Box), + BroadcastMessage(Box), + GroupUpdate(GroupChangeEvent), + QueryDhtResponse { key: Vec, value: Vec }, +} + +pub(crate) struct EphemeraNetworkCommunication; + +impl EphemeraNetworkCommunication { + pub(crate) fn init() -> (NetCommunicationSender, NetCommunicationReceiver) { + let (net_event_tx, net_event_rcv) = mpsc::channel(1000); + + let receiver = NetCommunicationReceiver::new(net_event_rcv); + let sender = NetCommunicationSender::new(net_event_tx); + + (sender, receiver) + } +} + +//Receives messages from the network +pub(crate) struct NetCommunicationReceiver { + pub(crate) net_event_rcv: mpsc::Receiver, +} + +impl NetCommunicationReceiver { + pub(crate) fn new(net_event_rcv: mpsc::Receiver) -> Self { + Self { net_event_rcv } + } +} + +//Sends messages to the network +pub(crate) struct NetCommunicationSender { + pub(crate) network_event_sender_tx: mpsc::Sender, +} + +impl NetCommunicationSender { + pub(crate) fn new(network_event_sender_tx: mpsc::Sender) -> Self { + Self { + network_event_sender_tx, + } + } + + pub(crate) async fn send_network_event(&mut self, event: NetworkEvent) -> anyhow::Result<()> { + trace!("Network event: {:?}", event); + self.network_event_sender_tx + .send(event) + .await + .map_err(|e| anyhow::anyhow!(e)) + } +} diff --git a/ephemera/src/network/libp2p/swarm_network.rs b/ephemera/src/network/libp2p/swarm_network.rs new file mode 100644 index 0000000000..3e9dc04776 --- /dev/null +++ b/ephemera/src/network/libp2p/swarm_network.rs @@ -0,0 +1,604 @@ +use std::collections::HashSet; +use std::future::Future; +use std::str::FromStr; + +use futures::StreamExt; +use libp2p::kad::{GetClosestPeersResult, GetRecordResult}; +use libp2p::swarm::{NetworkBehaviour, SwarmBuilder}; +use libp2p::{ + gossipsub, gossipsub::IdentTopic as Topic, kad, request_response, swarm::SwarmEvent, Multiaddr, + Swarm, +}; +use log::{debug, error, info, trace}; + +use crate::membership::PeerInfo; +use crate::{ + block::types::message::EphemeraMessage, + broadcast::RbMsg, + codec::Encode, + core::builder::NodeInfo, + network::libp2p::behaviours, + network::libp2p::{ + behaviours::{ + create_behaviour, create_transport, request_response::RbMsgResponse, + GroupBehaviourEvent, GroupNetworkBehaviour, + }, + ephemera_sender::{ + EphemeraEvent, EphemeraToNetwork, EphemeraToNetworkReceiver, EphemeraToNetworkSender, + }, + network_sender::{ + EphemeraNetworkCommunication, GroupChangeEvent, + GroupChangeEvent::{LocalPeerRemoved, NotEnoughPeers}, + NetCommunicationReceiver, NetCommunicationSender, NetworkEvent, + }, + }, +}; + +pub(crate) type InitSwarm

    = ( + SwarmNetwork

    , + NetCommunicationReceiver, + EphemeraToNetworkSender, +); + +pub struct SwarmNetwork

    +where + P: Future>> + Send + Unpin + 'static, +{ + node_info: NodeInfo, + swarm: Swarm>, + from_ephemera_rcv: EphemeraToNetworkReceiver, + to_ephemera_tx: NetCommunicationSender, + ephemera_msg_topic: Topic, +} + +impl

    SwarmNetwork

    +where + P: Future>> + Send + Unpin + 'static, +{ + pub(crate) fn new(node_info: NodeInfo, members_provider: P) -> anyhow::Result> + where + P: Future>> + Send + 'static, + { + let (from_ephemera_tx, from_ephemera_rcv) = EphemeraToNetwork::init(); + let (to_ephemera_tx, to_ephemera_rcv) = EphemeraNetworkCommunication::init(); + + let libp2p_configuration = node_info.initial_config.libp2p.clone(); + + let local_key = node_info.keypair.clone(); + let peer_id = node_info.peer_id; + let ephemera_msg_topic = Topic::new(&libp2p_configuration.ephemera_msg_topic_name); + + let transport = create_transport(&local_key)?; + + let members_provider_delay = + std::time::Duration::from_secs(libp2p_configuration.members_provider_delay_sec); + let behaviour = create_behaviour( + &local_key, + &ephemera_msg_topic, + members_provider, + members_provider_delay, + libp2p_configuration.membership_kind.into(), + ); + + let swarm = SwarmBuilder::with_tokio_executor(transport, behaviour, peer_id.into()).build(); + + let network = SwarmNetwork { + node_info, + swarm, + from_ephemera_rcv, + to_ephemera_tx, + ephemera_msg_topic, + }; + + Ok((network, to_ephemera_rcv, from_ephemera_tx)) + } + + pub(crate) fn listen(&mut self) -> anyhow::Result<()> { + let address = + Multiaddr::from_str(&self.node_info.protocol_address()).expect("Invalid multi-address"); + self.swarm.listen_on(address.clone())?; + + info!("Listening on {address:?}"); + Ok(()) + } + + pub(crate) async fn start(mut self) -> anyhow::Result<()> { + loop { + tokio::select! { + swarm_event = self.swarm.next() => { + match swarm_event{ + Some(event) => { + if let Err(err) = self.handle_incoming_messages(event).await{ + error!("Error handling swarm event: {:?}", err); + } + } + None => { + anyhow::bail!("Swarm event channel closed"); + } + } + }, + Some(event) = self.from_ephemera_rcv.net_event_rcv.recv() => { + self.process_ephemera_events(event); + } + } + } + } + + fn process_ephemera_events(&mut self, event: EphemeraEvent) { + match event { + EphemeraEvent::EphemeraMessage(em) => { + self.send_ephemera_message(em.as_ref()); + } + EphemeraEvent::ProtocolMessage(pm) => { + self.send_broadcast_message(pm.as_ref()); + } + EphemeraEvent::StoreInDht { key, value } => { + let record = kad::Record::new(key, value); + let quorum = kad::Quorum::One; + match self + .swarm + .behaviour_mut() + .kademlia + .put_record(record, quorum) + { + Ok(ok) => { + trace!("StoreDht: {:?}", ok); + } + Err(err) => { + error!("StoreDht: {:?}", err); + } + } + } + EphemeraEvent::QueryDht { key } => { + let kad_key = kad::record::Key::new::>(key.as_ref()); + let query_id = self.swarm.behaviour_mut().kademlia.get_record(kad_key); + trace!("QueryDht: {:?}", query_id); + } + } + } + + async fn handle_incoming_messages( + &mut self, + swarm_event: SwarmEvent, + ) -> anyhow::Result<()> { + if let SwarmEvent::Behaviour(b) = swarm_event { + if let Err(err) = self.process_group_behaviour_event(b).await { + error!("Error handling behaviour event: {:?}", err); + } + } else { + Self::process_other_swarm_events(swarm_event); + } + Ok(()) + } + + async fn process_group_behaviour_event( + &mut self, + event: GroupBehaviourEvent, + ) -> anyhow::Result<()> { + match event { + GroupBehaviourEvent::Gossipsub(gs) => { + if let Err(err) = self.process_gossipsub_event(gs).await { + error!("Error processing gossipsub event: {:?}", err); + } + } + GroupBehaviourEvent::RequestResponse(request_response) => { + if let Err(err) = self.process_request_response(request_response).await { + error!("Error processing request response: {:?}", err); + } + } + + GroupBehaviourEvent::Membership(event) => { + if let Err(err) = self.process_members_provider_event(event).await { + error!("Error processing rendezvous event: {:?}", err); + } + } + GroupBehaviourEvent::Kademlia(ev) => { + if let Err(err) = self.process_kad_event(ev).await { + error!("Error processing kademlia event: {:?}", err); + } + } // GroupBehaviourEvent::Ping(_) => {} + } + Ok(()) + } + + async fn process_gossipsub_event(&mut self, event: gossipsub::Event) -> anyhow::Result<()> { + match event { + gossipsub::Event::Message { + propagation_source: _, + message_id: _, + message, + } => { + let msg: EphemeraMessage = serde_json::from_slice(&message.data[..])?; + self.to_ephemera_tx + .send_network_event(NetworkEvent::EphemeraMessage(msg.into())) + .await?; + } + + gossipsub::Event::Subscribed { peer_id, topic } => { + trace!("Peer {peer_id:?} subscribed to topic {topic:?}"); + } + gossipsub::Event::Unsubscribed { peer_id, topic } => { + trace!("Peer {peer_id:?} unsubscribed from topic {topic:?}"); + } + gossipsub::Event::GossipsubNotSupported { peer_id } => { + trace!("Peer {peer_id:?} does not support gossipsub"); + } + } + Ok(()) + } + + async fn process_request_response( + &mut self, + event: request_response::Event, + ) -> anyhow::Result<()> { + match event { + request_response::Event::Message { peer, message } => match message { + request_response::Message::Request { + request_id: _, + request, + channel, + } => { + let rb_id = request.id.clone(); + trace!("Received request {:?}", request); + self.to_ephemera_tx + .send_network_event(NetworkEvent::BroadcastMessage(request.into())) + .await?; + if let Err(err) = self + .swarm + .behaviour_mut() + .request_response + .send_response(channel, RbMsgResponse::new(rb_id)) + { + error!("Error sending response: {:?}", err); + } + } + request_response::Message::Response { + request_id, + response, + } => { + trace!("Received response {response:?} from peer: {peer:?}, request_id: {request_id:?}",); + } + }, + request_response::Event::OutboundFailure { + peer, + request_id, + error, + } => { + error!("Outbound failure: {error:?}, peer:{peer:?}, request_id:{request_id:?}",); + } + request_response::Event::InboundFailure { + peer, + request_id, + error, + } => { + error!("Inbound failure: {error:?}, peer:{peer:?}, request_id:{request_id:?}",); + } + request_response::Event::ResponseSent { peer, request_id } => { + trace!("Response sent to peer: {peer:?}, {request_id:?}",); + } + } + Ok(()) + } + + async fn process_members_provider_event( + &mut self, + event: behaviours::membership::behaviour::Event, + ) -> anyhow::Result<()> { + match event { + behaviours::membership::behaviour::Event::PeersUpdated(peers_ids) => { + info!("Peers updated: {:?}", peers_ids); + + let local_peer_id = *self.swarm.local_peer_id(); + for peer_id in peers_ids { + if peer_id == local_peer_id { + continue; + } + //FIXME: deprecated + #[allow(deprecated)] + let address = self + .swarm + .behaviour_mut() + .members_provider + .addresses_of_peer(&peer_id); + if let Some(address) = address.first() { + self.swarm + .behaviour_mut() + .kademlia + .add_address(&peer_id, address.clone()); + } + } + + let query_id = self + .swarm + .behaviour_mut() + .kademlia + .get_closest_peers(libp2p::PeerId::random()); + debug!("Neighbours: {:?}", query_id); + } + behaviours::membership::behaviour::Event::PeerUpdatePending => { + info!("Peer update pending"); + } + behaviours::membership::behaviour::Event::LocalRemoved(peers_ids) => { + //TODO: should pause all network block and message activities...? + let peers_ids = peers_ids.into_iter().map(Into::into).collect(); + let update = NetworkEvent::GroupUpdate(LocalPeerRemoved(peers_ids)); + self.to_ephemera_tx.send_network_event(update).await?; + } + behaviours::membership::behaviour::Event::NotEnoughPeers(peers_ids) => { + //TODO: should pause all network block and message activities...? + let peers_ids = peers_ids.into_iter().map(Into::into).collect(); + let update = NetworkEvent::GroupUpdate(NotEnoughPeers(peers_ids)); + self.to_ephemera_tx.send_network_event(update).await?; + } + } + Ok(()) + } + + async fn process_kad_event(&mut self, event: kad::KademliaEvent) -> anyhow::Result<()> { + match event { + kad::KademliaEvent::OutboundQueryProgressed { + id, + result, + stats, + step, + } => { + trace!( + "Outbound query progressed: id:{:?}, result:{:?}, stats:{:?}, step:{:?}", + id, + result, + stats, + step + ); + match result { + kad::QueryResult::GetClosestPeers(gcp) => { + self.process_closest_peers(gcp).await?; + } + kad::QueryResult::GetRecord(get_res) => { + self.process_get_record(get_res).await?; + } + kad::QueryResult::Bootstrap(bt) => { + trace!("Bootstrap: {:?}", bt); + } + kad::QueryResult::GetProviders(gp) => { + trace!("GetProviders: {:?}", gp); + } + kad::QueryResult::StartProviding(sp) => { + trace!("StartProviding: {:?}", sp); + } + kad::QueryResult::RepublishProvider(rp) => { + trace!("RepublishProvider: {:?}", rp); + } + kad::QueryResult::PutRecord(pr) => { + trace!("PutRecord: {:?}", pr); + } + kad::QueryResult::RepublishRecord(rr) => { + trace!("RepublishRecord: {:?}", rr); + } + } + } + + kad::KademliaEvent::InboundRequest { request } => { + trace!("Inbound request: {:?}", request); + } + + kad::KademliaEvent::RoutingUpdated { + peer: peer_id, + is_new_peer: _, + addresses, + bucket_range: _, + old_peer: _, + } => { + trace!("Routing updated: peer:{peer_id}, addresses:{addresses:?}",); + } + kad::KademliaEvent::UnroutablePeer { peer } => { + trace!("Unroutable peer: {:?}", peer); + } + kad::KademliaEvent::RoutablePeer { peer, address } => { + trace!("Routable peer: {:?}, address: {:?}", peer, address); + } + kad::KademliaEvent::PendingRoutablePeer { peer, address } => { + trace!("Pending routable peer: {:?}, address: {:?}", peer, address); + } + } + Ok(()) + } + + async fn process_get_record(&mut self, get_res: GetRecordResult) -> anyhow::Result<()> { + trace!("GetRecord: {:?}", get_res); + match get_res { + Ok(ok) => match ok { + kad::GetRecordOk::FoundRecord(fr) => { + let record = fr.record; + let event = NetworkEvent::QueryDhtResponse { + key: record.key.to_vec(), + value: record.value, + }; + self.to_ephemera_tx.send_network_event(event).await?; + } + kad::GetRecordOk::FinishedWithNoAdditionalRecord { .. } => { + trace!("FinishedWithNoAdditionalRecord"); + } + }, + Err(err) => { + trace!("Not getting record: {:?}", err); + } + } + Ok(()) + } + + async fn process_closest_peers(&mut self, gcp: GetClosestPeersResult) -> anyhow::Result<()> { + trace!("GetClosestPeers: {:?}", gcp); + //TODO: we need also to make sure that we have enough peers + // (Repeat if not enough, may need to wait network to stabilize) + match gcp { + Ok(cp) => { + if cp.peers.is_empty() { + log::warn!("No peers found"); + return Ok(()); + } + + let gossipsub = &mut self.swarm.behaviour_mut().gossipsub; + for peer_id in cp.peers { + gossipsub.add_explicit_peer(&peer_id); + } + + let active_peers = self + .swarm + .behaviour_mut() + .members_provider + .active_peer_ids_with_local(); + let active_peers = active_peers + .into_iter() + .map(Into::into) + .collect::>(); + let group_update = + NetworkEvent::GroupUpdate(GroupChangeEvent::PeersUpdated(active_peers)); + self.to_ephemera_tx.send_network_event(group_update).await?; + } + Err(err) => { + error!("Error getting closest peers: {:?}", err); + } + } + Ok(()) + } + + fn send_broadcast_message(&mut self, msg: &RbMsg) { + trace!("Sending broadcast message: {:?}", msg); + let local_peer_id = *self.swarm.local_peer_id(); + let behaviours = self.swarm.behaviour_mut(); + for peer in behaviours.members_provider.active_peer_ids() { + trace!("Sending broadcast message: {:?} to peer: {peer:?}", msg.id,); + if *peer == local_peer_id { + continue; + } + behaviours.request_response.send_request(peer, msg.clone()); + } + } + + fn send_ephemera_message(&mut self, msg: &EphemeraMessage) { + trace!("Sending Ephemera message: {:?}", msg); + match msg.encode() { + Ok(vec) => { + let topic = self.ephemera_msg_topic.clone(); + if let Err(err) = self.swarm.behaviour_mut().gossipsub.publish(topic, vec) { + error!("Error publishing message: {}", err); + } + } + Err(err) => { + error!("Error serializing message: {}", err); + } + } + } + + //Just logging + #[allow(clippy::too_many_lines)] + fn process_other_swarm_events(swarm_event: SwarmEvent) { + match swarm_event { + SwarmEvent::ConnectionEstablished { + peer_id, + endpoint, + num_established, + concurrent_dial_errors, + established_in, + } => { + trace!("Connection established: peer_id:{:?}, endpoint:{:?}, num_established:{:?}, concurrent_dial_errors:{:?}, established_in:{:?}", peer_id, endpoint, num_established, concurrent_dial_errors, established_in); + } + SwarmEvent::ConnectionClosed { + peer_id, + endpoint, + num_established, + cause: _, + } => { + trace!( + "Connection closed: peer_id:{:?}, endpoint:{:?}, num_established:{:?}", + peer_id, + endpoint, + num_established + ); + } + SwarmEvent::IncomingConnection { + local_addr, + send_back_addr, + } => { + trace!( + "Incoming connection: local_addr:{:?}, send_back_addr:{:?}", + local_addr, + send_back_addr + ); + } + SwarmEvent::IncomingConnectionError { + local_addr, + send_back_addr, + error, + } => { + trace!( + "Incoming connection error: local_addr:{:?}, send_back_addr:{:?}, error:{:?}", + local_addr, + send_back_addr, + error + ); + } + SwarmEvent::OutgoingConnectionError { peer_id, error } => { + trace!( + "Outgoing connection error: peer_id:{:?}, error:{:?}", + peer_id, + error + ); + } + #[allow(deprecated)] + SwarmEvent::BannedPeer { peer_id, endpoint } => { + trace!( + "Banned peer: peer_id:{:?}, endpoint:{:?}", + peer_id, + endpoint + ); + } + SwarmEvent::NewListenAddr { + listener_id, + address, + } => { + trace!( + "New listen address: listener_id:{:?}, address:{:?}", + listener_id, + address + ); + } + SwarmEvent::ExpiredListenAddr { + listener_id, + address, + } => { + trace!( + "Expired listen address: listener_id:{:?}, address:{:?}", + listener_id, + address + ); + } + SwarmEvent::ListenerClosed { + listener_id, + addresses, + reason, + } => { + trace!( + "Listener closed: listener_id:{:?}, addresses:{:?}, reason:{:?}", + listener_id, + addresses, + reason + ); + } + SwarmEvent::ListenerError { listener_id, error } => { + trace!( + "Listener error: listener_id:{:?}, error:{:?}", + listener_id, + error + ); + } + SwarmEvent::Dialing(peer_id) => { + trace!("Dialing: {peer_id:?}",); + } + + SwarmEvent::Behaviour(_) => { + trace!("Unexpected behaviour event"); + } + } + } +} diff --git a/ephemera/src/network/members/mod.rs b/ephemera/src/network/members/mod.rs new file mode 100644 index 0000000000..efb99c802a --- /dev/null +++ b/ephemera/src/network/members/mod.rs @@ -0,0 +1,252 @@ +use std::fmt::Display; +use std::future::Future; +use std::io::Write; +use std::path::PathBuf; +use std::pin::Pin; +use std::task::{Context, Poll}; + +use futures_util::{future, FutureExt}; +use log::error; +use nym_ephemera_common::types::JsonPeerInfo; +use serde::{Deserialize, Serialize}; +use thiserror::Error; + +use crate::crypto::PublicKey; +use crate::network::{Address, Peer}; +use crate::peer::PeerId; + +/// Information about an Ephemera peer. +#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)] +pub struct PeerInfo { + /// The cosmos address of the peer, used in interacting with the chain. + pub cosmos_address: String, + /// The address of the peer. + /// Expected formats: + /// 1. `:` + /// 2. `/ip4//tcp/` - this is the format used by libp2p multiaddr + pub address: String, + /// The public key of the peer. It uniquely identifies the peer. + /// Public key is used to derive the peer id. + pub pub_key: PublicKey, +} + +impl Display for PeerInfo { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, + "cosmos address {}, address {}, public key {}", + self.cosmos_address, self.address, self.pub_key + ) + } +} + +impl TryFrom for Peer { + type Error = anyhow::Error; + + fn try_from(value: PeerInfo) -> std::result::Result { + let address: Address = value.address.parse()?; + let public_key = value.pub_key; + Ok(Self { + cosmos_address: value.cosmos_address, + address, + public_key: public_key.clone(), + peer_id: PeerId::from_public_key(&public_key), + }) + } +} + +#[derive(Error, Debug)] +pub enum ProviderError { + #[error("ResourceUnavailable: {0}")] + ResourceUnavailable(String), + #[error("MembersProvider: {0}")] + MembersProvider(#[from] anyhow::Error), + #[error("Could not get peers - {0}")] + GetPeers(String), +} + +pub type Result = std::result::Result; + +/// A membership provider that does nothing. +/// Might be useful for testing. +pub struct DummyMembersProvider; + +#[allow(clippy::missing_errors_doc, clippy::unused_async)] +impl DummyMembersProvider { + pub async fn empty_peers_list() -> Result> { + Ok(vec![]) + } +} + +#[derive(Error, Debug)] +pub enum ConfigMembersProviderError { + #[error("ConfigDoesNotExist: '{0}'")] + NotExist(String), + #[error("ParsingFailed: {0}")] + ParsingFailed(#[from] config::ConfigError), + #[error("TomlError: {0}")] + TomlError(#[from] toml::ser::Error), + #[error("IoError: {0}")] + IoError(#[from] std::io::Error), +} + +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)] +pub struct PeerSetting { + /// The cosmos address of the peer, used in interacting with the chain. + pub cosmos_address: String, + /// The address of the peer. + /// Expected formats: + /// 1. `:` + /// 2. `/ip4//tcp/` - this is the format used by libp2p multiaddr + pub address: String, + ///Serialized public key. + /// + /// # Converting to string and back example + ///``` + /// use ephemera::crypto::{EphemeraKeypair, EphemeraPublicKey, Keypair, PublicKey}; + /// + /// let public_key = Keypair::generate(None).public_key(); + /// + /// let public_key_str = public_key.to_string(); + /// + /// let public_key_parsed = public_key_str.parse::().unwrap(); + /// + /// assert_eq!(public_key, public_key_parsed); + /// ``` + pub public_key: String, +} + +impl TryFrom for PeerInfo { + type Error = anyhow::Error; + + fn try_from(setting: PeerSetting) -> std::result::Result { + let pub_key = setting.public_key.parse::()?; + Ok(PeerInfo { + cosmos_address: setting.cosmos_address, + address: setting.address, + pub_key, + }) + } +} + +///[`ProviderFut`] that reads the peers from a toml config file. +/// +/// # Configuration example +/// ```toml +/// [[peers]] +/// name = "node1" +/// address = "/ip4/127.0.0.1/tcp/3000" +/// pub_key = "4XTTMEghav9LZThm6opUaHrdGEEYUkrfkakVg4VAetetBZDWJ" +/// +/// [[peers]] +/// name = "node2" +/// address = "/ip4/127.0.0.1/tcp/3001" +/// pub_key = "4XTTMFQt2tgNRmwRgEAaGQe2NXygsK6Vr3pkuBfYezhDfoVty" +/// ``` +pub struct ConfigMembersProvider { + config_location: PathBuf, +} + +impl ConfigMembersProvider { + /// Creates a new [`ConfigMembersProvider`] instance. + /// + /// # Arguments + /// * `path` - Path to the peers toml config file. + /// + /// # Errors + /// Returns [`ConfigMembersProviderError::NotExist`] if the file does not exist. + /// Returns [`ConfigMembersProviderError::ParsingFailed`] if the file is not a valid members file. + pub fn init>( + path: I, + ) -> std::result::Result { + let path_buf = path.into(); + if !path_buf.exists() { + return Err(ConfigMembersProviderError::NotExist( + path_buf.to_string_lossy().to_string(), + )); + } + + let provider = Self { + config_location: path_buf, + }; + + if provider.read_config().is_err() { + return Err(ConfigMembersProviderError::ParsingFailed( + config::ConfigError::Message("Failed to parse config".to_string()), + )); + } + + Ok(provider) + } + + pub(crate) fn read_config(&self) -> Result> { + let config_peers = ConfigPeers::try_load(self.config_location.clone()) + .map_err(|err| anyhow::anyhow!(err))?; + + let peers = config_peers + .peers + .iter() + .map(|peer| PeerInfo::try_from(peer.clone())) + .collect::>>()?; + Ok(peers) + } +} + +impl Future for ConfigMembersProvider { + type Output = Result>; + + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + future::ready(self.read_config()).poll_unpin(cx) + } +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub(crate) struct ConfigPeers { + peers: Vec, +} + +impl ConfigPeers { + pub(crate) fn new(peers: Vec) -> Self { + Self { peers } + } + + pub(crate) fn try_load>( + path: I, + ) -> std::result::Result { + let path = path.into(); + let config = config::Config::builder() + .add_source(config::File::from(path)) + .build()?; + + config.try_deserialize().map_err(Into::into) + } + + pub(crate) fn try_write>( + &self, + path: I, + ) -> std::result::Result<(), ConfigMembersProviderError> { + let config = toml::to_string(&self)?; + + let config = format!( + "#This file is generated by cli and automatically overwritten every time when cli is §\n{config}", + ); + + let mut file = std::fs::File::create(path.into())?; + file.write_all(config.as_bytes())?; + + Ok(()) + } +} + +impl TryFrom for PeerInfo { + type Error = anyhow::Error; + + fn try_from(json_peer_info: JsonPeerInfo) -> std::result::Result { + let pub_key = json_peer_info.public_key.parse::()?; + Ok(PeerInfo { + cosmos_address: json_peer_info.cosmos_address.to_string(), + address: json_peer_info.ip_address, + pub_key, + }) + } +} diff --git a/ephemera/src/network/mod.rs b/ephemera/src/network/mod.rs new file mode 100644 index 0000000000..205e147903 --- /dev/null +++ b/ephemera/src/network/mod.rs @@ -0,0 +1,237 @@ +use std::fmt::Display; +use std::net::IpAddr; +use std::str::FromStr; + +use ::libp2p::{multiaddr::Protocol, Multiaddr}; +use libp2p_identity::PeerId as Libp2pPeerId; +use log::info; +use serde::{Deserialize, Serialize}; +use thiserror::Error; + +use crate::crypto::PublicKey; + +pub(crate) mod libp2p; +pub(crate) mod members; + +pub(crate) type PeerIdType = Libp2pPeerId; + +#[derive(Debug, Error)] +pub enum PeerIdError { + #[error("Invalid peer ID: {0}")] + InvalidPeerId(String), +} + +/// Unique identifier of a peer. +#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] +pub struct PeerId(pub(crate) PeerIdType); + +impl PeerId { + #[must_use] + pub fn random() -> Self { + Self(PeerIdType::random()) + } + + /// Returns the internal representation of the peer ID. + pub(crate) fn inner(&self) -> &PeerIdType { + &self.0 + } + + /// Returns a raw representation of the peer ID. + #[must_use] + pub fn to_bytes(&self) -> Vec { + self.0.to_bytes() + } + + /// Returns a peer ID from a raw representation. + /// + /// # Returns + /// A `PeerId` if the bytes are valid. + /// + /// # Errors + /// An error if input has wrong format. This function is reverse to [`PeerId::to_bytes`]. + pub fn from_bytes(bytes: &[u8]) -> Result { + Ok(Self(PeerIdType::from_bytes(bytes).map_err(|e| { + PeerIdError::InvalidPeerId(format!("Invalid peer ID: {e}")) + })?)) + } + + /// Builds a `PeerId` from a public key. + #[must_use] + pub fn from_public_key(public_key: &PublicKey) -> Self { + Self(PeerIdType::from_public_key(public_key.inner())) + } +} + +impl From for libp2p_identity::PeerId { + fn from(peer_id: PeerId) -> Self { + peer_id.0 + } +} + +impl From for PeerId { + fn from(peer_id: libp2p_identity::PeerId) -> Self { + Self(peer_id) + } +} + +impl Display for PeerId { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.0) + } +} + +pub trait ToPeerId { + fn peer_id(&self) -> PeerId; +} + +/// A peer of the network. +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub(crate) struct Peer { + /// The peer's ID. It identifies the peer uniquely and is derived from its public key. + /// + /// # Deriving PeerId from PublicKey example + /// + /// ``` + /// use ephemera::crypto::{EphemeraKeypair, Keypair, PublicKey}; + /// use ephemera::peer::{PeerId, ToPeerId}; + /// + /// let public_key = Keypair::generate(None).public_key(); + /// + /// let peer_id = PeerId::from_public_key(&public_key); + /// + /// assert_eq!(peer_id, public_key.peer_id()); + /// + /// ``` + pub peer_id: PeerId, + /// The peer's public key. It matches PeerId. + pub public_key: PublicKey, + /// The peer's address. + pub address: Address, + /// The cosmos address of the peer, used in interacting with the chain. + pub cosmos_address: String, +} + +#[derive(Error, Debug)] +pub enum AddressError { + #[error("Failed to parse address: {0}")] + ParsingError(String), +} + +/// Ephemera node address. +/// +/// Supported formats: +/// 1. `:` +/// 2. `/ip4//tcp/` - this is format used by libp2p multiaddr. +/// 3. `/dns4//tcp/` - this is format used by libp2p multiaddr. +/// See [libp2p/multiaddress](https://github.com/libp2p/specs/blob/master/addressing/README.md) for more details. +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct Address(pub Multiaddr); + +impl Address { + pub fn inner(&self) -> &Multiaddr { + &self.0 + } +} + +impl From for Address { + fn from(multiaddr: Multiaddr) -> Self { + Self(multiaddr) + } +} + +impl FromStr for Address { + type Err = AddressError; + + fn from_str(s: &str) -> Result { + let address: Option = match Multiaddr::from_str(s) { + Ok(multiaddr) => Some(multiaddr), + Err(err) => { + info!("Failed to parse multiaddr: {}", err); + None + } + }; + + let multi_address = address.or_else(|| match std::net::SocketAddr::from_str(s) { + Ok(sa) => { + let mut multiaddr = Multiaddr::empty(); + match sa { + std::net::SocketAddr::V4(v4) => { + multiaddr.push(Protocol::Ip4(*v4.ip())); + multiaddr.push(Protocol::Tcp(v4.port())); + } + std::net::SocketAddr::V6(v6) => { + multiaddr.push(Protocol::Ip6(*v6.ip())); + multiaddr.push(Protocol::Tcp(v6.port())); + } + } + + Some(multiaddr) + } + Err(err) => { + info!("Failed to parse socket addr: {err}"); + None + } + }); + + match multi_address { + Some(multi_address) => Ok(Self(multi_address)), + None => Err(AddressError::ParsingError(s.to_string())), + } + } +} + +impl TryFrom

    for (IpAddr, u16) { + type Error = std::io::Error; + + fn try_from(addr: Address) -> Result { + let mut multiaddr = addr.0; + if let Some(Protocol::Tcp(port)) = multiaddr.pop() { + if let Some(Protocol::Ip4(ip)) = multiaddr.pop() { + return Ok((IpAddr::V4(ip), port)); + } + } + Err(std::io::Error::new( + std::io::ErrorKind::Other, + "invalid address", + )) + } +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn test_parse_multiaddr() { + "/ip4/127.0.0.1/tcp/1234".parse::
    ().unwrap(); + } + + #[test] + fn test_parse_ip_port() { + "127.0.0.1:1234".parse::
    ().unwrap(); + } + + #[test] + fn test_fail_parse_multiaddr_without_port() { + let result = "/ip4/127.0.0.1/tcp/".parse::
    (); + assert!(matches!(result, Err(AddressError::ParsingError(_)))); + } + + #[test] + fn test_fail_parse_multiaddr_without_ip() { + let result = "/ip4//tcp/1234".parse::
    (); + assert!(matches!(result, Err(AddressError::ParsingError(_)))); + } + + #[test] + fn test_fail_parse_ip_port_without_port() { + let result = "127.0.0.1".parse::
    (); + assert!(matches!(result, Err(AddressError::ParsingError(_)))); + } + + #[test] + fn test_fail_parse_ip_port_without_ip() { + let result = "1234".parse::
    (); + assert!(matches!(result, Err(AddressError::ParsingError(_)))); + } +} diff --git a/ephemera/src/storage/mod.rs b/ephemera/src/storage/mod.rs new file mode 100644 index 0000000000..2342625d19 --- /dev/null +++ b/ephemera/src/storage/mod.rs @@ -0,0 +1,65 @@ +//! # Database +//! +//! It supports `SqlLite` and `RocksDB`. +//! +//! ## `RocksDB` +//! +//! `SqlLite` is used by default. +//! +//! ## `SqlLite` +//! +//! To use `SqlLite`, you need to compile with the `sqlite_storage` feature and with `--no-default-features` flag. + +use std::collections::HashSet; + +use thiserror::Error; + +use crate::block::types::block::Block; +use crate::peer::PeerId; +use crate::utilities::crypto::Certificate; +use crate::utilities::merkle::MerkleTree; + +#[cfg(feature = "rocksdb_storage")] +pub(crate) mod rocksdb; + +#[cfg(feature = "sqlite_storage")] +pub(crate) mod sqlite; + +pub(crate) type Result = std::result::Result; + +#[derive(Error, Debug)] +pub(crate) enum DatabaseError { + //Pessimistically assume that most database errors are fatal and unrecoverable. + #[error("Database failure: {0}")] + DatabaseFailure(#[from] anyhow::Error), +} + +pub(crate) trait EphemeraDatabase: Send { + /// Returns block by its id. Block ids are generated by Ephemera + fn get_block_by_hash(&self, block_hash: &str) -> Result>; + + /// Returns last committed/finalised block. + fn get_last_block(&self) -> Result>; + + /// Returns block by its height + fn get_block_by_height(&self, height: u64) -> Result>; + + /// Returns block certificates. + /// + /// Certificates were created as part of broadcast protocol and signed by peers who participated. + fn get_block_certificates(&self, block_hash: &str) -> Result>>; + + /// Returns peers who participated in block broadcast. + fn get_block_broadcast_group(&self, block_hash: &str) -> Result>>; + + /// Stores block and its signatures + fn store_block( + &mut self, + block: &Block, + certificates: HashSet, + members: HashSet, + ) -> Result<()>; + + /// Returns block merkle tree + fn get_block_merkle_tree(&self, block_hash: &str) -> Result>; +} diff --git a/ephemera/src/storage/rocksdb/mod.rs b/ephemera/src/storage/rocksdb/mod.rs new file mode 100644 index 0000000000..dc86a4bd7f --- /dev/null +++ b/ephemera/src/storage/rocksdb/mod.rs @@ -0,0 +1,125 @@ +use std::collections::HashSet; +use std::sync::Arc; + +use log::info; +use rocksdb::{TransactionDB, TransactionDBOptions}; + +use crate::block::types::block::Block; +use crate::config::DatabaseConfiguration; +use crate::peer::PeerId; +use crate::storage::rocksdb::query::Database; +use crate::storage::rocksdb::store::DbStore; +use crate::storage::EphemeraDatabase; +use crate::storage::Result; +use crate::utilities::crypto::Certificate; +use crate::utilities::merkle::MerkleTree; + +pub(crate) mod query; +pub(crate) mod store; + +pub(crate) struct RocksDbStorage { + pub(crate) db_store: DbStore, + pub(crate) db_query: Database, +} + +const PREFIX_LAST_BLOCK_KEY: &str = "last_block"; +const PREFIX_BLOCK_HASH: &str = "block_hash"; +const PREFIX_BLOCK_HEIGHT: &str = "block_height"; +const PREFIX_CERTIFICATES: &str = "block_certificates"; +const PREFIX_MEMBERS: &str = "block_members"; +const MERKLE_TREE: &str = "merkle_tree"; + +impl RocksDbStorage { + pub fn open(db_conf: &DatabaseConfiguration) -> Result { + info!("Opening RocksDB database at {}", db_conf.rocksdb_path); + + let mut options = rocksdb::Options::default(); + options.create_if_missing(db_conf.create_if_not_exists); + + let db = TransactionDB::open( + &options, + &TransactionDBOptions::default(), + db_conf.rocksdb_path.clone(), + ) + .map_err(|err| anyhow::anyhow!(err))?; + + let db = Arc::new(db); + let db_store = DbStore::new(db.clone()); + let db_query = Database::new(db); + let storage = Self { db_store, db_query }; + + info!("Opened RocksDB database at {}", db_conf.rocksdb_path); + Ok(storage) + } +} + +impl EphemeraDatabase for RocksDbStorage { + fn get_block_by_hash(&self, block_id: &str) -> Result> { + self.db_query + .get_block_by_hash(block_id) + .map_err(Into::into) + } + + fn get_last_block(&self) -> Result> { + self.db_query.get_last_block().map_err(Into::into) + } + + fn get_block_by_height(&self, height: u64) -> Result> { + self.db_query + .get_block_by_height(height) + .map_err(Into::into) + } + + fn get_block_certificates(&self, block_id: &str) -> Result>> { + self.db_query + .get_block_certificates(block_id) + .map_err(Into::into) + } + + fn get_block_broadcast_group(&self, block_id: &str) -> Result>> { + self.db_query + .get_block_broadcast_group(block_id) + .map_err(Into::into) + } + + fn store_block( + &mut self, + block: &Block, + certificates: HashSet, + members: HashSet, + ) -> Result<()> { + self.db_store + .store_block(block, certificates, members) + .map_err(Into::into) + } + + fn get_block_merkle_tree(&self, block_hash: &str) -> Result> { + self.db_query + .get_block_merkle_tree(block_hash) + .map_err(Into::into) + } +} + +fn block_hash_key(block_hash: &str) -> String { + format!("{PREFIX_BLOCK_HASH}:{block_hash}") +} + +fn block_height_key(height: &u64) -> String { + format!("{PREFIX_BLOCK_HEIGHT}:{height}") +} + +fn last_block_key() -> String { + PREFIX_LAST_BLOCK_KEY.to_string() +} + +fn certificates_key(block_hash: &str) -> String { + format!("{PREFIX_CERTIFICATES}:{block_hash}",) +} + +fn members_key(block_hash: &str) -> String { + format!("{PREFIX_MEMBERS}:{block_hash}",) +} + +fn merkle_tree_key(block_hash: &str) -> String { + format!("{MERKLE_TREE}:{block_hash}",) +} diff --git a/ephemera/src/storage/rocksdb/query.rs b/ephemera/src/storage/rocksdb/query.rs new file mode 100644 index 0000000000..a2586557e5 --- /dev/null +++ b/ephemera/src/storage/rocksdb/query.rs @@ -0,0 +1,118 @@ +use std::sync::Arc; + +use log::trace; +use rocksdb::TransactionDB; + +use crate::block::types::block::Block; +use crate::network::PeerId; +use crate::storage::rocksdb::{ + block_hash_key, block_height_key, certificates_key, last_block_key, members_key, + merkle_tree_key, +}; +use crate::utilities::crypto::Certificate; +use crate::utilities::merkle::MerkleTree; + +pub struct Database { + database: Arc, +} + +impl Database { + #[allow(dead_code)] + pub fn new(db: Arc) -> Database { + Database { database: db } + } + + pub(crate) fn get_block_by_hash(&self, block_hash: &str) -> anyhow::Result> { + trace!("Getting block by id: {:?}", block_hash); + + let block_hash_key = block_hash_key(block_hash); + + let block = if let Some(block) = self.database.get(block_hash_key)? { + let block = serde_json::from_slice::(&block)?; + trace!("Found block: {}", block.header); + Some(block) + } else { + trace!("Didn't find block"); + None + }; + Ok(block) + } + + pub(crate) fn get_last_block(&self) -> anyhow::Result> { + trace!("Getting last block"); + + if let Some(block_hash) = self.database.get(last_block_key())? { + let block_hash = String::from_utf8(block_hash)?; + self.get_block_by_hash(&block_hash) + } else { + trace!("Unable to get last block"); + Ok(None) + } + } + + pub(crate) fn get_block_by_height(&self, height: u64) -> anyhow::Result> { + trace!("Getting block by height: {}", height); + + if let Some(block_hash) = self.database.get(block_height_key(&height))? { + let block_hash = String::from_utf8(block_hash)?; + self.get_block_by_hash(&block_hash) + } else { + trace!("Didn't find block"); + Ok(None) + } + } + + pub(crate) fn get_block_certificates( + &self, + block_hash: &str, + ) -> anyhow::Result>> { + trace!("Getting block signatures: {}", block_hash); + + let certificates_key = certificates_key(block_hash); + + if let Some(certificates) = self.database.get(certificates_key)? { + let certificates: Vec = serde_json::from_slice(&certificates)?; + trace!("Found certificates: {:?}", certificates); + Ok(Some(certificates)) + } else { + trace!("Didn't find signatures"); + Ok(None) + } + } + + pub(crate) fn get_block_broadcast_group( + &self, + block_hash: &str, + ) -> anyhow::Result>> { + trace!("Getting block broadcast group: {}", block_hash); + + let members_key = members_key(block_hash); + + if let Some(members) = self.database.get(members_key)? { + let members: Vec = serde_json::from_slice(&members)?; + trace!("Found members: {:?}", members); + Ok(Some(members)) + } else { + trace!("Didn't find members"); + Ok(None) + } + } + + pub(crate) fn get_block_merkle_tree( + &self, + block_hash: &str, + ) -> anyhow::Result> { + trace!("Getting block merkle tree: {}", block_hash); + + let merkle_tree_key = merkle_tree_key(block_hash); + + if let Some(merkle_tree) = self.database.get(merkle_tree_key)? { + let merkle_tree: MerkleTree = serde_json::from_slice(&merkle_tree)?; + trace!("Found merkle tree: {:?}", merkle_tree); + Ok(Some(merkle_tree)) + } else { + trace!("Didn't find merkle tree"); + Ok(None) + } + } +} diff --git a/ephemera/src/storage/rocksdb/store.rs b/ephemera/src/storage/rocksdb/store.rs new file mode 100644 index 0000000000..66e1f6d2de --- /dev/null +++ b/ephemera/src/storage/rocksdb/store.rs @@ -0,0 +1,79 @@ +use std::collections::HashSet; +use std::sync::Arc; + +use crate::block::types::block::Block; +use crate::network::PeerId; +use crate::storage::rocksdb::{ + block_hash_key, block_height_key, certificates_key, last_block_key, members_key, + merkle_tree_key, +}; +use log::{debug, trace}; +use rocksdb::{TransactionDB, WriteBatchWithTransaction}; + +use crate::utilities::crypto::Certificate; + +pub struct DbStore { + connection: Arc, +} + +impl DbStore { + pub fn new(db: Arc) -> DbStore { + DbStore { connection: db } + } + + pub(crate) fn store_block( + &self, + block: &Block, + certificates: HashSet, + members: HashSet, + ) -> anyhow::Result<()> { + debug!("Storing block: {}", block.header); + trace!("Storing block certificates: {}", certificates.len()); + + let hash_str = block.header.hash.to_string(); + + let block_id_key = block_hash_key(&hash_str); + let certificates_key = certificates_key(&hash_str); + let height_key = block_height_key(&block.header.height); + let members_key = members_key(&hash_str); + let merkle_tree_key = merkle_tree_key(&hash_str); + + // Check UNIQUE constraints + let existing_id = self.connection.get(&block_id_key)?; + if existing_id.is_some() { + return Err(anyhow::anyhow!("Block already exists")); + } + + let mut batch = WriteBatchWithTransaction::::default(); + + //Store last block id(without prefix!) + //May want to check that height is incremented by 1 + batch.put(last_block_key(), hash_str.clone()); + + // Store block height + batch.put(height_key.as_bytes(), hash_str); + + // Store block(without signature) + let block_bytes = serde_json::to_vec::(block)?; + batch.put(block_id_key.as_bytes(), block_bytes); + + // Store block certificates + let certificates_bytes = + serde_json::to_vec(&certificates.into_iter().collect::>()) + .map_err(|e| anyhow::anyhow!(e))?; + batch.put(certificates_key.as_bytes(), certificates_bytes); + + // Store block members + let members_bytes = serde_json::to_vec(&members.into_iter().collect::>()) + .map_err(|e| anyhow::anyhow!(e))?; + batch.put(members_key.as_bytes(), members_bytes); + + //Store Merkle Tree + let merkle_tree = block.merkle_tree()?; + let merkle_tree_bytes = serde_json::to_vec(&merkle_tree).map_err(|e| anyhow::anyhow!(e))?; + batch.put(merkle_tree_key.as_bytes(), merkle_tree_bytes); + + self.connection.write(batch)?; + Ok(()) + } +} diff --git a/ephemera/src/storage/sqlite/mod.rs b/ephemera/src/storage/sqlite/mod.rs new file mode 100644 index 0000000000..798e9eb501 --- /dev/null +++ b/ephemera/src/storage/sqlite/mod.rs @@ -0,0 +1,107 @@ +use log::{error, info}; +use rusqlite::Connection; +use std::collections::HashSet; + +use crate::block::types::block::Block; +use crate::config::DatabaseConfiguration; +use crate::peer::PeerId; +use crate::storage::sqlite::query::DbQuery; +use crate::storage::sqlite::store::Database; +use crate::storage::EphemeraDatabase; +use crate::storage::Result; +use crate::utilities::crypto::Certificate; +use crate::utilities::merkle::MerkleTree; + +pub(crate) mod query; +pub(crate) mod store; + +mod migrations { + use refinery::embed_migrations; + + embed_migrations!("migrations"); +} + +pub(crate) struct SqliteStorage { + pub(crate) db_store: Database, + pub(crate) db_query: DbQuery, +} + +impl SqliteStorage { + pub(crate) fn open(db_conf: DatabaseConfiguration) -> Result { + let mut flags = rusqlite::OpenFlags::default(); + if !db_conf.create_if_not_exists { + flags.remove(rusqlite::OpenFlags::SQLITE_OPEN_CREATE); + } + + let mut connection = Connection::open_with_flags(db_conf.sqlite_path.clone(), flags) + .map_err(|err| anyhow::anyhow!(err))?; + Self::run_migrations(&mut connection)?; + + info!("Starting db backend with path: {}", db_conf.sqlite_path); + let db_store = Database::open(db_conf.clone(), flags)?; + let db_query = DbQuery::open(db_conf, flags)?; + let storage = Self { db_store, db_query }; + Ok(storage) + } + + pub(crate) fn run_migrations(connection: &mut Connection) -> Result<()> { + info!("Running database migrations"); + match migrations::migrations::runner().run(connection) { + Ok(ok) => { + info!("Database migrations completed:{:?} ", ok); + Ok(()) + } + Err(err) => { + error!("Database migrations failed: {}", err); + Err(anyhow::anyhow!(err).into()) + } + } + } +} + +impl EphemeraDatabase for SqliteStorage { + fn get_block_by_hash(&self, block_id: &str) -> Result> { + self.db_query + .get_block_by_hash(block_id) + .map_err(Into::into) + } + + fn get_last_block(&self) -> Result> { + self.db_query.get_last_block().map_err(Into::into) + } + + fn get_block_by_height(&self, height: u64) -> Result> { + self.db_query + .get_block_by_height(height) + .map_err(Into::into) + } + + fn get_block_certificates(&self, block_id: &str) -> Result>> { + self.db_query + .get_block_certificates(block_id) + .map_err(Into::into) + } + + fn get_block_broadcast_group(&self, block_id: &str) -> Result>> { + self.db_query + .get_block_broadcast_group(block_id) + .map_err(Into::into) + } + + fn store_block( + &mut self, + block: &Block, + certificates: HashSet, + members: HashSet, + ) -> Result<()> { + self.db_store + .store_block(block, certificates, members) + .map_err(Into::into) + } + + fn get_block_merkle_tree(&self, block_hash: &str) -> Result> { + self.db_query + .get_block_merkle_tree(block_hash) + .map_err(Into::into) + } +} diff --git a/ephemera/src/storage/sqlite/query.rs b/ephemera/src/storage/sqlite/query.rs new file mode 100644 index 0000000000..df3b1cd342 --- /dev/null +++ b/ephemera/src/storage/sqlite/query.rs @@ -0,0 +1,167 @@ +use log::{error, trace}; +use rusqlite::{params, Connection, OpenFlags, OptionalExtension, Row}; + +use crate::block::types::block::Block; +use crate::config::DatabaseConfiguration; +use crate::peer::PeerId; +use crate::utilities::crypto::Certificate; +use crate::utilities::merkle::MerkleTree; + +pub(crate) struct DbQuery { + pub(crate) connection: Connection, +} + +impl DbQuery { + pub(crate) fn open(db_conf: DatabaseConfiguration, flags: OpenFlags) -> anyhow::Result { + let connection = Connection::open_with_flags(db_conf.sqlite_path, flags)?; + let query = Self { connection }; + Ok(query) + } + + pub(crate) fn get_block_by_hash(&self, block_hash: &str) -> anyhow::Result> { + let mut stmt = self + .connection + .prepare_cached("SELECT block FROM blocks WHERE block_hash = ?1")?; + let block = stmt + .query_row(params![block_hash], Self::map_block()) + .optional()?; + + if let Some(block) = &block { + trace!("Found block: {}", block.header); + } else { + trace!("Block not found: {}", block_hash); + }; + + Ok(block) + } + + pub(crate) fn get_last_block(&self) -> anyhow::Result> { + let mut stmt = self + .connection + .prepare_cached("SELECT block FROM blocks where id = (select max(id) from blocks)")?; + + let block = stmt.query_row(params![], Self::map_block()).optional()?; + + if let Some(block) = &block { + trace!("Found last block: {}", block.header); + } else { + trace!("Last block not found"); + }; + + Ok(block) + } + + pub(crate) fn get_block_by_height(&self, height: u64) -> anyhow::Result> { + let mut stmt = self + .connection + .prepare_cached("SELECT block FROM blocks WHERE height = ?1")?; + let block = stmt + .query_row(params![height], Self::map_block()) + .optional()?; + + if let Some(block) = &block { + trace!("Found block: {}", block.header); + } else { + trace!("Block not found: {}", height); + }; + + Ok(block) + } + + pub(crate) fn get_block_certificates( + &self, + block_hash: &str, + ) -> anyhow::Result>> { + let mut stmt = self + .connection + .prepare_cached("SELECT certificates FROM block_certificates where block_hash = ?1")?; + + let signatures = stmt + .query_row(params![block_hash], |row| { + let certificates: Vec = row.get(0)?; + let certificates = serde_json::from_slice::>(&certificates) + .map_err(|e| { + error!("Error deserializing certificates: {}", e); + rusqlite::Error::InvalidQuery {} + })?; + Ok(certificates) + }) + .optional()?; + + if signatures.is_some() { + trace!("Found block {} certificates", block_hash); + } else { + trace!("Certificates not found"); + }; + + Ok(signatures) + } + + pub(crate) fn get_block_broadcast_group( + &self, + block_hash: &str, + ) -> anyhow::Result>> { + let mut stmt = self + .connection + .prepare_cached("SELECT members FROM block_broadcast_group where block_hash = ?1")?; + + let members = stmt + .query_row(params![block_hash], |row| { + let members: Vec = row.get(0)?; + let members = serde_json::from_slice::>(&members).map_err(|e| { + error!("Error deserializing members: {}", e); + rusqlite::Error::InvalidQuery {} + })?; + Ok(members) + }) + .optional()?; + + if members.is_some() { + trace!("Found block {} members", block_hash); + } else { + trace!("Members not found"); + }; + + Ok(members) + } + + pub(crate) fn get_block_merkle_tree( + &self, + block_hash: &str, + ) -> anyhow::Result> { + let mut stmt = self + .connection + .prepare_cached("SELECT merkle_tree FROM block_merkle_tree where block_hash = ?1")?; + + let merkle_tree = stmt + .query_row(params![block_hash], |row| { + let merkle_tree: Vec = row.get(0)?; + let merkle_tree = + serde_json::from_slice::(&merkle_tree).map_err(|e| { + error!("Error deserializing merkle_tree: {}", e); + rusqlite::Error::InvalidQuery {} + })?; + Ok(merkle_tree) + }) + .optional()?; + + if merkle_tree.is_some() { + trace!("Found block {} merkle_tree", block_hash); + } else { + trace!("Merkle_tree not found"); + }; + + Ok(merkle_tree) + } + + fn map_block() -> impl FnOnce(&Row) -> Result { + |row| { + let body: Vec = row.get(0)?; + let block = serde_json::from_slice::(&body).map_err(|e| { + error!("Error deserializing block: {}", e); + rusqlite::Error::InvalidQuery {} + })?; + Ok(block) + } + } +} diff --git a/ephemera/src/storage/sqlite/store.rs b/ephemera/src/storage/sqlite/store.rs new file mode 100644 index 0000000000..29cf181d9f --- /dev/null +++ b/ephemera/src/storage/sqlite/store.rs @@ -0,0 +1,71 @@ +use crate::block::types::block::Block; +use anyhow::Result; +use log::debug; +use rusqlite::{params, Connection, OpenFlags}; +use std::collections::HashSet; + +use crate::config::DatabaseConfiguration; +use crate::network::PeerId; +use crate::utilities::crypto::Certificate; + +pub struct Database { + connection: Connection, +} + +impl Database { + pub fn open(db_conf: DatabaseConfiguration, flags: OpenFlags) -> Result { + let connection = Connection::open_with_flags(db_conf.sqlite_path, flags)?; + Ok(Database { connection }) + } + + pub(crate) fn store_block( + &mut self, + block: &Block, + certificates: HashSet, + members: HashSet, + ) -> Result<()> { + debug!("Storing block: {}", block.header); + + let hash = block.header.hash.to_string(); + let height = block.header.height; + let block_bytes = serde_json::to_vec::(block).map_err(|e| anyhow::anyhow!(e))?; + let certificates_bytes = + serde_json::to_vec(&certificates.into_iter().collect::>()) + .map_err(|e| anyhow::anyhow!(e))?; + let members_bytes = serde_json::to_vec(&members.into_iter().collect::>()) + .map_err(|e| anyhow::anyhow!(e))?; + let merkle_tree = block.merkle_tree()?; + let merkle_tree_bytes = serde_json::to_vec(&merkle_tree).map_err(|e| anyhow::anyhow!(e))?; + + let tx = self.connection.transaction()?; + { + let mut statement = tx.prepare_cached( + "INSERT INTO blocks (block_hash, height, block) VALUES (?1, ?2, ?3)", + )?; + statement.execute(params![&hash, &height, &block_bytes,])?; + + let mut statement = tx.prepare_cached( + "INSERT INTO block_certificates (block_hash, certificates) VALUES (?1, ?2)", + )?; + + statement.execute(params![&hash, &certificates_bytes,])?; + + let mut statement = tx.prepare_cached( + "INSERT INTO block_broadcast_group (block_hash, members) VALUES (?1, ?2)", + )?; + + statement.execute(params![&hash, &members_bytes])?; + + //store Merkle Tree + let mut statement = tx.prepare_cached( + "INSERT INTO block_merkle_tree (block_hash, merkle_tree) VALUES (?1, ?2)", + )?; + + statement.execute(params![&hash, &merkle_tree_bytes])?; + } + + tx.commit()?; + + Ok(()) + } +} diff --git a/ephemera/src/utilities/codec/mod.rs b/ephemera/src/utilities/codec/mod.rs new file mode 100644 index 0000000000..69905b5440 --- /dev/null +++ b/ephemera/src/utilities/codec/mod.rs @@ -0,0 +1,102 @@ +use serde::{Deserialize, Serialize}; +use thiserror::Error; + +pub(crate) mod varint_async; +pub(crate) mod varint_bytes; + +pub(crate) type Codec = SerdeCodec; + +#[derive(Debug, Error)] +pub enum DecodingError { + #[error("Decoding error: {0}")] + DecodingError(#[from] serde_json::Error), +} + +#[allow(clippy::module_name_repetitions)] +#[derive(Debug, Error)] +pub enum EncodingError { + #[error("Encoding error: {0}")] + EncodingError(#[from] serde_json::Error), +} + +/// Simple trait for encoding +pub(crate) trait EphemeraCodec { + /// Encodes a message into a vector of bytes + /// + /// # Arguments + /// + /// * `data` - data to encode + /// + /// # Returns + /// + /// * `Result, EncodingError>` - encoded data or error + /// + /// # Errors + /// + /// * `EncodingError` - if encoding fails + fn encode(data: &M) -> Result, EncodingError>; + + /// Decodes a message from a vector of bytes + fn decode serde::Deserialize<'de>>(bytes: &[u8]) -> Result; +} + +pub(crate) struct SerdeCodec; + +impl EphemeraCodec for SerdeCodec { + fn encode(data: &M) -> Result, EncodingError> { + let bytes = serde_json::to_vec(data)?; + Ok(bytes) + } + + fn decode Deserialize<'de>>(bytes: &[u8]) -> Result { + let decoded = serde_json::from_slice(bytes)?; + Ok(decoded) + } +} + +/// Trait which types can implement to provide their own encoding +pub trait Encode { + /// Encodes itself into a vector of bytes + /// + /// # Returns + /// + /// * `Result, EncodingError>` - encoded data or error + /// + /// # Errors + /// + /// * `EncodingError` - if encoding fails + fn encode(&self) -> Result, EncodingError>; +} + +/// Trait which types can implement to provide their own decoding +pub trait Decode { + type Output: for<'de> serde::Deserialize<'de>; + + /// Decodes itself from a vector of bytes + /// + /// # Arguments + /// + /// * `bytes` - bytes to decode + /// + /// # Returns + /// + /// * `Result` - decoded data or error + /// + /// # Errors + /// + /// * `DecodingError` - if decoding fails + fn decode(bytes: &[u8]) -> Result; +} + +#[cfg(test)] +mod test { + use crate::utilities::codec::EphemeraCodec; + + #[test] + fn test_encode_decode() { + let data = vec![1, 2, 3, 4, 5]; + let encoded = super::SerdeCodec::encode(&data).unwrap(); + let decoded = super::SerdeCodec::decode::>(&encoded).unwrap(); + assert_eq!(data, decoded); + } +} diff --git a/ephemera/src/utilities/codec/varint_async.rs b/ephemera/src/utilities/codec/varint_async.rs new file mode 100644 index 0000000000..d097ce95c9 --- /dev/null +++ b/ephemera/src/utilities/codec/varint_async.rs @@ -0,0 +1,100 @@ +use futures::{AsyncRead, AsyncWrite}; +use futures_util::{AsyncReadExt, AsyncWriteExt}; +use log::error; + +#[allow(clippy::cast_possible_truncation)] + +pub(crate) async fn write_length_prefixed, I: AsyncWrite + Unpin>( + io: &mut I, + data: D, +) -> Result<(), std::io::Error> { + write_varint(io, data.as_ref().len() as u32).await?; + io.write_all(data.as_ref()).await?; + io.flush().await?; + + Ok(()) +} + +async fn write_varint(io: &mut I, len: u32) -> Result<(), std::io::Error> { + let mut len_data = unsigned_varint::encode::u32_buffer(); + let encoded_len = unsigned_varint::encode::u32(len, &mut len_data).len(); + io.write_all(&len_data[..encoded_len]).await?; + + Ok(()) +} + +async fn read_varint(io: &mut I) -> Result { + let mut buffer = unsigned_varint::encode::u32_buffer(); + let mut buffer_len = 0; + + loop { + //read 1 byte at time because we don't know how it compacted 32 bit integer + io.read_exact(&mut buffer[buffer_len..=buffer_len]).await?; + buffer_len += 1; + match unsigned_varint::decode::u32(&buffer[..buffer_len]) { + Ok((len, _)) => { + return Ok(len); + } + Err(unsigned_varint::decode::Error::Overflow) => { + error!("Invalid varint received"); + return Err(std::io::Error::new( + std::io::ErrorKind::InvalidData, + "Invalid varint", + )); + } + Err(unsigned_varint::decode::Error::Insufficient) => { + continue; + } + Err(_) => { + error!("Varint decoding error: #[non_exhaustive]"); + return Err(std::io::Error::new( + std::io::ErrorKind::InvalidData, + "Invalid varint", + )); + } + } + } +} + +pub(crate) async fn read_length_prefixed( + io: &mut I, + max_size: u32, +) -> Result, std::io::Error> { + let len = read_varint(io).await?; + if len > max_size { + return Err(std::io::Error::new( + std::io::ErrorKind::InvalidData, + "Message too large", + )); + } + + let mut buf = vec![0; len as usize]; + io.read_exact(&mut buf).await?; + Ok(buf) +} + +#[cfg(test)] +mod test { + + use futures_util::io::Cursor; + + use super::*; + + #[tokio::test] + async fn test_read_write() { + let data = "hello world".to_string(); + + let mut buf = Vec::with_capacity(data.len() + 1); + let mut cursor = Cursor::new(&mut buf); + + write_length_prefixed(&mut cursor, data.as_bytes()) + .await + .unwrap(); + cursor.set_position(0); + + let vec = read_length_prefixed(&mut cursor, 100).await.unwrap(); + let result = String::from_utf8(vec).unwrap(); + + assert_eq!(result, data); + } +} diff --git a/ephemera/src/utilities/codec/varint_bytes.rs b/ephemera/src/utilities/codec/varint_bytes.rs new file mode 100644 index 0000000000..9a7d9d08ea --- /dev/null +++ b/ephemera/src/utilities/codec/varint_bytes.rs @@ -0,0 +1,85 @@ +use bytes::{Buf, BytesMut}; + +use thiserror::Error; +use unsigned_varint::{decode, encode}; + +#[derive(Debug, Error)] +pub(crate) enum VarintError { + #[error("InvalidInput: {0}")] + InvalidInput(String), + #[error("varint error: {0}")] + Varint(#[from] decode::Error), + #[error("TooLarge")] + TooLarge, +} + +#[allow(clippy::cast_possible_truncation)] + +pub(crate) fn write_length_prefixed>(dst: &mut BytesMut, data: D) { + write_varint(dst, data.as_ref().len() as u32); + dst.extend_from_slice(data.as_ref()); +} + +fn write_varint(dst: &mut BytesMut, len: u32) { + let mut len_data = encode::u32_buffer(); + let encoded_len = encode::u32(len, &mut len_data).len(); + dst.extend_from_slice(&len_data[..encoded_len]); +} + +pub(crate) fn read_length_prefixed( + bytes: &mut BytesMut, + max_size: u32, +) -> Result>, VarintError> { + let len = read_varint(bytes)?; + if len > max_size { + return Err(VarintError::TooLarge); + } + + if bytes.remaining() < len as usize { + return Ok(None); + } + + let vec = bytes.to_vec(); + bytes.advance(len as usize); + Ok(Some(vec)) +} + +fn read_varint(bytes: &mut BytesMut) -> Result { + let mut buffer = encode::u32_buffer(); + + for (i, byte) in bytes.iter().enumerate() { + buffer[i] = *byte; + match decode::u32(&buffer[..i]) { + Ok((len, _)) => { + bytes.advance(i); + return Ok(len); + } + Err(decode::Error::Insufficient) => continue, + Err(err) => Err(err)?, + } + } + + Err(VarintError::InvalidInput( + "Unable to read varint".to_string(), + )) +} + +#[cfg(test)] +mod test { + use bytes::BytesMut; + + use super::*; + + #[test] + fn test_read_write() { + let data = "hello world".to_string(); + + let mut encoded = BytesMut::with_capacity(0); + write_length_prefixed(&mut encoded, data.clone()); + let vec = read_length_prefixed(&mut encoded, 100).unwrap().unwrap(); + let result = String::from_utf8(vec).unwrap(); + + assert_eq!(result, data); + assert_eq!(encoded.remaining(), 0); + } +} diff --git a/ephemera/src/utilities/crypto/ed25519.rs b/ephemera/src/utilities/crypto/ed25519.rs new file mode 100644 index 0000000000..9b92304c2d --- /dev/null +++ b/ephemera/src/utilities/crypto/ed25519.rs @@ -0,0 +1,186 @@ +use std::fmt::Display; +use std::str::FromStr; + +use serde::{Deserialize, Serialize}; + +use crate::crypto::EphemeraKeypair; +use crate::peer::{PeerId, ToPeerId}; +use crate::utilities::crypto::keypair::KeyPairError; +use crate::utilities::crypto::{EphemeraPublicKey, Signature}; + +// Internally uses libp2p Keypair for now +pub struct Keypair(pub(crate) libp2p::identity::Keypair); + +#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)] +pub struct PublicKey(libp2p::identity::PublicKey); + +impl PublicKey { + pub(crate) fn inner(&self) -> &libp2p::identity::PublicKey { + &self.0 + } + + pub(crate) fn to_bytes(&self) -> Vec { + self.0.encode_protobuf() + } +} + +impl Keypair { + pub(crate) fn inner(&self) -> &libp2p::identity::Keypair { + &self.0 + } +} + +impl Display for Keypair { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, + "Public key: {}, Secret key: .........", + self.public_key().to_base58() + ) + } +} + +impl Serialize for PublicKey { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + serializer.serialize_str(&self.to_base58()) + } +} + +impl<'de> Deserialize<'de> for PublicKey { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + let s = String::deserialize(deserializer)?; + PublicKey::from_base58(&s).map_err(serde::de::Error::custom) + } +} + +impl Display for PublicKey { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.to_base58()) + } +} + +impl FromStr for PublicKey { + type Err = KeyPairError; + + fn from_str(s: &str) -> Result { + PublicKey::from_base58(s) + } +} + +/// A wrapper around the libp2p Keypair type. +/// libp2p internally supports different key types, we only use Ed25519. +impl EphemeraKeypair for Keypair { + type Signature = Signature; + type PublicKey = PublicKey; + + fn generate(_seed: Option>) -> Self { + let keypair = libp2p::identity::Keypair::generate_ed25519(); + Keypair(keypair) + } + + fn sign>(&self, msg: &M) -> Result { + self.inner() + .sign(msg.as_ref()) + .map_err(|err| KeyPairError::Signing(err.to_string())) + .map(Signature) + } + + fn verify>(&self, msg: &M, signature: &Self::Signature) -> bool { + self.0.public().verify(msg.as_ref(), signature.as_ref()) + } + + fn to_bytes(&self) -> Vec { + self.inner().to_protobuf_encoding().unwrap() + } + + fn from_bytes(raw: &[u8]) -> Result + where + Self: Sized, + { + let keypair = libp2p::identity::Keypair::from_protobuf_encoding(raw) + .map_err(|err| KeyPairError::Decoding(err.to_string()))?; + Ok(Keypair(keypair)) + } + + fn public_key(&self) -> Self::PublicKey { + PublicKey(self.0.public()) + } +} + +impl EphemeraPublicKey for PublicKey { + type Signature = Signature; + + fn to_bytes(&self) -> Vec { + self.0.encode_protobuf() + } + + fn from_bytes(raw: &[u8]) -> Result + where + Self: Sized, + { + let public_key = libp2p::identity::PublicKey::try_decode_protobuf(raw) + .map_err(|err| KeyPairError::Decoding(err.to_string()))?; + Ok(PublicKey(public_key)) + } + + fn verify>(&self, msg: &M, signature: &Self::Signature) -> bool { + self.0.verify(msg.as_ref(), signature.as_ref()) + } +} + +impl ToPeerId for Keypair { + fn peer_id(&self) -> PeerId { + PeerId(self.0.public().to_peer_id()) + } +} + +impl ToPeerId for PublicKey { + fn peer_id(&self) -> PeerId { + PeerId(self.0.to_peer_id()) + } +} + +#[cfg(test)] +mod test { + use super::*; + use crate::crypto::EphemeraKeypair; + use crate::peer::ToPeerId; + use crate::utilities::crypto::keypair::KeyPairError; + use crate::utilities::crypto::EphemeraPublicKey; + use assert_matches::assert_matches; + use std::str::FromStr; + + #[test] + fn test_keypair() { + let keypair = Keypair::generate(None); + let public_key = keypair.public_key(); + let peer_id = keypair.peer_id(); + let peer_id_from_public_key = public_key.peer_id(); + assert_eq!(peer_id, peer_id_from_public_key); + + let msg = "Message to sign"; + let signature = keypair.sign(&msg).unwrap(); + assert!(public_key.verify(&msg, &signature)); + assert!(keypair.verify(&msg, &signature)); + + let initial = keypair.to_bytes(); + let parsed = Keypair::from_bytes(&initial).unwrap(); + assert_eq!(initial, parsed.to_bytes()); + + let initial = public_key.to_bytes(); + let parsed = PublicKey::from_bytes(&initial).unwrap(); + assert_eq!(initial, parsed.to_bytes()); + + let public_key_from_str = PublicKey::from_str(&public_key.to_base58()).unwrap(); + assert_eq!(public_key, public_key_from_str); + + let public_key_from_str = PublicKey::from_str(&keypair.to_base58()); + assert_matches!(public_key_from_str, Err(KeyPairError::Decoding(_))); + } +} diff --git a/ephemera/src/utilities/crypto/key_manager.rs b/ephemera/src/utilities/crypto/key_manager.rs new file mode 100644 index 0000000000..b27cb9020d --- /dev/null +++ b/ephemera/src/utilities/crypto/key_manager.rs @@ -0,0 +1,14 @@ +use std::sync::Arc; + +use crate::crypto::{EphemeraKeypair, Keypair}; + +pub(crate) struct KeyManager; + +impl KeyManager { + pub(crate) fn read_keypair_from_str(private_key: &str) -> anyhow::Result> { + let keypair: Arc = Keypair::from_base58(private_key) + .map_err(|e| anyhow::anyhow!("Failed to parse private key from config. Error: {e:?}"))? + .into(); + Ok(keypair) + } +} diff --git a/ephemera/src/utilities/crypto/keypair.rs b/ephemera/src/utilities/crypto/keypair.rs new file mode 100644 index 0000000000..fa609ae7e4 --- /dev/null +++ b/ephemera/src/utilities/crypto/keypair.rs @@ -0,0 +1,158 @@ +use thiserror::Error; + +#[derive(Error, Debug, PartialEq)] +pub enum KeyPairError { + #[error("Failed to encode: {0}")] + Encoding(String), + #[error("Failed to decode: {0}")] + Decoding(String), + #[error("Invalid signature")] + Signature(String), + #[error("Signing failed: {0}")] + Signing(String), +} + +pub trait EphemeraPublicKey { + type Signature: AsRef<[u8]>; + + /// Returns raw bytes of the public key + /// + /// # Returns + /// * `Vec` - raw bytes of the public key + fn to_bytes(&self) -> Vec; + + /// Parses public key from raw bytes + /// + /// # Arguments + /// * `raw` - raw bytes of the public key + /// + /// # Returns + /// * `Result` - public key or error + /// + /// # Errors + /// * `KeyPairError::Decoding` - if bytes are not valid + fn from_bytes(bytes: &[u8]) -> Result + where + Self: Sized; + + /// Verifies the signature of the message using the public key + /// + /// # Arguments + /// * `msg` - message to verify + /// * `signature` - signature of the message + /// + /// # Returns + /// * `bool` - true if the signature is valid, false otherwise + fn verify>(&self, msg: &M, signature: &Self::Signature) -> bool; + + /// Returns base58 encoded public key + /// + /// # Returns + /// * `String` - base58 encoded public key + fn to_base58(&self) -> String { + bs58::encode(self.to_bytes()).into_string() + } + + /// Parses public key from base58 encoded string + /// + /// # Arguments + /// * `base58` - base58 encoded public key + /// + /// # Returns + /// * `Result` - public key or error + /// + /// # Errors + /// * `KeyPairError::Decoding` - if bytes are not valid + fn from_base58(base58: &str) -> Result + where + Self: Sized, + { + let raw = bs58::decode(base58) + .into_vec() + .map_err(|err| KeyPairError::Decoding(err.to_string()))?; + Self::from_bytes(&raw) + } +} + +#[allow(clippy::module_name_repetitions)] +pub trait EphemeraKeypair { + type Signature; + type PublicKey; + + /// Generates a new keypair. Depending on the implementation, the seed may be used to + /// add entropy to the key generation process. + fn generate(seed: Option>) -> Self; + + /// Signs a message with the private key + /// + /// # Arguments + /// * `msg` - message to sign + /// + /// # Returns + /// * `Result` - signature or error + /// + /// # Errors + /// * `KeyPairError::Signing` - if the message cannot be encoded + fn sign>(&self, msg: &M) -> Result; + + /// Verifies the signature of the message using the related public key + /// + /// # Arguments + /// * `msg` - message to verify + /// * `signature` - signature of the message + /// + /// # Returns + /// * `bool` - true if the signature is valid, false otherwise + fn verify>(&self, msg: &M, signature: &Self::Signature) -> bool; + + /// Returns raw bytes of the keypair + /// + /// # Returns + /// * `Vec` - raw bytes of the keypair + fn to_bytes(&self) -> Vec; + + /// Parses keypair from bytes + /// + /// # Arguments + /// * `raw` - raw bytes of the keypair + /// + /// # Returns + /// * `Result` - keypair or error + /// + /// # Errors + /// * `KeyPairError::Decoding` - if bytes are not valid + fn from_bytes(raw: &[u8]) -> Result + where + Self: Sized; + + /// Returns related public key of the keypair + fn public_key(&self) -> Self::PublicKey; + + /// Returns base58 encoded keypair + /// + /// # Returns + /// * `String` - base58 encoded keypair + fn to_base58(&self) -> String { + bs58::encode(self.to_bytes()).into_string() + } + + /// Parses keypair from base58 encoded string + /// + /// # Arguments + /// * `base58` - base58 encoded keypair + /// + /// # Returns + /// * `Result` - keypair or error + /// + /// # Errors + /// * `KeyPairError::Decoding` - if bytes are not valid base58 encoded string + fn from_base58(base58: &str) -> Result + where + Self: Sized, + { + let raw = bs58::decode(base58) + .into_vec() + .map_err(|err| KeyPairError::Decoding(err.to_string()))?; + Self::from_bytes(&raw) + } +} diff --git a/ephemera/src/utilities/crypto/mod.rs b/ephemera/src/utilities/crypto/mod.rs new file mode 100644 index 0000000000..f95f26342b --- /dev/null +++ b/ephemera/src/utilities/crypto/mod.rs @@ -0,0 +1,137 @@ +use std::fmt::{Debug, Display}; +use std::hash::Hash; + +use serde::{Deserialize, Serialize}; + +pub use ed25519::{Keypair as Ed25519Keypair, PublicKey as Ed25519PublicKey}; +pub use keypair::{EphemeraKeypair, EphemeraPublicKey, KeyPairError}; + +use crate::codec::Encode; +use crate::utilities::codec::{Codec, EncodingError, EphemeraCodec}; + +pub mod ed25519; +pub mod key_manager; +mod keypair; + +pub type Keypair = Ed25519Keypair; +pub type PublicKey = Ed25519PublicKey; + +#[derive(Clone, PartialEq, Hash, Eq, PartialOrd, Ord, Deserialize, Serialize)] +pub struct Signature(Vec); + +impl Signature { + pub fn new(signature: Vec) -> Self { + Self(signature) + } + + pub fn to_bytes(&self) -> Vec { + self.0.clone() + } + + pub fn as_bytes(&self) -> &[u8] { + &self.0 + } + + pub fn inner(&self) -> &[u8] { + &self.0 + } + + pub fn to_base58(&self) -> String { + bs58::encode(self.0.clone()).into_string() + } +} + +impl Debug for Signature { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.to_base58()) + } +} + +impl Display for Signature { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.to_base58()) + } +} + +#[derive(Debug, Clone, PartialEq, Hash, Eq, PartialOrd, Ord, Deserialize, Serialize)] +pub(crate) struct Certificate { + pub(crate) signature: Signature, + pub(crate) public_key: PublicKey, +} + +impl Certificate { + pub fn prepare(key_pair: &Keypair, data: &D) -> anyhow::Result { + let data_bytes = data.encode()?; + let signature = key_pair.sign(&data_bytes)?; + let public_key = key_pair.public_key(); + Ok(Self::new(signature, public_key)) + } +} + +impl AsRef<[u8]> for Signature { + fn as_ref(&self) -> &[u8] { + self.as_bytes() + } +} + +impl From> for Signature { + fn from(signature: Vec) -> Self { + Self::new(signature) + } +} + +impl Certificate { + pub(crate) fn new(signature: Signature, public_key: PublicKey) -> Self { + Self { + signature, + public_key, + } + } + + pub(crate) fn verify(&self, data: &D) -> anyhow::Result { + let data_bytes = data.encode()?; + let valid = self.public_key.verify(&data_bytes, &self.signature); + Ok(valid) + } +} + +impl Encode for Certificate { + fn encode(&self) -> Result, EncodingError> { + let mut result = Codec::encode(&self.signature)?; + result.extend_from_slice(&self.public_key.encode()?); + Ok(result) + } +} + +impl Encode for PublicKey { + fn encode(&self) -> Result, EncodingError> { + Ok(self.to_bytes()) + } +} + +impl Display for Certificate { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, + "Signature {}, PublicKey {}", + self.signature.to_base58(), + self.public_key.to_base58() + ) + } +} + +#[cfg(test)] +mod test { + + use crate::crypto::{EphemeraKeypair, EphemeraPublicKey}; + + #[test] + fn test_sign_and_verify() { + let keypair = super::Keypair::generate(None); + let data = "Secret data"; + let signature = keypair.sign(&data.as_bytes()).unwrap(); + let public_key = keypair.public_key(); + let valid = public_key.verify(&data.as_bytes(), &signature); + assert!(valid); + } +} diff --git a/ephemera/src/utilities/hash/mod.rs b/ephemera/src/utilities/hash/mod.rs new file mode 100644 index 0000000000..e95a975da4 --- /dev/null +++ b/ephemera/src/utilities/hash/mod.rs @@ -0,0 +1,109 @@ +use std::fmt::{Debug, Display}; + +use std::str::FromStr; + +use blake2::{Blake2b, Digest}; +use digest::consts::U32; +use serde::{Deserialize, Serialize}; + +pub type Hasher = Blake2bHasher; + +#[derive(Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Deserialize, Serialize)] +pub struct Hash([u8; 32]); + +impl Hash { + pub fn new(hash: [u8; 32]) -> Self { + Self(hash) + } + + pub fn inner(&self) -> [u8; 32] { + self.0 + } + + pub(crate) fn base58(&self) -> String { + bs58::encode(self.0).into_string() + } +} + +impl FromStr for Hash { + type Err = bs58::decode::Error; + + fn from_str(s: &str) -> Result { + let bytes = bs58::decode(s).into_vec()?; + let mut hash = [0u8; 32]; + hash.copy_from_slice(&bytes); + Ok(Self(hash)) + } +} + +impl Debug for Hash { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{:?}", self.base58()) + } +} + +impl Display for Hash { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.base58()) + } +} + +impl From<[u8; 32]> for Hash { + fn from(hash: [u8; 32]) -> Self { + Self(hash) + } +} + +/// A trait for hashing data. +pub(crate) trait EphemeraHasher: Default { + /// Hashes the given data. + fn digest(data: &[u8]) -> [u8; 32]; + + /// Updates the hasher with the given data. + fn update(&mut self, bytes: &[u8]); + + /// Finalizes the hasher and returns the hash. + fn finish(&mut self) -> [u8; 32]; +} + +#[derive(Default)] +pub struct Blake2bHasher { + hasher: Blake2b, +} + +impl EphemeraHasher for Blake2bHasher { + fn digest(data: &[u8]) -> [u8; 32] { + type Blake2b256 = blake2::Blake2b; + let mut dest = [0; 32]; + dest.copy_from_slice(Blake2b256::digest(data).as_slice()); + dest + } + + fn update(&mut self, bytes: &[u8]) { + self.hasher.update(bytes); + } + + fn finish(&mut self) -> [u8; 32] { + self.hasher.finalize_reset().into() + } +} + +pub(crate) trait EphemeraHash { + fn hash(&self, state: &mut H) -> anyhow::Result<()>; +} + +#[cfg(test)] +mod test { + use super::*; + use rand::RngCore; + + #[test] + fn to_base58_parse() { + let mut bytes = [0u8; 32]; + rand::thread_rng().fill_bytes(&mut bytes); + let hash: Hash = bytes.into(); + let base58 = hash.base58(); + let hash2 = base58.parse::().unwrap(); + assert_eq!(hash, hash2); + } +} diff --git a/ephemera/src/utilities/id/mod.rs b/ephemera/src/utilities/id/mod.rs new file mode 100644 index 0000000000..5548f6070a --- /dev/null +++ b/ephemera/src/utilities/id/mod.rs @@ -0,0 +1,94 @@ +use std::fmt::{Debug, Display}; +use std::str::FromStr; + +use crate::utilities::hash::{EphemeraHash, EphemeraHasher}; +use serde::{Deserialize, Serialize}; +use uuid::Uuid; + +pub(crate) type EphemeraId = UuidEphemeraIdentifier; + +pub(crate) trait EphemeraIdentifier: ToString { + type Identifier; + + fn generate() -> Self; + + fn inner(&self) -> &Self::Identifier; + + fn into_inner(self) -> Self::Identifier; + + fn as_bytes(&self) -> &[u8]; +} + +#[derive(Clone, PartialEq, PartialOrd, Ord, Eq, Hash, Deserialize, Serialize)] +pub struct UuidEphemeraIdentifier { + identifier: String, +} + +impl FromStr for UuidEphemeraIdentifier { + type Err = uuid::Error; + + fn from_str(s: &str) -> Result { + Ok(UuidEphemeraIdentifier { + identifier: Uuid::parse_str(s)?.to_string(), + }) + } +} + +impl Display for UuidEphemeraIdentifier { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.identifier) + } +} + +impl Debug for UuidEphemeraIdentifier { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.identifier) + } +} + +impl Default for UuidEphemeraIdentifier { + fn default() -> Self { + UuidEphemeraIdentifier { + identifier: Uuid::new_v4().to_string(), + } + } +} + +impl EphemeraHash for UuidEphemeraIdentifier { + fn hash(&self, state: &mut H) -> anyhow::Result<()> { + state.update(self.as_bytes()); + Ok(()) + } +} + +impl EphemeraIdentifier for UuidEphemeraIdentifier { + type Identifier = String; + + fn generate() -> Self { + UuidEphemeraIdentifier::default() + } + + fn inner(&self) -> &Self::Identifier { + &self.identifier + } + + fn into_inner(self) -> Self::Identifier { + self.identifier + } + + fn as_bytes(&self) -> &[u8] { + self.identifier.as_bytes() + } +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn test_generate_parse() { + let id = UuidEphemeraIdentifier::generate(); + let id2 = UuidEphemeraIdentifier::from_str(&id.to_string()).unwrap(); + assert_eq!(id, id2); + } +} diff --git a/ephemera/src/utilities/merkle/mod.rs b/ephemera/src/utilities/merkle/mod.rs new file mode 100644 index 0000000000..1f0f866539 --- /dev/null +++ b/ephemera/src/utilities/merkle/mod.rs @@ -0,0 +1,162 @@ +use serde::{Deserialize, Serialize}; + +use crate::utilities::hash::{EphemeraHasher, Hash, Hasher}; + +#[allow(clippy::module_name_repetitions)] +#[derive(Debug, Serialize, Deserialize)] +pub struct MerkleTree { + leaf_count: usize, + nodes: Vec, +} + +impl MerkleTree { + pub(crate) fn build_tree(leaves: &[Hash]) -> Self { + if leaves.is_empty() { + return Self { + leaf_count: 1, + //So every message which matches this hash will be accepted + //Not sure if it's a problem + nodes: vec![Hash::new([0; 32])], + }; + } + + let leaf_count = leaves.len(); + let mut nodes = Vec::with_capacity(leaf_count * 2); + nodes.extend_from_slice(leaves); + + let mut prev_level_len = leaf_count; + let mut prev_offset = 0; + let mut current_offset = leaf_count; + + while prev_level_len > 1 { + let current_level_len = (prev_level_len + 1) / 2; + + for i in 0..current_level_len { + let prev_index = i * 2; + let left = nodes[prev_offset + prev_index]; + let right = if prev_index + 1 < prev_level_len { + nodes[prev_offset + prev_index + 1] + } else { + nodes[prev_offset + prev_index] + }; + let hash = Hasher::digest(&[left.inner(), right.inner()].concat()).into(); + nodes.push(hash); + } + prev_level_len = current_level_len; + prev_offset = current_offset; + current_offset += current_level_len; + } + Self { leaf_count, nodes } + } + + pub(crate) fn root_hash(&self) -> Hash { + self.nodes[self.nodes.len() - 1] + } + + pub(crate) fn verify_leaf_at_index(&self, hash: Hash, leaf_index: usize) -> bool { + let mut level_offset = 0; + let mut level_len = self.leaf_count; + let mut leaf_index = leaf_index; + let mut current_hash = hash; + while level_offset + level_len < self.nodes.len() { + let level = &self.nodes[level_offset..(level_offset + level_len)]; + if leaf_index % 2 == 0 { + let right_index = if leaf_index + 1 < level_len { + leaf_index + 1 + } else { + leaf_index + }; + current_hash = + Hasher::digest(&[current_hash.inner(), level[right_index].inner()].concat()) + .into(); + } else { + current_hash = + Hasher::digest(&[level[leaf_index - 1].inner(), current_hash.inner()].concat()) + .into(); + } + leaf_index /= 2; + level_offset += level_len; + level_len = (level_len + 1) / 2; + } + current_hash == self.root_hash() + } +} + +#[cfg(test)] +mod tests { + use std::iter; + + use rand::RngCore; + + use crate::utilities::hash::Hash; + + use super::*; + + #[test] + fn test_merkle() { + //Technically testing one implementation against another... + for i in 1..10 { + let mut rnd = rand::thread_rng(); + let leaves = iter::repeat_with(|| { + let mut bytes = [0u8; 32]; + rnd.fill_bytes(&mut bytes); + Hash::new(bytes) + }) + .take(i) + .collect::>(); + + let mut level: Vec = leaves.clone(); + + while level.len() > 1 { + level = level + .chunks(2) + .map(|chunk| { + if chunk.len() == 1 { + Hasher::digest(&[chunk[0].inner(), chunk[0].inner()].concat()).into() + } else { + Hasher::digest(&[chunk[0].inner(), chunk[1].inner()].concat()).into() + } + }) + .collect(); + } + + let root: Hash = level[0]; + + let tree = MerkleTree::build_tree(&leaves); + assert_eq!(tree.root_hash(), root); + } + } + + #[test] + fn test_verify_leaf() { + for i in 0..10 { + let mut rnd = rand::thread_rng(); + let right_leaves = iter::repeat_with(|| { + let mut bytes = [0u8; 32]; + rnd.fill_bytes(&mut bytes); + Hash::new(bytes) + }) + .take(i) + .collect::>(); + + let wrong_leaves = iter::repeat_with(|| { + let mut bytes = [0u8; 32]; + rnd.fill_bytes(&mut bytes); + Hash::new(bytes) + }) + .take(i) + .collect::>(); + + let tree = MerkleTree::build_tree(&right_leaves); + + for (i, (correct, wrong)) in right_leaves + .into_iter() + .zip(wrong_leaves.into_iter()) + .enumerate() + { + assert!(tree.verify_leaf_at_index(correct, i)); + assert!(!tree.verify_leaf_at_index(wrong, i)); + } + } + } +} diff --git a/ephemera/src/utilities/mod.rs b/ephemera/src/utilities/mod.rs new file mode 100644 index 0000000000..724cdba514 --- /dev/null +++ b/ephemera/src/utilities/mod.rs @@ -0,0 +1,6 @@ +pub(crate) mod codec; +pub(crate) mod crypto; +pub(crate) mod hash; +pub(crate) mod id; +pub(crate) mod merkle; +pub(crate) mod time; diff --git a/ephemera/src/utilities/time/mod.rs b/ephemera/src/utilities/time/mod.rs new file mode 100644 index 0000000000..0b3f2091c0 --- /dev/null +++ b/ephemera/src/utilities/time/mod.rs @@ -0,0 +1,11 @@ +use chrono::Utc; + +#[allow(clippy::module_name_repetitions)] +pub struct EphemeraTime; + +impl EphemeraTime { + #[allow(clippy::cast_sign_loss)] + pub fn now() -> u64 { + Utc::now().timestamp_millis() as u64 + } +} diff --git a/ephemera/src/websocket/mod.rs b/ephemera/src/websocket/mod.rs new file mode 100644 index 0000000000..73a138f3cb --- /dev/null +++ b/ephemera/src/websocket/mod.rs @@ -0,0 +1 @@ +pub(crate) mod ws_manager; diff --git a/ephemera/src/websocket/ws_manager.rs b/ephemera/src/websocket/ws_manager.rs new file mode 100644 index 0000000000..a7e837a2d4 --- /dev/null +++ b/ephemera/src/websocket/ws_manager.rs @@ -0,0 +1,138 @@ +use std::net::SocketAddr; + +use anyhow::Result; +use futures_util::SinkExt; +use log::{debug, error, info}; +use tokio::sync::broadcast::error::RecvError; +use tokio::{ + net::{TcpListener, TcpStream}, + sync::broadcast, +}; +use tokio_tungstenite::{tungstenite::Message, WebSocketStream}; + +use crate::api::types::ApiBlock; +use crate::block::types::block::Block; + +pub struct WsConnection { + socket: WebSocketStream, + pending_messages_rx: broadcast::Receiver, + address: SocketAddr, +} + +impl WsConnection { + pub fn new( + socket: WebSocketStream, + pending_messages_rx: broadcast::Receiver, + address: SocketAddr, + ) -> WsConnection { + WsConnection { + socket, + pending_messages_rx, + address, + } + } + + pub async fn accept_messages(mut self) { + loop { + match self.pending_messages_rx.recv().await { + Ok(msg) => { + debug!("Sending message to {}", self.address); + if let Err(err) = self.socket.send(msg).await { + error!("Error sending message to websocket client: {:?}", err); + } + } + Err(e) => { + if RecvError::Closed == e { + error!("Error while receiving message from broadcast channel: {:?}, closing connection", e); + break; + } + } + } + } + } +} + +#[derive(Clone)] +pub(crate) struct WsMessageBroadcaster { + pub(crate) pending_messages_tx: broadcast::Sender, +} + +impl WsMessageBroadcaster { + pub(crate) fn new(pending_messages_tx: broadcast::Sender) -> WsMessageBroadcaster { + WsMessageBroadcaster { + pending_messages_tx, + } + } + + pub(crate) fn send_block(&self, block: &Block) -> Result<()> { + debug!("Sending block {} to websocket clients", block.header.hash); + let json = serde_json::to_string::(block.into())?; + let msg = Message::Text(json); + self.pending_messages_tx.send(msg)?; + Ok(()) + } +} + +pub(crate) struct WsManager { + pub(crate) listener: Option, + pub(crate) ws_address: String, + pub(crate) pending_messages_tx: broadcast::Sender, + _pending_messages_rcv: broadcast::Receiver, +} + +impl WsManager { + #[allow(clippy::used_underscore_binding)] + pub(crate) fn new(address: String) -> (WsManager, WsMessageBroadcaster) { + let (pending_messages_tx, _pending_messages_rcv) = broadcast::channel(1000); + let ws_message_broadcast = WsMessageBroadcaster::new(pending_messages_tx.clone()); + let manager = WsManager { + listener: None, + ws_address: address, + pending_messages_tx, + _pending_messages_rcv, + }; + (manager, ws_message_broadcast) + } + + pub(crate) async fn listen(&mut self) -> Result<()> { + let listener = TcpListener::bind(&self.ws_address).await?; + info!("Listening for websocket connections on {}", self.ws_address); + self.listener = Some(listener); + Ok(()) + } + + pub async fn run(mut self) -> Result<()> { + let listener = self.listener.take().expect("Listener not set"); + loop { + tokio::select! { + res = listener.accept() => { + match res { + Ok((stream, addr)) => { + debug!("Accepted websocket connection from: {}", addr); + self.handle_connection(stream, addr); + } + Err(err) => { + return Err(err.into()); + } + } + } + } + } + } + + pub fn handle_connection(&self, stream: TcpStream, addr: SocketAddr) { + let pending_messages_rx = self.pending_messages_tx.subscribe(); + tokio::spawn(async move { + match tokio_tungstenite::accept_async(stream).await { + Ok(ws_stream) => { + let connection = WsConnection::new(ws_stream, pending_messages_rx, addr); + connection.accept_messages().await; + } + Err(err) => { + error!("Error accepting websocket connection: {:?}", err); + } + } + debug!("Websocket connection closed"); + }); + } +} diff --git a/nym-api/Cargo.toml b/nym-api/Cargo.toml index 5e7a9488d1..cc55f92afc 100644 --- a/nym-api/Cargo.toml +++ b/nym-api/Cargo.toml @@ -64,11 +64,22 @@ rocket_okapi = { version = "0.8.0-rc.2", features = ["swagger"] } schemars = { version = "0.8", features = ["preserve_order"] } zeroize = { workspace = true } +## ephemera-specific +actix-web = "4" +array-bytes = "6.0.0" +chrono = { version = "0.4.24", default-features = false, features = ["clock"] } +futures-util = "0.3.25" +serde_derive = "1.0.149" +tempfile = "3.3.0" +uuid = { version = "1.3.0", features = ["serde", "v4"] } + ## internal +ephemera = { path = "../ephemera" } nym-bandwidth-controller = { path = "../common/bandwidth-controller" } nym-coconut-bandwidth-contract-common = { path = "../common/cosmwasm-smart-contracts/coconut-bandwidth-contract" } nym-coconut-dkg-common = { path = "../common/cosmwasm-smart-contracts/coconut-dkg" } nym-coconut-interface = { path = "../common/coconut-interface" } +nym-ephemera-common = { path = "../common/cosmwasm-smart-contracts/ephemera" } nym-config = { path = "../common/config" } cosmwasm-std = { workspace = true } nym-credential-storage = { path = "../common/credential-storage" } diff --git a/nym-api/ephemera_migrations/contract/V1__contract.sql b/nym-api/ephemera_migrations/contract/V1__contract.sql new file mode 100644 index 0000000000..947c316940 --- /dev/null +++ b/nym-api/ephemera_migrations/contract/V1__contract.sql @@ -0,0 +1,19 @@ +CREATE TABLE contract_mixnode_reward +( + id INTEGER PRIMARY KEY AUTOINCREMENT, + mix_id INTEGER NOT NULL, + epoch INTEGER NOT NULL, + nym_api_id INTEGER NOT NULL, + reliability INTEGER NOT NULL, + timestamp INTEGER NOT NULL, + UNIQUE (mix_id, epoch) +); + +CREATE TABLE epoch_info +( + id INTEGER PRIMARY KEY AUTOINCREMENT, + epoch_id INTEGER NOT NULL, + start_time INTEGER NOT NULL, + duration INTEGER NOT NULL, + UNIQUE (epoch_id) +); \ No newline at end of file diff --git a/nym-api/ephemera_migrations/rewardsnew/V1__metrics.sql b/nym-api/ephemera_migrations/rewardsnew/V1__metrics.sql new file mode 100644 index 0000000000..9f5e0e2f04 --- /dev/null +++ b/nym-api/ephemera_migrations/rewardsnew/V1__metrics.sql @@ -0,0 +1,23 @@ +CREATE TABLE mixnode_status +( + mix_id INTEGER NOT NULL, + reliability INTEGER NOT NULL, + timestamp INTEGER NOT NULL, + UNIQUE (mix_id, timestamp) +); + +CREATE TABLE rewarding_report +( + epoch_id INTEGER NOT NULL, + eligible_mixnodes INTEGER NOT NULL, + timestamp INTEGER NOT NULL, + UNIQUE (epoch_id) +); + +CREATE TABLE epoch_blocks +( + epoch_id INTEGER NOT NULL, + block_id INTEGER NOT NULL, + timestamp INTEGER NOT NULL, + UNIQUE (epoch_id, block_id) +); \ No newline at end of file diff --git a/nym-api/ephemera_migrations/rewardsold/V1__metrics.sql b/nym-api/ephemera_migrations/rewardsold/V1__metrics.sql new file mode 100644 index 0000000000..3d5a66c4b8 --- /dev/null +++ b/nym-api/ephemera_migrations/rewardsold/V1__metrics.sql @@ -0,0 +1,6 @@ +CREATE TABLE mixnode_status +( + mix_id INTEGER NOT NULL, + reliability INTEGER NOT NULL, + timestamp INTEGER NOT NULL +); \ No newline at end of file diff --git a/nym-api/src/ephemera/application.rs b/nym-api/src/ephemera/application.rs new file mode 100644 index 0000000000..06c1c016d8 --- /dev/null +++ b/nym-api/src/ephemera/application.rs @@ -0,0 +1,209 @@ +use ephemera::{ + configuration::Configuration, + ephemera_api::{ + ApiBlock, ApiEphemeraMessage, Application, ApplicationResult, CheckBlockResult, + RemoveMessages, + }, +}; +use log::{debug, error, info}; + +use crate::ephemera::client::Client; +use crate::ephemera::epoch::Epoch; +use crate::ephemera::peers::members::MembersProvider; +use crate::ephemera::peers::{NymApiEphemeraPeerInfo, NymPeer}; +use crate::ephemera::reward::aggregator::RewardsAggregator; +use crate::ephemera::reward::{EphemeraAccess, RewardManager}; +use crate::ephemera::Args; +use crate::epoch_operations::MixnodeWithPerformance; +use crate::support::nyxd; +use ephemera::crypto::{EphemeraKeypair, EphemeraPublicKey, Keypair}; +use ephemera::ephemera_api::CommandExecutor; +use ephemera::{Ephemera, EphemeraStarterInit}; +use nym_task::TaskManager; + +pub struct NymApi; + +impl NymApi { + pub(crate) async fn run( + args: Args, + ephemera_config: Configuration, + nyxd_client: nyxd::Client, + shutdown: &TaskManager, + ) -> anyhow::Result { + //KEYPAIR - Ephemera keypair or Validator keypair + //Can be a file, keystore etc + let key_pair = Self::read_nym_api_keypair(&ephemera_config)?; + + //EPHEMERA + let ephemera = Self::init_ephemera(ephemera_config, nyxd_client.clone()).await?; + let ephemera_handle = ephemera.handle(); + + //REWARDS + let rewards = + Self::create_rewards_manager(args, nyxd_client, key_pair, ephemera_handle.api.clone()) + .await?; + + //STARTING + let shutdown_listener = shutdown.subscribe(); + tokio::spawn(ephemera.run(shutdown_listener)); + + Ok(rewards) + } + + pub(crate) async fn init_ephemera( + ephemera_config: Configuration, + nyxd_client: nyxd::Client, + ) -> anyhow::Result> { + info!("Initializing ephemera ..."); + + let node_info = ephemera_config.node.clone(); + + let keypair = bs58::decode(&node_info.private_key).into_vec().unwrap(); + let keypair = Keypair::from_bytes(&keypair).unwrap(); + let local_peer_id = keypair.public_key().to_base58(); + + //Members provider for Ephemera + let members_provider = MembersProvider::new(nyxd_client.clone()); + + let cosmos_address = nyxd_client.client_address().await.to_string(); + let ip_address = format!( + "/ip4/{}/tcp/{}", + ephemera_config.node.ip, ephemera_config.libp2p.port + ); + + if !nyxd_client + .get_ephemera_peers() + .await? + .iter() + .any(|peer_info| peer_info.cosmos_address == cosmos_address) + { + let local_peer = NymPeer::new( + cosmos_address, + ip_address, + keypair.public_key(), + local_peer_id.clone(), + ); + members_provider.register_peer(local_peer).await?; + } + + //Application for Ephemera + let rewards_ephemera_application = + RewardsEphemeraApplication::init(local_peer_id, nyxd_client.clone()).await?; + + //EPHEMERA + let ephemera_builder = EphemeraStarterInit::new(ephemera_config)?; + let ephemera_builder = ephemera_builder.with_application(rewards_ephemera_application); + let ephemera_builder = ephemera_builder.with_members_provider(members_provider)?; + let ephemera = ephemera_builder.build(); + Ok(ephemera) + } + + async fn create_rewards_manager( + args: Args, + nyxd_client: nyxd::Client, + key_pair: Keypair, + ephemera_api: CommandExecutor, + ) -> anyhow::Result { + let epoch = Epoch::request_epoch(nyxd_client).await?; + Ok(RewardManager::new( + args.clone(), + EphemeraAccess::new(ephemera_api, key_pair).into(), + Some(RewardsAggregator), + epoch, + )) + } + + fn read_nym_api_keypair(ephemera_config: &Configuration) -> anyhow::Result { + let key_pair = bs58::decode(&ephemera_config.node.private_key).into_vec()?; + let key_pair = Keypair::from_bytes(&key_pair)?; + Ok(key_pair) + } +} + +pub(crate) struct RewardsEphemeraApplicationConfig { + /// Percentage of messages relative to total number of peers + pub(crate) peers_rewards_threshold: u64, +} + +pub(crate) struct RewardsEphemeraApplication { + peer_info: NymApiEphemeraPeerInfo, + app_config: RewardsEphemeraApplicationConfig, +} + +impl RewardsEphemeraApplication { + pub(crate) async fn init( + local_peer_id: String, + nyxd_client: nyxd::Client, + ) -> anyhow::Result { + let peer_info = match NymApiEphemeraPeerInfo::from_ephemera_dev_cluster_conf( + local_peer_id, + nyxd_client, + ) + .await + { + Ok(info) => info, + Err(err) => { + error!("Failed to load peers info: {}", err); + return Err(err); + } + }; + let app_config = RewardsEphemeraApplicationConfig { + peers_rewards_threshold: peer_info.get_peers_count() as u64, + }; + Ok(Self { + peer_info, + app_config, + }) + } +} + +/// - TODO: We should also check that the messages has expected label(like epoch 100) +/// because next block should have only reward info for correct epoch. +impl Application for RewardsEphemeraApplication { + /// Perform validation checks: + /// - Check that the transaction has a valid signature, we don't want to accept garbage messages + /// or messages from unknown peers + fn check_tx(&self, tx: ApiEphemeraMessage) -> ApplicationResult { + if serde_json::from_slice::>(&tx.data).is_err() { + error!("Message is not a valid Reward message"); + return Ok(false); + } + Ok(true) + + //TODO + //PS! message label should also be part of the message hash to prevent replay attacks + } + + /// Agree to accept the block if it contains threshold number of transactions + /// We trust that transactions are valid(checked by check_tx) + fn check_block(&self, block: &ApiBlock) -> ApplicationResult { + info!("Block message count: {}", block.message_count()); + + let block_threshold = ((block.message_count() as f64 + / self.peer_info.get_peers_count() as f64) + * 100.0) as u64; + + if block_threshold > 100 { + error!("Block threshold is greater than 100%!. We expected only single message from each peer"); + return Ok(CheckBlockResult::RejectAndRemoveMessages( + RemoveMessages::All, + )); + } + + if block_threshold >= self.app_config.peers_rewards_threshold { + info!( + "Block accepted {}:{}", + block.header.height, block.header.hash + ); + Ok(CheckBlockResult::Accept) + } else { + debug!("Block rejected: not enough messages"); + Ok(CheckBlockResult::Reject) + } + } + + /// It is possible to use this method as a callback to get notified when block is committed + fn deliver_block(&self, _block: ApiBlock) -> ApplicationResult<()> { + Ok(()) + } +} diff --git a/nym-api/src/ephemera/client.rs b/nym-api/src/ephemera/client.rs new file mode 100644 index 0000000000..146153759a --- /dev/null +++ b/nym-api/src/ephemera/client.rs @@ -0,0 +1,12 @@ +// Copyright 2023 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use crate::ephemera::error::Result; +use nym_ephemera_common::types::JsonPeerInfo; +use nym_validator_client::nyxd::cosmwasm_client::types::ExecuteResult; + +#[async_trait] +pub trait Client { + async fn get_ephemera_peers(&self) -> Result>; + async fn register_ephemera_peer(&self, peer_info: JsonPeerInfo) -> Result; +} diff --git a/nym-api/src/ephemera/epoch.rs b/nym-api/src/ephemera/epoch.rs new file mode 100644 index 0000000000..646dd451eb --- /dev/null +++ b/nym-api/src/ephemera/epoch.rs @@ -0,0 +1,82 @@ +use crate::support::nyxd; +use chrono::{DateTime, NaiveDateTime, Timelike, Utc}; +use log::info; +use nym_mixnet_contract_common::Interval as RewardsInterval; +use serde::{Deserialize, Serialize}; +use std::fmt::Display; +use tokio::time::Interval; + +//It synchronizes rewarding across simulated Nym-APIs and the "Smart Contract". +//Maintained by the "Smart Contract" +#[derive(Debug)] +pub struct Epoch { + pub start_time: DateTime, + pub interval: Interval, + pub epoch_start_id: u64, +} + +#[derive(Debug, Clone, Deserialize, Serialize)] +pub struct EpochInfo { + pub epoch_id: u64, + pub start_time: i64, + pub duration: u64, +} + +impl EpochInfo { + pub(crate) fn new(epoch_id: u64, start_time: i64, duration: u64) -> Self { + Self { + epoch_id, + start_time, + duration, + } + } +} + +impl From for Epoch { + fn from(value: RewardsInterval) -> Self { + Epoch::new(EpochInfo::new( + value.current_epoch_id() as u64, + value.current_epoch_start().unix_timestamp(), + value.epoch_length_secs(), + )) + } +} + +impl Epoch { + pub fn new(info: EpochInfo) -> Self { + info!("New epoch duration"); + let start_time = NaiveDateTime::from_timestamp_opt(info.start_time, 0) + .ok_or("Invalid start time") + .unwrap(); + let start_time: DateTime = DateTime::from_utc(start_time, Utc); + let interval = tokio::time::interval(std::time::Duration::from_secs(info.duration)); + Self { + start_time, + interval, + epoch_start_id: start_time.minute().into(), + } + } + + pub fn current_epoch_numer(&self) -> u64 { + let since_start = (Utc::now().timestamp() - self.start_time.timestamp()) as u64; + let nr = since_start / self.interval.period().as_secs(); + nr + self.epoch_start_id + } + + pub(crate) async fn request_epoch(nyxd_client: nyxd::Client) -> anyhow::Result { + let interval = nyxd_client.get_current_interval().await?.interval; + Ok(Epoch::from(interval)) + } +} + +impl Display for Epoch { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, + "Epoch {{ start_time: {}, interval: {:?}, epoch_start_id: {} }}", + self.start_time, + self.interval.period(), + self.epoch_start_id + ) + } +} diff --git a/nym-api/src/ephemera/error.rs b/nym-api/src/ephemera/error.rs new file mode 100644 index 0000000000..e8dd0b6ace --- /dev/null +++ b/nym-api/src/ephemera/error.rs @@ -0,0 +1,15 @@ +// Copyright 2023 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use thiserror::Error; + +pub type Result = std::result::Result; + +#[derive(Debug, Error)] +pub enum EphemeraError { + #[error("Validator client error - {0}")] + ValidatorClientError(#[from] nym_validator_client::ValidatorClientError), + + #[error("Nyxd error - {0}")] + NyxdError(#[from] nym_validator_client::nyxd::error::NyxdError), +} diff --git a/nym-api/src/ephemera/metrics/mod.rs b/nym-api/src/ephemera/metrics/mod.rs new file mode 100644 index 0000000000..cd408564ea --- /dev/null +++ b/nym-api/src/ephemera/metrics/mod.rs @@ -0,0 +1 @@ +pub mod types; diff --git a/nym-api/src/ephemera/metrics/types.rs b/nym-api/src/ephemera/metrics/types.rs new file mode 100644 index 0000000000..eb549a2360 --- /dev/null +++ b/nym-api/src/ephemera/metrics/types.rs @@ -0,0 +1,9 @@ +#[derive(Debug)] +pub struct MixnodeResult { + pub mix_id: u32, + pub reliability: u8, +} + +// value in range 0-100 +#[derive(Clone, Copy, Debug, Default)] +pub struct Uptime(u8); diff --git a/nym-api/src/ephemera/mod.rs b/nym-api/src/ephemera/mod.rs new file mode 100644 index 0000000000..d6960fcba5 --- /dev/null +++ b/nym-api/src/ephemera/mod.rs @@ -0,0 +1,51 @@ +// Copyright 2023 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +extern crate core; + +use clap::Parser; +use ephemera::cli::init::Cmd; +use serde_derive::{Deserialize, Serialize}; +use std::path::PathBuf; + +pub(crate) mod application; +pub(crate) mod client; +pub(crate) mod epoch; +pub(crate) mod error; +pub(crate) mod metrics; +pub(crate) mod peers; +pub(crate) mod reward; + +#[derive(Parser, Debug, Clone, Eq, PartialEq, Serialize, Deserialize)] +pub struct Args { + #[clap(skip)] + pub ephemera_config: PathBuf, + #[command(flatten)] + #[serde(skip)] + pub cmd: Cmd, + #[clap(skip)] + #[serde(skip, default = "default_block_polling_interval_seconds")] + pub block_polling_interval_seconds: u64, + #[clap(skip)] + #[serde(skip, default = "default_block_polling_max_attempts")] + pub block_polling_max_attempts: u64, +} + +fn default_block_polling_interval_seconds() -> u64 { + 1 +} + +fn default_block_polling_max_attempts() -> u64 { + 60 +} + +impl Default for Args { + fn default() -> Self { + Args { + ephemera_config: Default::default(), + cmd: Default::default(), + block_polling_interval_seconds: default_block_polling_interval_seconds(), + block_polling_max_attempts: default_block_polling_max_attempts(), + } + } +} diff --git a/nym-api/src/ephemera/peers/members.rs b/nym-api/src/ephemera/peers/members.rs new file mode 100644 index 0000000000..c0ff8e286e --- /dev/null +++ b/nym-api/src/ephemera/peers/members.rs @@ -0,0 +1,108 @@ +// Copyright 2023 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use crate::ephemera::client::Client; +use crate::ephemera::peers::NymPeer; +use crate::support::nyxd; +use cosmwasm_std::Addr; +use ephemera::membership::{PeerInfo, ProviderError}; +use futures_util::future::BoxFuture; +use futures_util::FutureExt; +use nym_ephemera_common::types::JsonPeerInfo; +use std::future::Future; +use std::pin::Pin; +use std::task::Poll::Pending; +use std::task::{Context, Poll}; + +/// Future type which allows user to implement their own peers membership source mechanism. +pub type ProviderFut = BoxFuture<'static, Result, ProviderError>>; + +///[`ProviderFut`] that reads peers from a http endpoint. +/// +/// The endpoint must return a json array of [`JsonPeerInfo`]. +/// # Configuration example +/// ```json +/// [ +/// { +/// "name": "node1", +/// "address": "/ip4/", +/// "public_key": "4XTTMEghav9LZThm6opUaHrdGEEYUkrfkakVg4VAetetBZDWJ" +/// }, +/// { +/// "name": "node2", +/// "address": "/ip4/", +/// "public_key": "4XTTMFQt2tgNRmwRgEAaGQe2NXygsK6Vr3pkuBfYezhDfoVty" +/// } +/// ] +/// ``` +pub struct MembersProvider { + /// The url of the http endpoint. + nyxd_client: nyxd::Client, + fut: Option, +} + +impl MembersProvider { + #[must_use] + pub(crate) fn new(nyxd_client: nyxd::Client) -> Self { + Self { + nyxd_client, + fut: None, + } + } + + pub(crate) async fn register_peer( + &self, + peer_info: NymPeer, + ) -> crate::ephemera::error::Result<()> { + let json_peer_info = JsonPeerInfo::new( + Addr::unchecked(peer_info.cosmos_address), + peer_info.ip_address, + peer_info.public_key.to_string(), + ); + self.nyxd_client + .register_ephemera_peer(json_peer_info) + .await?; + Ok(()) + } + + async fn request_peers(nyxd_client: nyxd::Client) -> Result, ProviderError> { + let peers = nyxd_client + .get_ephemera_peers() + .await + .map_err(|e| ProviderError::GetPeers(e.to_string()))? + .into_iter() + .filter_map(|peer| PeerInfo::try_from(peer).ok()) + .collect(); + Ok(peers) + } +} + +impl Future for MembersProvider { + type Output = Result, ProviderError>; + + fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + match self.fut.take() { + None => { + self.fut = Some(Box::pin(MembersProvider::request_peers( + self.nyxd_client.clone(), + ))); + } + Some(mut fut) => { + let peers = match fut.poll_unpin(cx) { + Poll::Ready(Ok(peers)) => peers, + Poll::Ready(Err(err)) => { + error!("Failed to get peers: {err}"); + return Poll::Ready(Err(err)); + } + Pending => { + self.fut = Some(fut); + return Pending; + } + }; + + return Poll::Ready(Ok(peers)); + } + } + Pending + } +} diff --git a/nym-api/src/ephemera/peers/mod.rs b/nym-api/src/ephemera/peers/mod.rs new file mode 100644 index 0000000000..0706c51fde --- /dev/null +++ b/nym-api/src/ephemera/peers/mod.rs @@ -0,0 +1,78 @@ +use std::collections::HashMap; +use std::str::FromStr; + +use anyhow::anyhow; + +use crate::ephemera::client::Client; +use crate::support::nyxd; +use ephemera::crypto::PublicKey; + +pub(crate) type PeerId = String; + +pub mod members; + +#[derive(Debug, Clone)] +pub struct NymPeer { + pub cosmos_address: String, + pub ip_address: String, + pub public_key: PublicKey, + pub peer_id: PeerId, +} + +impl NymPeer { + pub(crate) fn new( + cosmos_address: String, + ip_address: String, + public_key: PublicKey, + peer_id: PeerId, + ) -> Self { + Self { + cosmos_address, + ip_address, + public_key, + peer_id, + } + } +} + +// Information about other Nym-Apis. +pub(crate) struct NymApiEphemeraPeerInfo { + pub(crate) _local_peer: NymPeer, + pub(crate) peers: HashMap, +} + +impl NymApiEphemeraPeerInfo { + fn new(_local_peer: NymPeer, peers: HashMap) -> Self { + Self { _local_peer, peers } + } + + pub(crate) fn get_peers_count(&self) -> usize { + self.peers.len() + } + + pub(crate) async fn from_ephemera_dev_cluster_conf( + local_peer_id: String, + nyxd_client: nyxd::Client, + ) -> anyhow::Result { + let mut peers = HashMap::new(); + for peer_info in nyxd_client.get_ephemera_peers().await? { + let public_key = PublicKey::from_str(&peer_info.public_key)?; + + let peer = NymPeer::new( + peer_info.cosmos_address.to_string(), + peer_info.ip_address, + public_key, + peer_info.public_key.clone(), + ); + + peers.insert(peer_info.public_key, peer); + } + + let local_peer = peers + .get(&local_peer_id) + .ok_or(anyhow!("Failed to get local peer"))? + .clone(); + let peer_info = NymApiEphemeraPeerInfo::new(local_peer, peers); + Ok(peer_info) + } +} diff --git a/nym-api/src/ephemera/reward/aggregator.rs b/nym-api/src/ephemera/reward/aggregator.rs new file mode 100644 index 0000000000..e83119c4f3 --- /dev/null +++ b/nym-api/src/ephemera/reward/aggregator.rs @@ -0,0 +1,42 @@ +use cosmwasm_std::Decimal; +use log::{info, trace}; +use nym_mixnet_contract_common::reward_params::Performance; +use std::collections::HashMap; + +use crate::epoch_operations::MixnodeWithPerformance; + +pub(crate) struct RewardsAggregator; + +impl RewardsAggregator { + //Simple mean average + pub(crate) fn aggregate( + &self, + all_rewards: Vec>, + ) -> anyhow::Result> { + let mut mix_rewards = HashMap::new(); + for api_rewards in all_rewards { + for mixnode in api_rewards { + mix_rewards + .entry(mixnode.mix_id) + .or_insert(vec![]) + .push(mixnode.performance); + } + } + trace!("Mix rewards by node: {:?}", mix_rewards); + + trace!("Calculating mean average for each node"); + let mut mean_avg = vec![]; + for (mix_id, rewards) in mix_rewards { + let sum: Decimal = rewards.iter().map(|r| r.value()).sum(); + let avg = sum / Decimal::from_ratio(rewards.len() as u128, 1u128); + let performance = Performance::new(avg)?; + mean_avg.push(MixnodeWithPerformance { + mix_id, + performance, + }); + } + info!("Mean average rewards: {:?}", mean_avg); + + Ok(mean_avg) + } +} diff --git a/nym-api/src/ephemera/reward/mod.rs b/nym-api/src/ephemera/reward/mod.rs new file mode 100644 index 0000000000..e4e815a9e4 --- /dev/null +++ b/nym-api/src/ephemera/reward/mod.rs @@ -0,0 +1,263 @@ +use async_trait::async_trait; +use log::{debug, info, trace}; +use std::time::Duration; + +use crate::epoch_operations::MixnodeWithPerformance; +use ephemera::{ + crypto::Keypair, + ephemera_api::{self, ApiBlock, ApiEphemeraMessage, ApiError, CommandExecutor}, +}; + +use super::epoch::Epoch; +use super::reward::aggregator::RewardsAggregator; +use super::Args; + +pub(crate) mod aggregator; + +pub struct EphemeraAccess { + pub(crate) api: CommandExecutor, + pub(crate) key_pair: Keypair, +} + +#[async_trait] +impl EpochOperations for RewardManager { + async fn perform_epoch_operations( + &mut self, + rewards: Vec, + ) -> anyhow::Result> { + //Submit our own rewards message. + //It will be included in the next block(ours and/or others) + self.send_rewards_to_ephemera(rewards).await?; + + //Assuming that next block includes our rewards message + //This assumptions need to be "configured" by the application. + let prev_block = self.get_last_block().await?; + let next_height = prev_block.header.height + 1; + + //Poll next block which should include all messages from the previous epoch from almost all Nym-Api nodes + let mut counter = 0; + info!( + "Waiting for block with height {next_height} maximum {} seconds", + self.args.block_polling_max_attempts * self.args.block_polling_interval_seconds + ); + loop { + if counter > self.args.block_polling_max_attempts { + error!("Block for height {next_height} is not available after {counter} attempts"); + break; + } + tokio::select! { + Ok(Some(block)) = self.get_block_by_height(next_height) => { + info!("Received local block with height {next_height}, hash:{:?}", block.header.hash); + if let Ok(agg_rewards) = self.try_aggregate_rewards(block.clone()).await{ + info!("Submitted rewards to smart contract"); + let epoch_id = self.epoch.current_epoch_numer(); + self.store_in_dht(epoch_id, &block).await?; + info!("Stored rewards in DHT"); + return Ok(agg_rewards); + } + break; + } + _ = tokio::time::sleep(Duration::from_secs(self.args.block_polling_interval_seconds)) => { + trace!("Block for height {next_height} is not available yet, waiting..."); + } + } + counter += 1; + } + + info!("Querying for block with height {next_height} from the DHT"); + counter = 0; + let epoch_id = self.epoch.current_epoch_numer(); + loop { + if counter > self.args.block_polling_max_attempts { + error!( + "DHT: Block for height {next_height} is not available after {counter} attempts" + ); + break; + } + tokio::select! { + Ok(Some(block)) = self.query_dht(epoch_id) => { + info!("DHT: Received block {block}"); + break; + } + _= tokio::time::sleep(Duration::from_secs(self.args.block_polling_interval_seconds)) => { + trace!("DHT: Block for height {next_height} is not available in yet, waiting..."); + } + } + counter += 1; + } + + // TODO: query smart contract for the nym-api which was able to submit rewards and query its block. + // TODO: Because each Ephemera "sees" all blocks during RB then it might be safe to save them locally + // TODO: already during RB. In case of failure of that node. + + info!("Finished reward calculation for previous epoch"); + Ok(vec![]) + } +} + +impl EphemeraAccess { + pub(crate) fn new(api: CommandExecutor, key_pair: Keypair) -> Self { + Self { api, key_pair } + } +} + +#[async_trait] +pub(crate) trait EpochOperations { + async fn perform_epoch_operations( + &mut self, + rewards: Vec, + ) -> anyhow::Result>; +} + +pub(crate) struct RewardManager { + pub epoch: Epoch, + pub args: Args, + pub ephemera_access: Option, + aggregator: Option, +} + +impl RewardManager +where + Self: EpochOperations, +{ + pub(crate) fn new( + args: Args, + ephemera_access: Option, + aggregator: Option, + epoch: Epoch, + ) -> Self { + info!( + "Starting RewardManager with epoch nr {}", + epoch.current_epoch_numer() + ); + Self { + epoch, + args, + ephemera_access, + aggregator, + } + } + + pub(crate) async fn get_last_block(&self) -> Result { + let access = self + .ephemera_access + .as_ref() + .expect("Ephemera access not set"); + let block = access.api.get_last_block().await?; + Ok(block) + } + + pub(crate) async fn get_block_by_height( + &self, + height: u64, + ) -> Result, ApiError> { + let access = self + .ephemera_access + .as_ref() + .expect("Ephemera access not set"); + let block = access.api.get_block_by_height(height).await?; + Ok(block) + } + + pub(crate) async fn store_in_dht(&self, epoch_id: u64, block: &ApiBlock) -> anyhow::Result<()> { + info!("Storing ourselves as 'winner' in DHT for epoch id: {epoch_id:?}"); + + let access = self + .ephemera_access + .as_ref() + .expect("Ephemera access not set"); + + let key = format!("epoch_id_{epoch_id}").into_bytes(); + let value = serde_json::to_vec(&block).expect("Failed to serialize block"); + + access.api.store_in_dht(key, value).await?; + info!("Sent store request to DHT"); + Ok(()) + } + + pub(crate) async fn query_dht(&self, epoch_id: u64) -> anyhow::Result> { + let access = self + .ephemera_access + .as_ref() + .expect("Ephemera access not set"); + + let key = format!("epoch_id_{epoch_id}").into_bytes(); + + match access.api.query_dht(key).await? { + None => { + info!("No 'winner' found for epoch id from DHT: {epoch_id:?}"); + Ok(None) + } + Some((_, block)) => { + let block = serde_json::from_slice(block.as_slice())?; + info!("'Winner' found for epoch id from DHT: {epoch_id:?} - {block:?}"); + Ok(Some(block)) + } + } + } + + pub(crate) async fn send_rewards_to_ephemera( + &self, + rewards: Vec, + ) -> anyhow::Result<()> { + let ephemera_msg = self.create_ephemera_message(rewards)?; + debug!("Sending rewards to ephemera: {:?}", ephemera_msg); + + let access = self + .ephemera_access + .as_ref() + .expect("Ephemera access not set"); + + access.api.send_ephemera_message(ephemera_msg).await?; + Ok(()) + } + + fn create_ephemera_message( + &self, + rewards: Vec, + ) -> anyhow::Result { + let keypair = &self + .ephemera_access + .as_ref() + .expect("Ephemera access not set") + .key_pair; + + let label = self.epoch.current_epoch_numer().to_string(); + let data = serde_json::to_vec(&rewards)?; + let raw_message = ephemera_api::RawApiEphemeraMessage::new(label, data); + + let certificate = ephemera_api::ApiCertificate::prepare(keypair, &raw_message)?; + let signed_message = ApiEphemeraMessage::new(raw_message, certificate); + + Ok(signed_message) + } + + //By current assumption, all nodes will try submit their aggregated rewards + //and contract will reject all but first one. + async fn try_aggregate_rewards( + &mut self, + block: ApiBlock, + ) -> anyhow::Result> { + info!( + "Calculating aggregated rewards from block with height: {:?}", + block.header.height + ); + let mut mix_node_rewards = vec![]; + + for message in block.messages { + trace!("Message: {}", message); + let mix_node_reward: Vec = + serde_json::from_slice(&message.data)?; + mix_node_rewards.push(mix_node_reward); + } + + let aggregated_rewards = self.aggregator().aggregate(mix_node_rewards)?; + debug!("Aggregated rewards: {:?}", aggregated_rewards); + + Ok(aggregated_rewards) + } + + fn aggregator(&self) -> &RewardsAggregator { + self.aggregator.as_ref().expect("Aggregator not set") + } +} diff --git a/nym-api/src/epoch_operations/error.rs b/nym-api/src/epoch_operations/error.rs index ab354701dc..e92d28700f 100644 --- a/nym-api/src/epoch_operations/error.rs +++ b/nym-api/src/epoch_operations/error.rs @@ -47,6 +47,9 @@ pub enum RewardingError { #[from] source: rand::distributions::WeightedError, }, + + #[error("{0}")] + GenericError(#[from] anyhow::Error), } impl From for RewardingError { diff --git a/nym-api/src/epoch_operations/helpers.rs b/nym-api/src/epoch_operations/helpers.rs index 2f82df8248..929cbac770 100644 --- a/nym-api/src/epoch_operations/helpers.rs +++ b/nym-api/src/epoch_operations/helpers.rs @@ -5,8 +5,9 @@ use crate::epoch_operations::RewardedSetUpdater; use cosmwasm_std::{Decimal, Fraction}; use nym_mixnet_contract_common::reward_params::Performance; use nym_mixnet_contract_common::{ExecuteMsg, Interval, MixId}; +use serde_derive::{Deserialize, Serialize}; -#[derive(Debug, Clone, Copy)] +#[derive(Debug, Clone, Copy, Serialize, Deserialize)] pub(crate) struct MixnodeWithPerformance { pub(crate) mix_id: MixId, diff --git a/nym-api/src/epoch_operations/mod.rs b/nym-api/src/epoch_operations/mod.rs index d1d58998cc..aa88d0aa66 100644 --- a/nym-api/src/epoch_operations/mod.rs +++ b/nym-api/src/epoch_operations/mod.rs @@ -12,6 +12,7 @@ // 3. Eventually this whole procedure is going to get expanded to allow for distribution of rewarded set generation // and hence this might be a good place for it. +use crate::ephemera::reward::{EpochOperations, RewardManager}; use crate::node_status_api::ONE_DAY; use crate::nym_contract_cache::cache::NymContractCache; use crate::support::nyxd::Client; @@ -32,6 +33,7 @@ mod rewarding; mod transition_beginning; pub struct RewardedSetUpdater { + ephemera_reward_manager: Option, nyxd_client: Client, nym_contract_cache: NymContractCache, storage: NymApiStorage, @@ -45,11 +47,13 @@ impl RewardedSetUpdater { } pub(crate) fn new( + ephemera_reward_manager: Option, nyxd_client: Client, nym_contract_cache: NymContractCache, storage: NymApiStorage, ) -> Self { RewardedSetUpdater { + ephemera_reward_manager, nyxd_client, nym_contract_cache, storage, @@ -58,9 +62,11 @@ impl RewardedSetUpdater { // This is where the epoch gets advanced, and all epoch related transactions originate /// Upon each epoch having finished the following actions are executed by this nym-api: - /// 1. it queries the mixnet contract to check the current `EpochState` in order to figure out whether + /// 1. it computes the rewards for each node using the ephemera channel for the epoch that + /// ended + /// 2. it queries the mixnet contract to check the current `EpochState` in order to figure out whether /// a different nym-api has already started epoch transition (not yet applicable) - /// 2. it sends a `BeginEpochTransition` message to the mixnet contract causing the following to happen: + /// 3. it sends a `BeginEpochTransition` message to the mixnet contract causing the following to happen: /// - if successful, the address of the this validator is going to be saved as being responsible for progressing this epoch. /// What it means in practice is that once we have multiple instances of nym-api running, /// only this one will try to perform the rest of the actions. It will also allow it to @@ -70,21 +76,29 @@ impl RewardedSetUpdater { /// until that is done. /// - ability to send transactions (by other users) that get resolved once given epoch/interval rolls over, /// such as `BondMixnode` or `DelegateToMixnode` will temporarily be frozen until the entire procedure is finished. - /// 3. it obtains the current rewarded set and for each node in there (**SORTED BY MIX_ID!!**), + /// 4. it obtains the current rewarded set and for each node in there (**SORTED BY MIX_ID!!**), /// it sends (in a single batch) `RewardMixnode` message with the measured performance. /// Once the final message gets executed, the mixnet contract automatically transitions /// the state to `ReconcilingEvents`. - /// 4. it obtains the number of pending epoch and interval events and repeatedly sends + /// 5. it obtains the number of pending epoch and interval events and repeatedly sends /// `ReconcileEpochEvents` transaction until all of them are resolved. /// At this point the mixnet contract automatically transitions the state to `AdvancingEpoch`. - /// 5. it obtains the list of all nodes on the network and pseudorandomly (but weighted by total stake) + /// 6. it obtains the list of all nodes on the network and pseudorandomly (but weighted by total stake) /// determines the new rewarded set. It then assigns layers to the provided nodes taking /// family information into consideration. Finally it sends `AdvanceCurrentEpoch` message /// containing the set and layer information thus rolling over the epoch and changing the state /// to `InProgress`. - /// 6. it purges old (older than 48h) measurement data - /// 7. the whole process repeats once the new epoch finishes - async fn perform_epoch_operations(&self, interval: Interval) -> Result<(), RewardingError> { + /// 7. it purges old (older than 48h) measurement data + /// 8. the whole process repeats once the new epoch finishes + async fn perform_epoch_operations(&mut self, interval: Interval) -> Result<(), RewardingError> { + let mut rewards = self.nodes_to_reward(interval).await; + if let Some(ephemera_reward_manager) = self.ephemera_reward_manager.as_mut() { + rewards = ephemera_reward_manager + .perform_epoch_operations(rewards) + .await?; + } + rewards.sort_by_key(|a| a.mix_id); + log::info!("The current epoch has finished."); log::info!( "Interval id: {}, epoch id: {} (absolute epoch id: {})", @@ -128,7 +142,7 @@ impl RewardedSetUpdater { // Reward all the nodes in the still current, soon to be previous rewarded set log::info!("Rewarding the current rewarded set..."); - self.reward_current_rewarded_set(interval).await?; + self.reward_current_rewarded_set(&rewards, interval).await?; // note: those operations don't really have to be atomic, so it's fine to send them // as separate transactions @@ -260,12 +274,14 @@ impl RewardedSetUpdater { } pub(crate) fn start( + ephemera_reward_manager: Option, nyxd_client: Client, nym_contract_cache: &NymContractCache, storage: &NymApiStorage, shutdown: &TaskManager, ) { let mut rewarded_set_updater = RewardedSetUpdater::new( + ephemera_reward_manager, nyxd_client, nym_contract_cache.to_owned(), storage.to_owned(), diff --git a/nym-api/src/epoch_operations/rewarding.rs b/nym-api/src/epoch_operations/rewarding.rs index d050f7efd6..56084a3dfd 100644 --- a/nym-api/src/epoch_operations/rewarding.rs +++ b/nym-api/src/epoch_operations/rewarding.rs @@ -9,6 +9,7 @@ use nym_mixnet_contract_common::{EpochState, Interval, MixId}; impl RewardedSetUpdater { pub(super) async fn reward_current_rewarded_set( &self, + to_reward: &[MixnodeWithPerformance], current_interval: Interval, ) -> Result<(), RewardingError> { let epoch_status = self.nyxd_client.get_current_epoch_status().await?; @@ -34,7 +35,10 @@ impl RewardedSetUpdater { return Err(RewardingError::MidMixRewarding { last_rewarded }); } - if let Err(err) = self._reward_current_rewarded_set(current_interval).await { + if let Err(err) = self + ._reward_current_rewarded_set(to_reward, current_interval) + .await + { log::error!("FAILED to reward rewarded set - {err}"); Err(err) } else { @@ -47,14 +51,12 @@ impl RewardedSetUpdater { async fn _reward_current_rewarded_set( &self, + to_reward: &[MixnodeWithPerformance], current_interval: Interval, ) -> Result<(), RewardingError> { - let mut to_reward = self.nodes_to_reward(current_interval).await; - to_reward.sort_by_key(|a| a.mix_id); - if to_reward.is_empty() { error!("There are no nodes to reward in this epoch - we shouldn't have been in the 'Rewarding' state!"); - } else if let Err(err) = self.nyxd_client.send_rewarding_messages(&to_reward).await { + } else if let Err(err) = self.nyxd_client.send_rewarding_messages(to_reward).await { error!( "failed to perform mixnode rewarding for epoch {}! Error encountered: {err}", current_interval.current_epoch_absolute_id(), @@ -67,7 +69,7 @@ impl RewardedSetUpdater { Ok(()) } - async fn nodes_to_reward(&self, interval: Interval) -> Vec { + pub(crate) async fn nodes_to_reward(&self, interval: Interval) -> Vec { // try to get current up to date view of the network bypassing the cache // in case the epochs were significantly shortened for the purposes of testing let rewarded_set: Vec = match self.nyxd_client.get_rewarded_set_mixnodes().await { diff --git a/nym-api/src/main.rs b/nym-api/src/main.rs index 5784247e7f..a259879ace 100644 --- a/nym-api/src/main.rs +++ b/nym-api/src/main.rs @@ -12,6 +12,7 @@ use crate::support::cli::CliArgs; use crate::support::config::Config; use crate::support::storage; use crate::support::storage::NymApiStorage; +use ::ephemera::configuration::Configuration as EphemeraConfiguration; use ::nym_config::defaults::setup_env; use anyhow::Result; use circulating_supply_api::cache::CirculatingSupplyCache; @@ -29,6 +30,7 @@ use support::{http, nyxd}; mod circulating_supply_api; mod coconut; +mod ephemera; mod epoch_operations; pub(crate) mod network; mod network_monitor; @@ -126,6 +128,33 @@ async fn start_nym_api_tasks( // and then only start the uptime updater (and the monitor itself, duh) // if the monitoring if it's enabled if config.network_monitor.enabled { + let ephemera_config = + match EphemeraConfiguration::try_load(config.get_ephemera_config_path()) { + Ok(c) => c, + Err(_) => { + config + .get_ephemera_args() + .cmd + .clone() + .execute(Some(&config.get_id())); + EphemeraConfiguration::try_load(config.get_ephemera_config_path()) + .expect("Config file should be created now") + } + }; + let ephemera_reward_manager = if config.ephemera.enabled { + Some( + ephemera::application::NymApi::run( + config.get_ephemera_args().clone(), + ephemera_config, + nyxd_client.clone(), + &shutdown, + ) + .await?, + ) + } else { + None + }; + // if network monitor is enabled, the storage MUST BE available let storage = maybe_storage.unwrap(); @@ -143,7 +172,13 @@ async fn start_nym_api_tasks( // start 'rewarding' if its enabled if config.rewarding.enabled { epoch_operations::ensure_rewarding_permission(&nyxd_client).await?; - RewardedSetUpdater::start(nyxd_client, nym_contract_cache_state, storage, &shutdown); + RewardedSetUpdater::start( + ephemera_reward_manager, + nyxd_client, + nym_contract_cache_state, + storage, + &shutdown, + ); } } @@ -164,6 +199,11 @@ async fn run_nym_api(cli_args: CliArgs) -> Result<(), Box, + /// Specifies whether ephemera is used to aggregate monitor data on this API + #[clap(short = 'e', long, requires = "enable_monitor")] + pub(crate) enable_ephemera: Option, + /// Endpoint to nyxd instance from which the monitor will grab nodes to test #[clap(long)] pub(crate) nyxd_validator: Option, @@ -105,6 +109,10 @@ pub(crate) struct CliArgs { hide = true )] pub(crate) enable_coconut: Option, + + /// Ephemera configuration arguments. + #[command(flatten)] + pub(crate) ephemera_args: ephemera::cli::init::Cmd, } pub(crate) fn override_config(config: Config, args: CliArgs) -> Config { @@ -139,12 +147,26 @@ pub(crate) fn override_config(config: Config, args: CliArgs) -> Config { ) .with_optional(Config::with_network_monitor_enabled, args.enable_monitor) .with_optional(Config::with_rewarding_enabled, args.enable_rewarding) + .with_optional(Config::with_ephemera_enabled, args.enable_ephemera) .with_optional( Config::with_disabled_credentials_mode, args.enabled_credentials_mode.map(|b| !b), ) .with_optional(Config::with_announce_address, args.announce_address) .with_optional(Config::with_coconut_signer_enabled, args.enable_coconut) + .with_optional(Config::with_ephemera_ip, args.ephemera_args.ephemera_ip) + .with_optional( + Config::with_ephemera_protocol_port, + args.ephemera_args.ephemera_protocol_port, + ) + .with_optional( + Config::with_ephemera_websocket_port, + args.ephemera_args.ephemera_websocket_port, + ) + .with_optional( + Config::with_ephemera_http_api_port, + args.ephemera_args.ephemera_http_api_port, + ) } pub(crate) fn build_config(args: CliArgs) -> Result { diff --git a/nym-api/src/support/config/mod.rs b/nym-api/src/support/config/mod.rs index 163eaa44db..bc4bdd7b54 100644 --- a/nym-api/src/support/config/mod.rs +++ b/nym-api/src/support/config/mod.rs @@ -8,7 +8,7 @@ use crate::support::config::template::CONFIG_TEMPLATE; use nym_config::defaults::{mainnet, NymNetworkDetails}; use nym_config::{ must_get_home, read_config_from_toml_file, save_formatted_config_to_file, NymConfigTemplate, - DEFAULT_CONFIG_DIR, DEFAULT_CONFIG_FILENAME, DEFAULT_DATA_DIR, NYM_DIR, + DEFAULT_CONFIG_DIR, DEFAULT_CONFIG_FILENAME, DEFAULT_DATA_DIR, DEFAULT_NYM_APIS_DIR, NYM_DIR, }; use nym_validator_client::nyxd; use serde::{Deserialize, Serialize}; @@ -23,8 +23,6 @@ pub(crate) mod old_config_v1_1_21; mod persistence; mod template; -const DEFAULT_NYM_APIS_DIR: &str = "nym-api"; - pub const DEFAULT_LOCAL_VALIDATOR: &str = "http://localhost:26657"; pub const DEFAULT_NYM_API_PORT: u16 = 8080; @@ -96,6 +94,8 @@ pub struct Config { pub rewarding: Rewarding, pub coconut_signer: CoconutSigner, + + pub ephemera: Ephemera, } impl<'a> From<&'a Config> for NymNetworkDetails { @@ -124,6 +124,7 @@ impl Config { circulating_supply_cacher: Default::default(), rewarding: Default::default(), coconut_signer: CoconutSigner::new_default(id.as_ref()), + ephemera: Ephemera::new_default(id.as_ref()), } } @@ -168,6 +169,11 @@ impl Config { self } + pub fn with_ephemera_enabled(mut self, enabled: bool) -> Self { + self.ephemera.enabled = enabled; + self + } + pub fn with_custom_nyxd_validator(mut self, validator: Url) -> Self { self.base.local_validator = validator; self @@ -208,6 +214,30 @@ impl Config { self } + pub fn with_ephemera_ip(mut self, ip: String) -> Self { + self.ephemera.args.cmd.ephemera_ip = Some(ip); + self + } + + pub fn with_ephemera_protocol_port(mut self, port: u16) -> Self { + self.ephemera.args.cmd.ephemera_protocol_port = Some(port); + self + } + + pub fn with_ephemera_websocket_port(mut self, port: u16) -> Self { + self.ephemera.args.cmd.ephemera_websocket_port = Some(port); + self + } + + pub fn with_ephemera_http_api_port(mut self, port: u16) -> Self { + self.ephemera.args.cmd.ephemera_http_api_port = Some(port); + self + } + + pub fn get_id(&self) -> String { + self.base.id.clone() + } + pub fn get_nyxd_url(&self) -> Url { self.base.local_validator.clone() } @@ -223,6 +253,14 @@ impl Config { pub fn get_mnemonic(&self) -> bip39::Mnemonic { self.base.mnemonic.clone() } + + pub fn get_ephemera_args(&self) -> &crate::ephemera::Args { + &self.ephemera.args + } + + pub fn get_ephemera_config_path(&self) -> PathBuf { + self.ephemera.args.ephemera_config.clone() + } } // we only really care about the mnemonic being zeroized @@ -542,3 +580,25 @@ impl Default for CoconutSignerDebug { } } } + +#[derive(Debug, Default, Deserialize, PartialEq, Eq, Serialize)] +#[serde(default)] +pub struct Ephemera { + pub enabled: bool, + args: crate::ephemera::Args, +} + +impl Ephemera { + fn new_default(id: &str) -> Self { + Ephemera { + enabled: false, + args: crate::ephemera::Args { + ephemera_config: ephemera::configuration::Configuration::ephemera_config_file_home( + Some(id), + ) + .unwrap(), + ..Default::default() + }, + } + } +} diff --git a/nym-api/src/support/config/old_config_v1_1_21.rs b/nym-api/src/support/config/old_config_v1_1_21.rs index 3e5ded9964..2f13baaad0 100644 --- a/nym-api/src/support/config/old_config_v1_1_21.rs +++ b/nym-api/src/support/config/old_config_v1_1_21.rs @@ -156,6 +156,7 @@ impl From for Config { dkg_contract_polling_rate: value.coconut_signer.dkg_contract_polling_rate, }, }, + ephemera: Default::default(), } } } diff --git a/nym-api/src/support/config/template.rs b/nym-api/src/support/config/template.rs index 7ef105d91d..32d4f5e087 100644 --- a/nym-api/src/support/config/template.rs +++ b/nym-api/src/support/config/template.rs @@ -130,4 +130,12 @@ decryption_key_path = '{{ coconut_signer.storage_paths.decryption_key_path }}' # Path to the dkg dealer public key with proof public_key_with_proof_path = '{{ coconut_signer.storage_paths.public_key_with_proof_path }}' +[ephemera] + +enabled = {{ ephemera.enabled }} + +[ephemera.args] + +ephemera_config = '{{ ephemera.args.ephemera_config }}' + "#; diff --git a/nym-api/src/support/nyxd/mod.rs b/nym-api/src/support/nyxd/mod.rs index 501be9e581..790be3ca31 100644 --- a/nym-api/src/support/nyxd/mod.rs +++ b/nym-api/src/support/nyxd/mod.rs @@ -18,6 +18,8 @@ use nym_coconut_dkg_common::{ }; use nym_config::defaults::ChainDetails; use nym_contracts_common::dealings::ContractSafeBytes; +use nym_ephemera_common::msg::QueryMsg as EphemeraQueryMsg; +use nym_ephemera_common::types::JsonPeerInfo; use nym_mixnet_contract_common::families::FamilyHead; use nym_mixnet_contract_common::mixnode::MixNodeDetails; use nym_mixnet_contract_common::reward_params::RewardingParams; @@ -31,10 +33,11 @@ use nym_validator_client::nyxd::contract_traits::{NameServiceQueryClient, PagedD use nym_validator_client::nyxd::error::NyxdError; use nym_validator_client::nyxd::{ contract_traits::{ - CoconutBandwidthQueryClient, DkgQueryClient, DkgSigningClient, GroupQueryClient, - MixnetQueryClient, MixnetSigningClient, MultisigQueryClient, MultisigSigningClient, - NymContractsProvider, PagedMixnetQueryClient, PagedMultisigQueryClient, - PagedVestingQueryClient, SpDirectoryQueryClient, + CoconutBandwidthQueryClient, DkgQueryClient, DkgSigningClient, EphemeraQueryClient, + EphemeraSigningClient, GroupQueryClient, MixnetQueryClient, MixnetSigningClient, + MultisigQueryClient, MultisigSigningClient, NymContractsProvider, PagedEphemeraQueryClient, + PagedMixnetQueryClient, PagedMultisigQueryClient, PagedVestingQueryClient, + SpDirectoryQueryClient, }, cosmwasm_client::types::ExecuteResult, CosmWasmClient, Fee, @@ -434,6 +437,25 @@ impl crate::coconut::client::Client for Client { } } +#[async_trait] +impl crate::ephemera::client::Client for Client { + async fn get_ephemera_peers(&self) -> crate::ephemera::error::Result> { + Ok(self.0.read().await.get_all_ephemera_peers().await?) + } + + async fn register_ephemera_peer( + &self, + peer_info: JsonPeerInfo, + ) -> crate::ephemera::error::Result { + Ok(self + .0 + .write() + .await + .register_as_peer(peer_info, None) + .await?) + } +} + #[async_trait] impl DkgQueryClient for Client { async fn query_dkg_contract(&self, query: DkgQueryMsg) -> std::result::Result @@ -444,6 +466,19 @@ impl DkgQueryClient for Client { } } +#[async_trait] +impl EphemeraQueryClient for Client { + async fn query_ephemera_contract( + &self, + query: EphemeraQueryMsg, + ) -> std::result::Result + where + for<'a> T: Deserialize<'a>, + { + self.0.read().await.query_ephemera_contract(query).await + } +} + #[async_trait] impl SpDirectoryQueryClient for Client { async fn query_service_provider_contract( diff --git a/nym-connect/desktop/Cargo.lock b/nym-connect/desktop/Cargo.lock index 51641a7a86..47977894ba 100644 --- a/nym-connect/desktop/Cargo.lock +++ b/nym-connect/desktop/Cargo.lock @@ -4118,6 +4118,16 @@ dependencies = [ "zeroize", ] +[[package]] +name = "nym-ephemera-common" +version = "0.1.0" +dependencies = [ + "cosmwasm-schema", + "cosmwasm-std", + "cw-utils", + "nym-contracts-common", +] + [[package]] name = "nym-explorer-api-requests" version = "0.1.0" @@ -4578,6 +4588,7 @@ dependencies = [ "nym-coconut-interface", "nym-config", "nym-contracts-common", + "nym-ephemera-common", "nym-group-contract-common", "nym-mixnet-contract-common", "nym-multisig-contract-common", diff --git a/nym-wallet/Cargo.lock b/nym-wallet/Cargo.lock index 72ea6b5683..fbe8b1d3f4 100644 --- a/nym-wallet/Cargo.lock +++ b/nym-wallet/Cargo.lock @@ -3268,6 +3268,16 @@ dependencies = [ "zeroize", ] +[[package]] +name = "nym-ephemera-common" +version = "0.1.0" +dependencies = [ + "cosmwasm-schema", + "cosmwasm-std", + "cw-utils", + "nym-contracts-common", +] + [[package]] name = "nym-group-contract-common" version = "0.1.0" @@ -3432,6 +3442,7 @@ dependencies = [ "nym-coconut-interface", "nym-config", "nym-contracts-common", + "nym-ephemera-common", "nym-group-contract-common", "nym-mixnet-contract-common", "nym-multisig-contract-common", diff --git a/nym-wallet/nym-wallet-types/src/network/qa.rs b/nym-wallet/nym-wallet-types/src/network/qa.rs index 1c1ca33ae8..be282384f5 100644 --- a/nym-wallet/nym-wallet-types/src/network/qa.rs +++ b/nym-wallet/nym-wallet-types/src/network/qa.rs @@ -23,6 +23,8 @@ pub(crate) const MULTISIG_CONTRACT_ADDRESS: &str = "n1p54qvfde6mpnqvz3dnpa78x2qyyr5k4sgw9qr97mxjgklc5gze9sv6t964"; pub(crate) const COCONUT_DKG_CONTRACT_ADDRESS: &str = "n1xqkp8x4gqwjnhemtemc5dqhwll6w6rrgpywvhka7sh8vz8swul9sp3lv3w"; +pub(crate) const EPHEMERA_CONTRACT_ADDRESS: &str = + "n1xqkp8x4gqwjnhemtemc5dqhwll6w6rrgpywvhka7sh8vz8swul9sp3lv3w"; pub(crate) const SERVICE_PROVIDER_DIRECTORY_CONTRACT_ADDRESS: &str = "n1nhdr07kmjns2x8dnp53tdk4qxreze8zdxj6xucyvkdj9tta73rjqa96wps"; pub(crate) const NAME_SERVICE_CONTRACT_ADDRESS: &str = @@ -55,6 +57,7 @@ pub(crate) fn network_details() -> nym_network_defaults::NymNetworkDetails { group_contract_address: parse_optional_str(GROUP_CONTRACT_ADDRESS), multisig_contract_address: parse_optional_str(MULTISIG_CONTRACT_ADDRESS), coconut_dkg_contract_address: parse_optional_str(COCONUT_DKG_CONTRACT_ADDRESS), + ephemera_contract_address: parse_optional_str(EPHEMERA_CONTRACT_ADDRESS), service_provider_directory_contract_address: parse_optional_str( SERVICE_PROVIDER_DIRECTORY_CONTRACT_ADDRESS, ), diff --git a/nym-wallet/nym-wallet-types/src/network/sandbox.rs b/nym-wallet/nym-wallet-types/src/network/sandbox.rs index 4c241248c7..0499fa5420 100644 --- a/nym-wallet/nym-wallet-types/src/network/sandbox.rs +++ b/nym-wallet/nym-wallet-types/src/network/sandbox.rs @@ -23,6 +23,8 @@ pub(crate) const MULTISIG_CONTRACT_ADDRESS: &str = "n14ph4e660eyqz0j36zlkaey4zgzexm5twkmjlqaequxr2cjm9eprqsmad6k"; pub(crate) const COCONUT_DKG_CONTRACT_ADDRESS: &str = "n1ahg0erc2fs6xx3j5m8sfx3ryuzdjh6kf6qm9plsf865fltekyrfsesac6a"; +pub(crate) const EPHEMERA_CONTRACT_ADDRESS: &str = + "n1ahg0erc2fs6xx3j5m8sfx3ryuzdjh6kf6qm9plsf865fltekyrfsesac6a"; // -- Constructor functions -- @@ -51,6 +53,7 @@ pub(crate) fn network_details() -> nym_network_defaults::NymNetworkDetails { group_contract_address: parse_optional_str(GROUP_CONTRACT_ADDRESS), multisig_contract_address: parse_optional_str(MULTISIG_CONTRACT_ADDRESS), coconut_dkg_contract_address: parse_optional_str(COCONUT_DKG_CONTRACT_ADDRESS), + ephemera_contract_address: parse_optional_str(EPHEMERA_CONTRACT_ADDRESS), service_provider_directory_contract_address: None, name_service_contract_address: None, }, From e49c8588c68ecfd96787d5319a768c5ac3f2b58e Mon Sep 17 00:00:00 2001 From: serinko <97586125+serinko@users.noreply.github.com> Date: Fri, 18 Aug 2023 14:03:43 +0200 Subject: [PATCH 270/361] initiated telegram tutorial --- .../dev-portal/src/tutorials/telegram.md | 41 +++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 documentation/dev-portal/src/tutorials/telegram.md diff --git a/documentation/dev-portal/src/tutorials/telegram.md b/documentation/dev-portal/src/tutorials/telegram.md new file mode 100644 index 0000000000..1965fceea4 --- /dev/null +++ b/documentation/dev-portal/src/tutorials/telegram.md @@ -0,0 +1,41 @@ +# Telegram NymConnect Integration + +*This is a shortened version of a [Nym Community post](https://blog.nymtech.net/how-to-use-telegram-in-iraq-with-nymconnect-106a3b8dd050) written by Saliveja.* + +The purpose of the following manual is not to promote Telegram but so people can use it with the Nym mixnet if they wish to, should a situation ask for that. This privacy-enhances Telegram at the network level and allows users to access the application from locations like where the application was banned. + +See also: [Element (Matrix) over the Nym mixnet](./matrix.md): private, decentralised and secure messaging. + +## Setup & Run + +Here’s how to configure Telegram with NymConnect: + +1. Download and install NymConnect ().** + For more releases, check out [Github](https://github.com/nymtech/nym/tags). NymConnect is available for Linux, Windows, and MacOS. + On Linux make sure NymConnect is executable. Opening a terminal in the same directory and run: + ```sh + chmod +x ./.AppImage + ``` +2. **Start NymConnect** + Telegram is added to NymConnect by default. +3. **Click connect - the host and port will now be displayed.** +4. **Click on host or port to copy** the value to the clipboard. +5. **Open the Telegram proxy settings.** + Linux: Telegram -> Settings -> Advanced -> Connection type -> Use custom proxy + MacOS: Telegram -> Settings -> Advanced -> Data & Storage -> Connection Type -> Use custom Proxy + Windows: Telegram -> Settings -> Data and Storage -> Use proxy +6. **Add a proxy** with the Add proxy button. +7. **Select SOCKS5** and make sure the port details are the same as those generated by NymConnect. Alternatively, follow this link: https://t.me/socks?server=127.0.0.1&port=1080 +8. **Save the proxy settings** in Telegram. +9. **Telegram is now running through the Nym Mixnet and is privacy-enhanced!** + This allows you to connect from regions which blocked Telegram. +10. Note if you remain idle on Telegram for a while you might lose connectivity and your messages might not get through via SOCKS5 proxy. If that happens reconnect your NymConnect and reset the proxy again. + + +Follow this [video](https://youtu.be/quj8H2qeOwY?t=97) to see the steps on Telegram setup. + + +**Now your Telegram runs over NymConnect.** + +NymConnect is currently available for several applications and service providers. Support for more apps is on the way. For any bug reports or feedback please reach out to us on Telegram or our [Discord](http://discord.gg/nym) server. + From c8516071fca4755edbf2bbda8d2783f6943142ac Mon Sep 17 00:00:00 2001 From: mfahampshire Date: Fri, 18 Aug 2023 14:36:22 +0200 Subject: [PATCH 271/361] removed developer portal specific files --- .github/workflows/cd-dev.yml | 77 ------------------------------------ .github/workflows/ci-dev.yml | 70 -------------------------------- 2 files changed, 147 deletions(-) delete mode 100644 .github/workflows/cd-dev.yml delete mode 100644 .github/workflows/ci-dev.yml diff --git a/.github/workflows/cd-dev.yml b/.github/workflows/cd-dev.yml deleted file mode 100644 index 664f3cd79e..0000000000 --- a/.github/workflows/cd-dev.yml +++ /dev/null @@ -1,77 +0,0 @@ -name: CD dev-portal - -on: - workflow_dispatch: - push: - branches: master - paths: - - 'documentation/dev-portal/**' - -jobs: - build: - runs-on: custom-runner-linux - steps: - - uses: actions/checkout@v3 - - name: Install rsync - run: sudo apt-get install rsync - - uses: rlespinasse/github-slug-action@v3.x - - uses: actions/setup-node@v3 - with: - node-version: 18 - - name: Install Rust stable - uses: actions-rs/toolchain@v1 - with: - toolchain: stable - - name: Install mdbook - run: (test -x $HOME/.cargo/bin/mdbook || cargo install --vers "^0.4" mdbook) - - name: Install mdbook plugins - run: | - cargo install --vers "^0.2.0" mdbook-variables && cargo install \ - --vers "^1.8.0" mdbook-admonish && cargo install --vers \ - "^0.1.2" mdbook-last-changed && cargo install --vers "^0.1.2" \ - mdbook-theme && cargo install --vers "^0.7.7" mdbook-linkcheck - - name: Clean website - run: cd documentation/dev-portal && mdbook clean - - name: Build website - run: cd documentation/dev-portal && mdbook build - - name: Deploy branch master to dev - continue-on-error: true - uses: easingthemes/ssh-deploy@main - env: - SSH_PRIVATE_KEY: ${{ secrets.CD_WWW_SSH_PRIVATE_KEY }} - ARGS: "-rltgoDzvO --delete" - SOURCE: "documentation/dev-portal/book/html/" - REMOTE_HOST: ${{ secrets.CD_WWW_REMOTE_HOST_DEV }} - REMOTE_USER: ${{ secrets.CD_WWW_REMOTE_USER }} - TARGET: ${{ secrets.CD_WWW_REMOTE_TARGET_DEVP }}/ - EXCLUDE: "/dist/, /node_modules/" - - name: Deploy branch master to prod - uses: easingthemes/ssh-deploy@main - env: - SSH_PRIVATE_KEY: ${{ secrets.CD_WWW_SSH_PRIVATE_KEY }} - ARGS: "-rltgoDzvO --delete" - SOURCE: "documentation/dev-portal/book/html/" - REMOTE_HOST: ${{ secrets.CD_WWW_REMOTE_HOST_PROD }} - REMOTE_USER: ${{ secrets.CD_WWW_REMOTE_USER }} - TARGET: ${{ secrets.CD_WWW_REMOTE_TARGET_DEVP }}/ - EXCLUDE: "/dist/, /node_modules/" - - name: Matrix - Node Install - run: npm install - working-directory: .github/workflows/support-files - - name: Matrix - Send Notification - env: - NYM_NOTIFICATION_KIND: cd-dev - NYM_PROJECT_NAME: "Dev portal CD" - NYM_CI_WWW_BASE: "${{ secrets.NYM_CD_WWW_BASE }}" - NYM_CI_WWW_LOCATION: "${{ env.GITHUB_REF_SLUG }}" - GIT_COMMIT_MESSAGE: "${{ github.event.head_commit.message }}" - GIT_BRANCH: "${GITHUB_REF##*/}" - MATRIX_SERVER: "${{ secrets.MATRIX_SERVER }}" - MATRIX_ROOM: "${{ secrets.MATRIX_ROOM_DEVP }}" - MATRIX_USER_ID: "${{ secrets.MATRIX_USER_ID }}" - MATRIX_TOKEN: "${{ secrets.MATRIX_TOKEN }}" - MATRIX_DEVICE_ID: "${{ secrets.MATRIX_DEVICE_ID }}" - IS_SUCCESS: "${{ job.status == 'success' }}" - uses: docker://keybaseio/client:stable-node - with: - args: .github/workflows/support-files/notifications/entry_point.sh diff --git a/.github/workflows/ci-dev.yml b/.github/workflows/ci-dev.yml deleted file mode 100644 index 90470e8c3e..0000000000 --- a/.github/workflows/ci-dev.yml +++ /dev/null @@ -1,70 +0,0 @@ -name: CI dev-portal - -on: - workflow_dispatch: - push: - branches-ignore: master - paths: - - 'documentation/dev-portal/**' - -jobs: - build: - runs-on: custom-runner-linux - steps: - - uses: actions/checkout@v3 - - name: Install rsync - run: sudo apt-get install rsync - - uses: rlespinasse/github-slug-action@v3.x - - uses: actions/setup-node@v3 - with: - node-version: 18 - - name: Install Rust stable - uses: actions-rs/toolchain@v1 - with: - toolchain: stable - - name: Install mdbook - # to make sure we don't run into toml dependency issue, make sure to pull fixed mdbook that depends on old 0.5 - # and OLD mdbook-variables that uses the same dependency - # (mdbook-variables 0.2.3 updated to 0.7 which was used by mdbook incorrectly and extremely briefly) - run: (test -x $HOME/.cargo/bin/mdbook || cargo install --vers "^0.4.33" mdbook) - - name: Install mdbook plugins - run: | - cargo install --vers "=0.2.2" mdbook-variables && cargo install \ - --vers "^1.8.0" mdbook-admonish && cargo install --vers \ - "^0.1.2" mdbook-last-changed && cargo install --vers "^0.1.2" mdbook-theme \ - && cargo install --vers "^0.7.7" mdbook-linkcheck - - name: Clean website - run: cd documentation/dev-portal && mdbook clean - - name: Build website - run: cd documentation/dev-portal && mdbook build - - name: Deploy branch to CI www - continue-on-error: true - uses: easingthemes/ssh-deploy@main - env: - SSH_PRIVATE_KEY: ${{ secrets.CI_WWW_SSH_PRIVATE_KEY }} - ARGS: "-rltgoDzvO --delete" - SOURCE: "documentation/dev-portal/book/html/" - REMOTE_HOST: ${{ secrets.CI_WWW_REMOTE_HOST }} - REMOTE_USER: ${{ secrets.CI_WWW_REMOTE_USER }} - TARGET: ${{ secrets.CI_WWW_REMOTE_TARGET }}/dev-portal-${{ env.GITHUB_REF_SLUG }} - EXCLUDE: "/dist/, /node_modules/" - - name: Matrix - Node Install - run: npm install - working-directory: .github/workflows/support-files - - name: Matrix - Send Notification - env: - NYM_NOTIFICATION_KIND: ci-dev - NYM_PROJECT_NAME: "Dev portal CI" - NYM_CI_WWW_BASE: "${{ secrets.NYM_CI_WWW_BASE }}" - NYM_CI_WWW_LOCATION: "dev-portal-${{ env.GITHUB_REF_SLUG }}" - GIT_COMMIT_MESSAGE: "${{ github.event.head_commit.message }}" - GIT_BRANCH: "${GITHUB_REF##*/}" - MATRIX_SERVER: "${{ secrets.MATRIX_SERVER }}" - MATRIX_ROOM: "${{ secrets.MATRIX_ROOM_DEVP }}" - MATRIX_USER_ID: "${{ secrets.MATRIX_USER_ID }}" - MATRIX_TOKEN: "${{ secrets.MATRIX_TOKEN }}" - MATRIX_DEVICE_ID: "${{ secrets.MATRIX_DEVICE_ID }}" - IS_SUCCESS: "${{ job.status == 'success' }}" - uses: docker://keybaseio/client:stable-node - with: - args: .github/workflows/support-files/notifications/entry_point.sh From ff4350ec9083d89eb9a8e11c41d7c49212797f4f Mon Sep 17 00:00:00 2001 From: mfahampshire Date: Fri, 18 Aug 2023 14:36:46 +0200 Subject: [PATCH 272/361] changed name of script for staging to dist/ --- documentation/{staging.sh => build_all_to_dist.sh} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename documentation/{staging.sh => build_all_to_dist.sh} (100%) diff --git a/documentation/staging.sh b/documentation/build_all_to_dist.sh similarity index 100% rename from documentation/staging.sh rename to documentation/build_all_to_dist.sh From 43dfb31eb22e946cbbbe078fbd78a5a09cccc1a6 Mon Sep 17 00:00:00 2001 From: mfahampshire Date: Fri, 18 Aug 2023 14:37:14 +0200 Subject: [PATCH 273/361] modified ci script --- .github/workflows/ci-docs.yml | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/.github/workflows/ci-docs.yml b/.github/workflows/ci-docs.yml index 5d7874833b..f7c7af8d07 100644 --- a/.github/workflows/ci-docs.yml +++ b/.github/workflows/ci-docs.yml @@ -1,6 +1,7 @@ name: CI docs on: + workflow_dispatch: push: branches-ignore: master paths: @@ -34,21 +35,24 @@ jobs: --vers "^1.8.0" mdbook-admonish && cargo install --vers \ "^0.1.2" mdbook-last-changed && cargo install --vers "^0.1.2" mdbook-theme \ && cargo install --vers "^0.7.7" mdbook-linkcheck - - name: Clean website - run: cd documentation/docs && mdbook clean - - name: Build website - run: cd documentation/docs && mdbook build + # - name: Clean website + # run: cd documentation/docs && mdbook clean + # - name: Build website + # run: cd documentation/docs && mdbook build + - name: Build all projects in documentation/ & move to ~/dist/docs/ + run: cd documentation && chmod +x build_all_to_dist.sh && ./build_all_to_dist.sh + continue-on-error: false - name: Deploy branch to CI www continue-on-error: true uses: easingthemes/ssh-deploy@main env: SSH_PRIVATE_KEY: ${{ secrets.CI_WWW_SSH_PRIVATE_KEY }} ARGS: "-rltgoDzvO --delete" - SOURCE: "documentation/docs/book/" + SOURCE: "dist/docs/" REMOTE_HOST: ${{ secrets.CI_WWW_REMOTE_HOST }} REMOTE_USER: ${{ secrets.CI_WWW_REMOTE_USER }} TARGET: ${{ secrets.CI_WWW_REMOTE_TARGET }}/docs-${{ env.GITHUB_REF_SLUG }} - EXCLUDE: "/dist/, /node_modules/" + EXCLUDE: "/node_modules/" - name: Matrix - Node Install run: npm install working-directory: .github/workflows/support-files From d6369ea784c66dffb3031d9d5fbad9b7a4c9b795 Mon Sep 17 00:00:00 2001 From: serinko <97586125+serinko@users.noreply.github.com> Date: Fri, 18 Aug 2023 15:31:11 +0200 Subject: [PATCH 274/361] added telegram to SUMMARY.md --- documentation/dev-portal/src/SUMMARY.md | 1 + 1 file changed, 1 insertion(+) diff --git a/documentation/dev-portal/src/SUMMARY.md b/documentation/dev-portal/src/SUMMARY.md index 0e0c02bc58..785d26267e 100644 --- a/documentation/dev-portal/src/SUMMARY.md +++ b/documentation/dev-portal/src/SUMMARY.md @@ -20,6 +20,7 @@ - [NymConnect Monero](tutorials/monero.md) - [NymConnect Matrix](tutorials/matrix.md) +- [NymConnect Telegram](tutorials/telegram.md) # Integrations From 7b3cbdcd447cfe17d8d9f2b10c3f00efbdabea29 Mon Sep 17 00:00:00 2001 From: mfahampshire Date: Fri, 18 Aug 2023 15:36:11 +0200 Subject: [PATCH 275/361] cont. work on cicd --- .github/workflows/cd-docs.yml | 15 +++++++-------- .github/workflows/ci-docs.yml | 13 +++++-------- 2 files changed, 12 insertions(+), 16 deletions(-) diff --git a/.github/workflows/cd-docs.yml b/.github/workflows/cd-docs.yml index b3f15ab265..25deaa0e82 100644 --- a/.github/workflows/cd-docs.yml +++ b/.github/workflows/cd-docs.yml @@ -36,31 +36,30 @@ jobs: "^0.1.2" mdbook-last-changed && cargo install --vers "^0.1.2" \ mdbook-theme && cargo install --vers "^0.7.7" mdbook-linkcheck && \ cargo install --vers "^0.5.0" mdbook-cmdrun - - name: Clean website - run: cd documentation/docs && mdbook clean - - name: Build website - run: cd documentation/docs && mdbook build + - 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 env: SSH_PRIVATE_KEY: ${{ secrets.CD_WWW_SSH_PRIVATE_KEY }} ARGS: "-rltgoDzvO --delete" - SOURCE: "documentation/docs/book/" + SOURCE: "dist/docs/" REMOTE_HOST: ${{ secrets.CD_WWW_REMOTE_HOST_DEV }} REMOTE_USER: ${{ secrets.CD_WWW_REMOTE_USER }} TARGET: ${{ secrets.CD_WWW_REMOTE_TARGET }}/ - EXCLUDE: "/dist/, /node_modules/" + EXCLUDE: "/node_modules/" - name: Deploy branch master to prod uses: easingthemes/ssh-deploy@main env: SSH_PRIVATE_KEY: ${{ secrets.CD_WWW_SSH_PRIVATE_KEY }} ARGS: "-rltgoDzvO --delete" - SOURCE: "documentation/docs/book/" + SOURCE: "dist/docs/" REMOTE_HOST: ${{ secrets.CD_WWW_REMOTE_HOST_PROD }} REMOTE_USER: ${{ secrets.CD_WWW_REMOTE_USER }} TARGET: ${{ secrets.CD_WWW_REMOTE_TARGET }}/ - EXCLUDE: "/dist/, /node_modules/" + EXCLUDE: "/node_modules/" - name: Matrix - Node Install run: npm install working-directory: .github/workflows/support-files diff --git a/.github/workflows/ci-docs.yml b/.github/workflows/ci-docs.yml index f7c7af8d07..f69da39928 100644 --- a/.github/workflows/ci-docs.yml +++ b/.github/workflows/ci-docs.yml @@ -31,16 +31,13 @@ jobs: run: (test -x $HOME/.cargo/bin/mdbook || cargo install --vers "^0.4.33" mdbook) - name: Install mdbook plugins run: | - cargo install --vers "=0.2.2" mdbook-variables && cargo install \ + cargo install --vers "^0.2.0" mdbook-variables && cargo install \ --vers "^1.8.0" mdbook-admonish && cargo install --vers \ - "^0.1.2" mdbook-last-changed && cargo install --vers "^0.1.2" mdbook-theme \ - && cargo install --vers "^0.7.7" mdbook-linkcheck - # - name: Clean website - # run: cd documentation/docs && mdbook clean - # - name: Build website - # run: cd documentation/docs && mdbook build + "^0.1.2" mdbook-last-changed && cargo install --vers "^0.1.2" \ + mdbook-theme && cargo install --vers "^0.7.7" mdbook-linkcheck && \ + cargo install --vers "^0.5.0" mdbook-cmdrun - name: Build all projects in documentation/ & move to ~/dist/docs/ - run: cd documentation && chmod +x build_all_to_dist.sh && ./build_all_to_dist.sh + run: cd documentation && ./build_all_to_dist.sh continue-on-error: false - name: Deploy branch to CI www continue-on-error: true From c169971287c10d9af144949cd10fb346a0725b84 Mon Sep 17 00:00:00 2001 From: mfahampshire Date: Fri, 18 Aug 2023 15:45:44 +0200 Subject: [PATCH 276/361] fixed mdbook versions --- .github/workflows/cd-docs.yml | 9 ++++----- .github/workflows/ci-docs.yml | 7 +++---- 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/.github/workflows/cd-docs.yml b/.github/workflows/cd-docs.yml index 25deaa0e82..f3e6e34da5 100644 --- a/.github/workflows/cd-docs.yml +++ b/.github/workflows/cd-docs.yml @@ -28,14 +28,13 @@ jobs: command: build args: --workspace --release --all - name: Install mdbook - run: (test -x $HOME/.cargo/bin/mdbook || cargo install --vers "^0.4" mdbook) + run: (test -x $HOME/.cargo/bin/mdbook || cargo install --vers "^0.4.33" mdbook) - name: Install mdbook plugins run: | - cargo install --vers "^0.2.0" mdbook-variables && cargo install \ + cargo install --vers "=0.2.2" mdbook-variables && cargo install \ --vers "^1.8.0" mdbook-admonish && cargo install --vers \ - "^0.1.2" mdbook-last-changed && cargo install --vers "^0.1.2" \ - mdbook-theme && cargo install --vers "^0.7.7" mdbook-linkcheck && \ - cargo install --vers "^0.5.0" mdbook-cmdrun + "^0.1.2" mdbook-last-changed && cargo install --vers "^0.1.2" mdbook-theme \ + && cargo install --vers "^0.7.7" mdbook-linkcheck - name: Build all projects in documentation/ & move to ~/dist/docs/ run: cd documentation && ./build_all_to_dist.sh continue-on-error: false diff --git a/.github/workflows/ci-docs.yml b/.github/workflows/ci-docs.yml index f69da39928..2ce019272a 100644 --- a/.github/workflows/ci-docs.yml +++ b/.github/workflows/ci-docs.yml @@ -31,11 +31,10 @@ jobs: run: (test -x $HOME/.cargo/bin/mdbook || cargo install --vers "^0.4.33" mdbook) - name: Install mdbook plugins run: | - cargo install --vers "^0.2.0" mdbook-variables && cargo install \ + cargo install --vers "=0.2.2" mdbook-variables && cargo install \ --vers "^1.8.0" mdbook-admonish && cargo install --vers \ - "^0.1.2" mdbook-last-changed && cargo install --vers "^0.1.2" \ - mdbook-theme && cargo install --vers "^0.7.7" mdbook-linkcheck && \ - cargo install --vers "^0.5.0" mdbook-cmdrun + "^0.1.2" mdbook-last-changed && cargo install --vers "^0.1.2" mdbook-theme \ + && cargo install --vers "^0.7.7" mdbook-linkcheck - name: Build all projects in documentation/ & move to ~/dist/docs/ run: cd documentation && ./build_all_to_dist.sh continue-on-error: false From 6ead19bd97058191f700b33507fb8353ff8de773 Mon Sep 17 00:00:00 2001 From: mfahampshire Date: Fri, 18 Aug 2023 15:55:59 +0200 Subject: [PATCH 277/361] added conditional push for master branch --- .github/workflows/cd-docs.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/cd-docs.yml b/.github/workflows/cd-docs.yml index f3e6e34da5..a1ba0c104a 100644 --- a/.github/workflows/cd-docs.yml +++ b/.github/workflows/cd-docs.yml @@ -50,6 +50,7 @@ jobs: 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 env: SSH_PRIVATE_KEY: ${{ secrets.CD_WWW_SSH_PRIVATE_KEY }} From 38b9e14851a70649ac10b45912ebea61d6e05b0c Mon Sep 17 00:00:00 2001 From: Mark Sinclair <14054343+mmsinclair@users.noreply.github.com> Date: Fri, 18 Aug 2023 15:43:50 +0100 Subject: [PATCH 278/361] Update cd-docs.yml --- .github/workflows/cd-docs.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/cd-docs.yml b/.github/workflows/cd-docs.yml index 782a2c196d..b3f15ab265 100644 --- a/.github/workflows/cd-docs.yml +++ b/.github/workflows/cd-docs.yml @@ -1,6 +1,7 @@ name: CD docs on: + workflow_dispatch: push: branches: master paths: From b5f2ecd79e1e4e63afd4bc4244196c1f327df7e0 Mon Sep 17 00:00:00 2001 From: Mark Sinclair <14054343+mmsinclair@users.noreply.github.com> Date: Fri, 18 Aug 2023 16:24:57 +0100 Subject: [PATCH 279/361] Update build_all_to_dist.sh --- documentation/build_all_to_dist.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/documentation/build_all_to_dist.sh b/documentation/build_all_to_dist.sh index 87fe4d5b58..87834f5a3c 100755 --- a/documentation/build_all_to_dist.sh +++ b/documentation/build_all_to_dist.sh @@ -34,3 +34,4 @@ do # cd back to ../documentation/ cd ../ done +mv ../../dist/docs/dev-portal ../../dist/docs/developers From 404d2d9857f6c13fb1d7b993e743f1cf3487663b Mon Sep 17 00:00:00 2001 From: Mark Sinclair <14054343+mmsinclair@users.noreply.github.com> Date: Fri, 18 Aug 2023 16:28:42 +0100 Subject: [PATCH 280/361] Update build_all_to_dist.sh --- documentation/build_all_to_dist.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/documentation/build_all_to_dist.sh b/documentation/build_all_to_dist.sh index 87834f5a3c..721ed07e6a 100755 --- a/documentation/build_all_to_dist.sh +++ b/documentation/build_all_to_dist.sh @@ -34,4 +34,4 @@ do # cd back to ../documentation/ cd ../ done -mv ../../dist/docs/dev-portal ../../dist/docs/developers +mv ../dist/docs/dev-portal ../dist/docs/developers From ff4dbd09d0c1ef3513b882180cc71650d82f9ec5 Mon Sep 17 00:00:00 2001 From: Mark Sinclair <14054343+mmsinclair@users.noreply.github.com> Date: Fri, 18 Aug 2023 16:42:18 +0100 Subject: [PATCH 281/361] Update general.css --- documentation/dev-portal/theme/css/general.css | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/documentation/dev-portal/theme/css/general.css b/documentation/dev-portal/theme/css/general.css index 84dbfa3371..1d77810f5e 100644 --- a/documentation/dev-portal/theme/css/general.css +++ b/documentation/dev-portal/theme/css/general.css @@ -8,7 +8,7 @@ } html { - font-family: "Open Sans", sans-serif; + /* font-family: "Open Sans", sans-serif; */ color: var(--fg); background-color: var(--bg); text-size-adjust: none; @@ -16,13 +16,14 @@ html { } body { + font-family: "Open Sans", sans-serif; margin: 0; font-size: 1.5rem; overflow-x: hidden; } code { - font-family: var(--mono-font) !important; + font-family: var(--mono-font); font-size: 0.9em; } From 5f095d703d9c534e065ab9e2a5e109bff2215f61 Mon Sep 17 00:00:00 2001 From: Mark Sinclair <14054343+mmsinclair@users.noreply.github.com> Date: Fri, 18 Aug 2023 16:44:01 +0100 Subject: [PATCH 282/361] Update custom.css --- documentation/dev-portal/custom.css | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/documentation/dev-portal/custom.css b/documentation/dev-portal/custom.css index 0174c7796c..855f5eef5f 100644 --- a/documentation/dev-portal/custom.css +++ b/documentation/dev-portal/custom.css @@ -1,6 +1,5 @@ :root { - --mono-font: "Open Sans", Consolas, "Ubuntu Mono", Menlo, "DejaVu Sans Mono", - monospace, monospace; + --mono-font: Consolas, "Ubuntu Mono", Menlo, "DejaVu Sans Mono", monospace; } .coal { --bg: #121726; @@ -35,4 +34,4 @@ footer { text-align: center; border-top: 1px solid black; padding: 5px 0; -} \ No newline at end of file +} From 4ff69b77a91199a9a2a77c379e1d64bd51b86eed Mon Sep 17 00:00:00 2001 From: Mark Sinclair <14054343+mmsinclair@users.noreply.github.com> Date: Fri, 18 Aug 2023 16:44:38 +0100 Subject: [PATCH 283/361] Update general.css --- documentation/dev-portal/theme/css/general.css | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/documentation/dev-portal/theme/css/general.css b/documentation/dev-portal/theme/css/general.css index 1d77810f5e..f8597e5767 100644 --- a/documentation/dev-portal/theme/css/general.css +++ b/documentation/dev-portal/theme/css/general.css @@ -8,7 +8,7 @@ } html { - /* font-family: "Open Sans", sans-serif; */ + font-family: "Open Sans", sans-serif; color: var(--fg); background-color: var(--bg); text-size-adjust: none; @@ -16,7 +16,6 @@ html { } body { - font-family: "Open Sans", sans-serif; margin: 0; font-size: 1.5rem; overflow-x: hidden; From e0b32e1cc9192d6c6a3fbe17e5e8f009ee800a2a Mon Sep 17 00:00:00 2001 From: mfahampshire Date: Fri, 18 Aug 2023 17:47:48 +0200 Subject: [PATCH 284/361] updated css font --- documentation/docs/custom.css | 3 +-- documentation/operators/custom.css | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/documentation/docs/custom.css b/documentation/docs/custom.css index 170e91dd26..855f5eef5f 100644 --- a/documentation/docs/custom.css +++ b/documentation/docs/custom.css @@ -1,6 +1,5 @@ :root { - --mono-font: "Open Sans", Consolas, "Ubuntu Mono", Menlo, "DejaVu Sans Mono", - monospace, monospace; + --mono-font: Consolas, "Ubuntu Mono", Menlo, "DejaVu Sans Mono", monospace; } .coal { --bg: #121726; diff --git a/documentation/operators/custom.css b/documentation/operators/custom.css index 170e91dd26..855f5eef5f 100644 --- a/documentation/operators/custom.css +++ b/documentation/operators/custom.css @@ -1,6 +1,5 @@ :root { - --mono-font: "Open Sans", Consolas, "Ubuntu Mono", Menlo, "DejaVu Sans Mono", - monospace, monospace; + --mono-font: Consolas, "Ubuntu Mono", Menlo, "DejaVu Sans Mono", monospace; } .coal { --bg: #121726; From 8dfe8f46784b098486b2b6511a5c814a71483227 Mon Sep 17 00:00:00 2001 From: mfahampshire Date: Mon, 21 Aug 2023 09:54:09 +0200 Subject: [PATCH 285/361] version bump in toml files fr docs projects --- documentation/dev-portal/book.toml | 3 +-- documentation/docs/book.toml | 3 +-- documentation/operators/book.toml | 5 +---- 3 files changed, 3 insertions(+), 8 deletions(-) diff --git a/documentation/dev-portal/book.toml b/documentation/dev-portal/book.toml index d9853f839c..5946ace450 100644 --- a/documentation/dev-portal/book.toml +++ b/documentation/dev-portal/book.toml @@ -48,8 +48,7 @@ assets_version = "2.0.0" # do not edit: managed by `mdbook-admonish install` # https://gitlab.com/tglman/mdbook-variables/ [preprocessor.variables.variables] minimum_rust_version = "1.66" -# vars for links: TODO think on how to streamline updating -platform_release_version = "v1.1.27" +platform_release_version = "v1.1.28" wallet_release_version = "v1.2.7" [preprocessor.last-changed] diff --git a/documentation/docs/book.toml b/documentation/docs/book.toml index 6d22440ee3..0305c52c51 100644 --- a/documentation/docs/book.toml +++ b/documentation/docs/book.toml @@ -48,8 +48,7 @@ assets_version = "2.0.0" # do not edit: managed by `mdbook-admonish install` # https://gitlab.com/tglman/mdbook-variables/ [preprocessor.variables.variables] minimum_rust_version = "1.66" -# vars for links: TODO think on how to streamline updating -platform_release_version = "v1.1.27" +platform_release_version = "v1.1.28" wallet_release_version = "v1.2.7" [preprocessor.last-changed] diff --git a/documentation/operators/book.toml b/documentation/operators/book.toml index be7233c069..d6c3a61c5b 100644 --- a/documentation/operators/book.toml +++ b/documentation/operators/book.toml @@ -48,16 +48,13 @@ assets_version = "2.0.0" # do not edit: managed by `mdbook-admonish install` # https://gitlab.com/tglman/mdbook-variables/ [preprocessor.variables.variables] minimum_rust_version = "1.66" -# vars for links: TODO think on how to streamline updating -platform_release_version = "v1.1.27" +platform_release_version = "v1.1.28" wallet_release_version = "v1.2.7" - [preprocessor.last-changed] command = "mdbook-last-changed" renderer = ["html"] - # used for grabbing output of binary commands for automation # https://github.com/FauconFan/mdbook-cmdrun [preprocessor.cmdrun] From c1ba56ab1cc1aecf28fe97236d6a45640017e0a1 Mon Sep 17 00:00:00 2001 From: mfahampshire Date: Mon, 21 Aug 2023 13:59:20 +0200 Subject: [PATCH 286/361] include 2faktor's PR material --- .../docs/src/clients/socks5-client.md | 33 +++++++++++++++++-- 1 file changed, 31 insertions(+), 2 deletions(-) diff --git a/documentation/docs/src/clients/socks5-client.md b/documentation/docs/src/clients/socks5-client.md index e3a5759685..052a52f027 100644 --- a/documentation/docs/src/clients/socks5-client.md +++ b/documentation/docs/src/clients/socks5-client.md @@ -146,8 +146,6 @@ You can set this via the `--host` flag during either the `init` or `run` command Alternatively, a custom host can be set in the `config.toml` file under the `socket` section. If you do this, remember to restart your client process. - - ### Running the socks5 client You can run the initalised client by doing this: @@ -156,6 +154,37 @@ You can run the initalised client by doing this: ./nym-socks5-client run --id docs-example ``` +## Automating your socks5 client with systemd + +Create a service file for the socks5 client at `/etc/systemd/system/nym-socks5-client.service`: + +```ini +[Unit] +Description=Nym Socks5 Client ({{platform_release_version}}) +StartLimitInterval=350 +StartLimitBurst=10 + +[Service] +User=nym # replace this with whatever user you wish +LimitNOFILE=65536 +ExecStart=/home/nym/nym-socks5-client run --id +KillSignal=SIGINT +Restart=on-failure +RestartSec=30 + +[Install] +WantedBy=multi-user.target +``` + +Now enable and start your socks5 client: + +``` +systemctl enable nym-socks5-client.service +systemctl start nym-socks5-client.service +# you can always check your socks5 client has succesfully started with: +systemctl status nym-socks5-client.service +``` + ## Using your Socks5 Client After completing the steps above, your local Socks5 Client will be listening on `localhost:1080` ready to proxy traffic to the Network Requester set as the `--provider` when initialising. From 463033aa64887160a6453499ae394b49402b436e Mon Sep 17 00:00:00 2001 From: mfahampshire Date: Mon, 21 Aug 2023 09:54:09 +0200 Subject: [PATCH 287/361] version bump in toml files fr docs projects --- documentation/dev-portal/book.toml | 3 +-- documentation/docs/book.toml | 3 +-- documentation/operators/book.toml | 5 +---- 3 files changed, 3 insertions(+), 8 deletions(-) diff --git a/documentation/dev-portal/book.toml b/documentation/dev-portal/book.toml index d9853f839c..5946ace450 100644 --- a/documentation/dev-portal/book.toml +++ b/documentation/dev-portal/book.toml @@ -48,8 +48,7 @@ assets_version = "2.0.0" # do not edit: managed by `mdbook-admonish install` # https://gitlab.com/tglman/mdbook-variables/ [preprocessor.variables.variables] minimum_rust_version = "1.66" -# vars for links: TODO think on how to streamline updating -platform_release_version = "v1.1.27" +platform_release_version = "v1.1.28" wallet_release_version = "v1.2.7" [preprocessor.last-changed] diff --git a/documentation/docs/book.toml b/documentation/docs/book.toml index 6d22440ee3..0305c52c51 100644 --- a/documentation/docs/book.toml +++ b/documentation/docs/book.toml @@ -48,8 +48,7 @@ assets_version = "2.0.0" # do not edit: managed by `mdbook-admonish install` # https://gitlab.com/tglman/mdbook-variables/ [preprocessor.variables.variables] minimum_rust_version = "1.66" -# vars for links: TODO think on how to streamline updating -platform_release_version = "v1.1.27" +platform_release_version = "v1.1.28" wallet_release_version = "v1.2.7" [preprocessor.last-changed] diff --git a/documentation/operators/book.toml b/documentation/operators/book.toml index be7233c069..d6c3a61c5b 100644 --- a/documentation/operators/book.toml +++ b/documentation/operators/book.toml @@ -48,16 +48,13 @@ assets_version = "2.0.0" # do not edit: managed by `mdbook-admonish install` # https://gitlab.com/tglman/mdbook-variables/ [preprocessor.variables.variables] minimum_rust_version = "1.66" -# vars for links: TODO think on how to streamline updating -platform_release_version = "v1.1.27" +platform_release_version = "v1.1.28" wallet_release_version = "v1.2.7" - [preprocessor.last-changed] command = "mdbook-last-changed" renderer = ["html"] - # used for grabbing output of binary commands for automation # https://github.com/FauconFan/mdbook-cmdrun [preprocessor.cmdrun] From 24099d159bd29662dea7b4aea2cb7664693497aa Mon Sep 17 00:00:00 2001 From: mfahampshire Date: Mon, 21 Aug 2023 14:02:29 +0200 Subject: [PATCH 288/361] change minibolt description --- .../community-resources/community-applications-and-guides.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/documentation/dev-portal/src/community-resources/community-applications-and-guides.md b/documentation/dev-portal/src/community-resources/community-applications-and-guides.md index 90e0801e80..5186dceae1 100644 --- a/documentation/dev-portal/src/community-resources/community-applications-and-guides.md +++ b/documentation/dev-portal/src/community-resources/community-applications-and-guides.md @@ -87,7 +87,7 @@ We love seeing our developer community create applications using Nym. If you wou ## Minibolt -> Anonymize your p2p inventory messages and mempool for your Bitcoin & Lightning full nodes on consumer PCs! +> Proxies the clearnet connections of a Bitcoin node and its associated tools using the NYM mixnet. * [Github](https://github.com/minibolt-guide/minibolt) * [Documentation](https://v2.minibolt.info/bonus-guides/system/nym-mixnet#proxying-bitcoin-core) From d4920b82f0193d7b50dd090d79b18612e45c9c32 Mon Sep 17 00:00:00 2001 From: mfahampshire Date: Mon, 21 Aug 2023 13:59:20 +0200 Subject: [PATCH 289/361] include 2faktor's PR material --- .../docs/src/clients/socks5-client.md | 33 +++++++++++++++++-- 1 file changed, 31 insertions(+), 2 deletions(-) diff --git a/documentation/docs/src/clients/socks5-client.md b/documentation/docs/src/clients/socks5-client.md index e3a5759685..052a52f027 100644 --- a/documentation/docs/src/clients/socks5-client.md +++ b/documentation/docs/src/clients/socks5-client.md @@ -146,8 +146,6 @@ You can set this via the `--host` flag during either the `init` or `run` command Alternatively, a custom host can be set in the `config.toml` file under the `socket` section. If you do this, remember to restart your client process. - - ### Running the socks5 client You can run the initalised client by doing this: @@ -156,6 +154,37 @@ You can run the initalised client by doing this: ./nym-socks5-client run --id docs-example ``` +## Automating your socks5 client with systemd + +Create a service file for the socks5 client at `/etc/systemd/system/nym-socks5-client.service`: + +```ini +[Unit] +Description=Nym Socks5 Client ({{platform_release_version}}) +StartLimitInterval=350 +StartLimitBurst=10 + +[Service] +User=nym # replace this with whatever user you wish +LimitNOFILE=65536 +ExecStart=/home/nym/nym-socks5-client run --id +KillSignal=SIGINT +Restart=on-failure +RestartSec=30 + +[Install] +WantedBy=multi-user.target +``` + +Now enable and start your socks5 client: + +``` +systemctl enable nym-socks5-client.service +systemctl start nym-socks5-client.service +# you can always check your socks5 client has succesfully started with: +systemctl status nym-socks5-client.service +``` + ## Using your Socks5 Client After completing the steps above, your local Socks5 Client will be listening on `localhost:1080` ready to proxy traffic to the Network Requester set as the `--provider` when initialising. From 6d3b198f006e728267653881c05f5936e6ff73fb Mon Sep 17 00:00:00 2001 From: mfahampshire Date: Mon, 21 Aug 2023 14:02:29 +0200 Subject: [PATCH 290/361] change minibolt description --- .../community-resources/community-applications-and-guides.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/documentation/dev-portal/src/community-resources/community-applications-and-guides.md b/documentation/dev-portal/src/community-resources/community-applications-and-guides.md index 90e0801e80..5186dceae1 100644 --- a/documentation/dev-portal/src/community-resources/community-applications-and-guides.md +++ b/documentation/dev-portal/src/community-resources/community-applications-and-guides.md @@ -87,7 +87,7 @@ We love seeing our developer community create applications using Nym. If you wou ## Minibolt -> Anonymize your p2p inventory messages and mempool for your Bitcoin & Lightning full nodes on consumer PCs! +> Proxies the clearnet connections of a Bitcoin node and its associated tools using the NYM mixnet. * [Github](https://github.com/minibolt-guide/minibolt) * [Documentation](https://v2.minibolt.info/bonus-guides/system/nym-mixnet#proxying-bitcoin-core) From fa9908413badea0215ffc51f4a37963d98fd15af Mon Sep 17 00:00:00 2001 From: mfahampshire Date: Mon, 21 Aug 2023 15:40:03 +0200 Subject: [PATCH 291/361] minimal first version bump script --- documentation/bump_versions.sh | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100755 documentation/bump_versions.sh diff --git a/documentation/bump_versions.sh b/documentation/bump_versions.sh new file mode 100755 index 0000000000..507af1492e --- /dev/null +++ b/documentation/bump_versions.sh @@ -0,0 +1,17 @@ +#!/bin/bash +# this takes two args: platform release version and wallet release version. +# it then uses sed to bump them in the three book.toml files. +# +# e.g if the upcoming platform release was v1.1.29 and the release version 1.2.9 you'd run this as: +# `./bump_versions.sh "1.1.29" "1.2.9"` + +# array of project dirs +declare -a projects=("docs" "dev-portal" "operators") + +## now loop through the above array sed-ing the variable values in the book.toml files +for i in "${projects[@]}" +do + # sed the vars in the book.toml file for each project + sed -i 's/platform_release_version =.*/platform_release_version = "'$1'"/' "$i"/book.toml + sed -i 's/wallet_release_version =.*/wallet_release_version = "'$2'"/' "$i"/book.toml +done \ No newline at end of file From 067a501d980ca71472d74de9493c525e699659ae Mon Sep 17 00:00:00 2001 From: mfahampshire Date: Mon, 21 Aug 2023 15:40:14 +0200 Subject: [PATCH 292/361] added docs --- documentation/build_all_to_dist.sh | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/documentation/build_all_to_dist.sh b/documentation/build_all_to_dist.sh index 721ed07e6a..71310bbb5e 100755 --- a/documentation/build_all_to_dist.sh +++ b/documentation/build_all_to_dist.sh @@ -1,6 +1,9 @@ #!/bin/bash - -# commands assume you run script from `nym/documentation/` +# this is a script called by the github CI and CD workflows to build all 3 docs projects +# and move them to /dist/ in the root of the monorepo. They are rsynced to various servers +# from there by the CI and CD workflows. +# +# these commands assume the script is run from `nym/documentation/` # array of project dirs declare -a projects=("docs" "dev-portal" "operators") @@ -34,4 +37,5 @@ do # cd back to ../documentation/ cd ../ done +# rename for server paths mv ../dist/docs/dev-portal ../dist/docs/developers From dffe171b7f060898777b23313463436b7b983ed1 Mon Sep 17 00:00:00 2001 From: Pierre Dommerc Date: Mon, 21 Aug 2023 15:50:18 +0200 Subject: [PATCH 293/361] feat(explorer-api): add gateway geoip location (#3785) * feat(explorer-api): add gateway geoip location * fix lint --- explorer-api/explorer-api-requests/src/lib.rs | 12 ++- .../src/country_statistics/geolocate.rs | 67 +++++++++++++++- explorer-api/src/gateways/http.rs | 6 +- explorer-api/src/gateways/location.rs | 8 ++ explorer-api/src/gateways/mod.rs | 1 + explorer-api/src/gateways/models.rs | 80 +++++++++++++++++-- explorer-api/src/location.rs | 40 ++++++++++ explorer-api/src/main.rs | 1 + explorer-api/src/mix_nodes/location.rs | 37 +-------- explorer-api/src/mix_nodes/models.rs | 11 +-- explorer-api/src/mix_nodes/utils.rs | 4 +- explorer-api/src/state.rs | 17 ++-- nym-api/src/network/mod.rs | 4 +- 13 files changed, 228 insertions(+), 60 deletions(-) create mode 100644 explorer-api/src/gateways/location.rs create mode 100644 explorer-api/src/location.rs diff --git a/explorer-api/explorer-api-requests/src/lib.rs b/explorer-api/explorer-api-requests/src/lib.rs index 6180d65f11..2109348899 100644 --- a/explorer-api/explorer-api-requests/src/lib.rs +++ b/explorer-api/explorer-api-requests/src/lib.rs @@ -1,6 +1,6 @@ use nym_api_requests::models::NodePerformance; use nym_contracts_common::Percent; -use nym_mixnet_contract_common::{Addr, Coin, Layer, MixId, MixNode}; +use nym_mixnet_contract_common::{Addr, Coin, Gateway, Layer, MixId, MixNode}; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; @@ -42,3 +42,13 @@ pub struct Location { pub latitude: Option, pub longitude: Option, } + +#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)] +pub struct PrettyDetailedGatewayBond { + pub pledge_amount: Coin, + pub owner: Addr, + pub block_height: u64, + pub gateway: Gateway, + pub proxy: Option, + pub location: Option, +} diff --git a/explorer-api/src/country_statistics/geolocate.rs b/explorer-api/src/country_statistics/geolocate.rs index a80ebc28e4..858f593986 100644 --- a/explorer-api/src/country_statistics/geolocate.rs +++ b/explorer-api/src/country_statistics/geolocate.rs @@ -17,13 +17,14 @@ impl GeoLocateTask { } pub(crate) fn start(mut self) { - info!("Spawning mix node locator task runner..."); + info!("Spawning locator task runner..."); tokio::spawn(async move { let mut interval_timer = tokio::time::interval(std::time::Duration::from_millis(50)); while !self.shutdown.is_shutdown() { tokio::select! { _ = interval_timer.tick() => { self.locate_mix_nodes().await; + self.locate_gateways().await; } _ = self.shutdown.recv() => { trace!("Listener: Received shutdown"); @@ -107,4 +108,68 @@ impl GeoLocateTask { trace!("All mix nodes located"); } + + async fn locate_gateways(&mut self) { + let gateways = self.state.inner.gateways.get_gateways().await; + + let geo_ip = self.state.inner.geo_ip.0.clone(); + + for (i, cache_item) in gateways.iter().enumerate() { + if self + .state + .inner + .gateways + .is_location_valid(cache_item.identity().to_owned()) + .await + { + // when the cached location is valid, don't locate and continue to next gateway + continue; + } + + match geo_ip.query(&cache_item.gateway.host, Some(cache_item.gateway.mix_port)) { + Ok(opt) => match opt { + Some(location) => { + let location: Location = location.into(); + + trace!( + "{} gateways already located. Ip {} is located in {:#?}", + i, + cache_item.gateway.host, + location.three_letter_iso_country_code, + ); + + if i > 0 && (i % 100) == 0 { + info!("Located {} gateways...", i + 1,); + } + + self.state + .inner + .gateways + .set_location(cache_item.identity().to_owned(), Some(location)) + .await; + + // one node has been located, so return out of the loop + return; + } + None => { + warn!("❌ Location for {} not found.", cache_item.gateway.host); + self.state + .inner + .gateways + .set_location(cache_item.identity().to_owned(), None) + .await; + } + }, + Err(_e) => { + // warn!( + // "❌ Oh no! Location for {} failed. Error: {:#?}", + // cache_item.gateway.host, + // e + // ); + } + }; + } + + trace!("All gateways located"); + } } diff --git a/explorer-api/src/gateways/http.rs b/explorer-api/src/gateways/http.rs index 1f494d0edd..ac0d85c0b1 100644 --- a/explorer-api/src/gateways/http.rs +++ b/explorer-api/src/gateways/http.rs @@ -1,6 +1,7 @@ // Copyright 2022 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 +use nym_explorer_api_requests::PrettyDetailedGatewayBond; use rocket::response::status::NotFound; use rocket::serde::json::Json; use rocket::{Route, State}; @@ -9,7 +10,6 @@ use rocket_okapi::openapi_get_routes_spec; use rocket_okapi::settings::OpenApiSettings; use crate::state::ExplorerApiStateContext; -use nym_mixnet_contract_common::GatewayBond; pub fn gateways_make_default_routes(settings: &OpenApiSettings) -> (Vec, OpenApi) { openapi_get_routes_spec![settings: list] @@ -19,6 +19,6 @@ pub fn gateways_make_default_routes(settings: &OpenApiSettings) -> (Vec, #[get("/")] pub(crate) async fn list( state: &State, -) -> Result>, NotFound> { - Ok(Json(state.inner.gateways.get_gateways().await)) +) -> Result>, NotFound> { + Ok(Json(state.inner.gateways.get_detailed_gateways().await)) } diff --git a/explorer-api/src/gateways/location.rs b/explorer-api/src/gateways/location.rs new file mode 100644 index 0000000000..68420720b3 --- /dev/null +++ b/explorer-api/src/gateways/location.rs @@ -0,0 +1,8 @@ +// Copyright 2021 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use nym_contracts_common::IdentityKey; + +use crate::location::LocationCache; + +pub(crate) type GatewayLocationCache = LocationCache; diff --git a/explorer-api/src/gateways/mod.rs b/explorer-api/src/gateways/mod.rs index 5df938f83c..cbce7ef930 100644 --- a/explorer-api/src/gateways/mod.rs +++ b/explorer-api/src/gateways/mod.rs @@ -1,2 +1,3 @@ pub(crate) mod http; +pub(crate) mod location; pub(crate) mod models; diff --git a/explorer-api/src/gateways/models.rs b/explorer-api/src/gateways/models.rs index ab2712c3fc..a3ed406067 100644 --- a/explorer-api/src/gateways/models.rs +++ b/explorer-api/src/gateways/models.rs @@ -1,12 +1,15 @@ // Copyright 2022 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use crate::cache::Cache; +use crate::{cache::Cache, location::LocationCacheItem}; +use nym_explorer_api_requests::{Location, PrettyDetailedGatewayBond}; use nym_mixnet_contract_common::{GatewayBond, IdentityKey}; use serde::Serialize; -use std::sync::Arc; +use std::{sync::Arc, time::SystemTime}; use tokio::sync::RwLock; +use super::location::GatewayLocationCache; + pub(crate) struct GatewayCache { pub(crate) gateways: Cache, } @@ -18,30 +21,93 @@ pub(crate) struct GatewaySummary { #[derive(Clone)] pub(crate) struct ThreadsafeGatewayCache { - inner: Arc>, + gateways: Arc>, + locations: Arc>, } impl ThreadsafeGatewayCache { pub(crate) fn new() -> Self { ThreadsafeGatewayCache { - inner: Arc::new(RwLock::new(GatewayCache { + gateways: Arc::new(RwLock::new(GatewayCache { gateways: Cache::new(), })), + locations: Arc::new(RwLock::new(GatewayLocationCache::new())), + } + } + + fn create_detailed_gateway( + &self, + bond: GatewayBond, + location: Option<&LocationCacheItem>, + ) -> PrettyDetailedGatewayBond { + PrettyDetailedGatewayBond { + pledge_amount: bond.pledge_amount, + owner: bond.owner, + block_height: bond.block_height, + gateway: bond.gateway, + proxy: bond.proxy, + location: location.and_then(|l| l.location.clone()), } } pub(crate) async fn get_gateways(&self) -> Vec { - self.inner.read().await.gateways.get_all() + self.gateways.read().await.gateways.get_all() + } + + pub(crate) async fn get_detailed_gateways(&self) -> Vec { + let gateways_guard = self.gateways.read().await; + let location_guard = self.locations.read().await; + + gateways_guard + .gateways + .get_all() + .iter() + .map(|bond| { + let location = location_guard.get(bond.identity()); + self.create_detailed_gateway(bond.to_owned(), location) + }) + .collect() } pub(crate) async fn get_gateway_summary(&self) -> GatewaySummary { GatewaySummary { - count: self.inner.read().await.gateways.len(), + count: self.gateways.read().await.gateways.len(), } } + pub(crate) fn new_with_location_cache(locations: GatewayLocationCache) -> Self { + ThreadsafeGatewayCache { + gateways: Arc::new(RwLock::new(GatewayCache { + gateways: Cache::new(), + })), + locations: Arc::new(RwLock::new(locations)), + } + } + + pub(crate) async fn is_location_valid(&self, identity_key: IdentityKey) -> bool { + self.locations + .read() + .await + .get(&identity_key) + .map_or(false, |cache_item| { + cache_item.valid_until > SystemTime::now() + }) + } + + pub(crate) async fn get_locations(&self) -> GatewayLocationCache { + self.locations.read().await.clone() + } + + pub(crate) async fn set_location(&self, identy_key: IdentityKey, location: Option) { + // cache the location for this mix node so that it can be used when the mix node list is refreshed + self.locations + .write() + .await + .insert(identy_key, LocationCacheItem::new_from_location(location)); + } + pub(crate) async fn update_cache(&self, gateways: Vec) { - let mut guard = self.inner.write().await; + let mut guard = self.gateways.write().await; for gateway in gateways { guard diff --git a/explorer-api/src/location.rs b/explorer-api/src/location.rs new file mode 100644 index 0000000000..d11572e631 --- /dev/null +++ b/explorer-api/src/location.rs @@ -0,0 +1,40 @@ +// Copyright 2021 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use nym_explorer_api_requests::Location; +use serde::{Deserialize, Serialize}; +use std::collections::HashMap; +use std::time::{Duration, SystemTime}; + +pub(crate) type LocationCache = HashMap; + +#[allow(dead_code)] +#[derive(Debug, Deserialize)] +pub(crate) struct GeoLocation { + pub(crate) ip: String, + pub(crate) country_code: String, + pub(crate) country_name: String, + pub(crate) region_code: String, + pub(crate) region_name: String, + pub(crate) city: String, + pub(crate) zip_code: String, + pub(crate) time_zone: String, + pub(crate) latitude: f32, + pub(crate) longitude: f32, + pub(crate) metro_code: u32, +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub(crate) struct LocationCacheItem { + pub(crate) location: Option, + pub(crate) valid_until: SystemTime, +} + +impl LocationCacheItem { + pub(crate) fn new_from_location(location: Option) -> Self { + LocationCacheItem { + location, + valid_until: SystemTime::now() + Duration::from_secs(60 * 60 * 24), // valid for 1 day + } + } +} diff --git a/explorer-api/src/main.rs b/explorer-api/src/main.rs index 676f1c5cea..0df77aeaf9 100644 --- a/explorer-api/src/main.rs +++ b/explorer-api/src/main.rs @@ -19,6 +19,7 @@ mod geo_ip; mod guards; mod helpers; mod http; +mod location; mod mix_node; pub(crate) mod mix_nodes; mod overview; diff --git a/explorer-api/src/mix_nodes/location.rs b/explorer-api/src/mix_nodes/location.rs index 4908564697..de3c2e2fd6 100644 --- a/explorer-api/src/mix_nodes/location.rs +++ b/explorer-api/src/mix_nodes/location.rs @@ -1,41 +1,8 @@ // Copyright 2021 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use nym_explorer_api_requests::Location; use nym_mixnet_contract_common::MixId; -use serde::{Deserialize, Serialize}; -use std::collections::HashMap; -use std::time::{Duration, SystemTime}; -pub(crate) type LocationCache = HashMap; +use crate::location::LocationCache; -#[allow(dead_code)] -#[derive(Debug, Deserialize)] -pub(crate) struct GeoLocation { - pub(crate) ip: String, - pub(crate) country_code: String, - pub(crate) country_name: String, - pub(crate) region_code: String, - pub(crate) region_name: String, - pub(crate) city: String, - pub(crate) zip_code: String, - pub(crate) time_zone: String, - pub(crate) latitude: f32, - pub(crate) longitude: f32, - pub(crate) metro_code: u32, -} - -#[derive(Clone, Debug, Serialize, Deserialize)] -pub(crate) struct LocationCacheItem { - pub(crate) location: Option, - pub(crate) valid_until: SystemTime, -} - -impl LocationCacheItem { - pub(crate) fn new_from_location(location: Option) -> Self { - LocationCacheItem { - location, - valid_until: SystemTime::now() + Duration::from_secs(60 * 60 * 24), // valid for 1 day - } - } -} +pub(crate) type MixnodeLocationCache = LocationCache; diff --git a/explorer-api/src/mix_nodes/models.rs b/explorer-api/src/mix_nodes/models.rs index b107bb2168..fe5ae7fde8 100644 --- a/explorer-api/src/mix_nodes/models.rs +++ b/explorer-api/src/mix_nodes/models.rs @@ -12,10 +12,11 @@ use serde::Serialize; use tokio::sync::{RwLock, RwLockReadGuard}; use crate::helpers::best_effort_small_dec_to_f64; +use crate::location::LocationCacheItem; use nym_validator_client::models::MixNodeBondAnnotated; +use super::location::MixnodeLocationCache; use super::utils::family_numerical_id; -use crate::mix_nodes::location::{LocationCache, LocationCacheItem}; use crate::mix_nodes::CACHE_ENTRY_TTL; #[derive(Clone, Debug, Serialize, JsonSchema)] @@ -83,18 +84,18 @@ impl MixNodesResult { #[derive(Clone)] pub(crate) struct ThreadsafeMixNodesCache { mixnodes: Arc>, - locations: Arc>, + locations: Arc>, } impl ThreadsafeMixNodesCache { pub(crate) fn new() -> Self { ThreadsafeMixNodesCache { mixnodes: Arc::new(RwLock::new(MixNodesResult::new())), - locations: Arc::new(RwLock::new(LocationCache::new())), + locations: Arc::new(RwLock::new(MixnodeLocationCache::new())), } } - pub(crate) fn new_with_location_cache(locations: LocationCache) -> Self { + pub(crate) fn new_with_location_cache(locations: MixnodeLocationCache) -> Self { ThreadsafeMixNodesCache { mixnodes: Arc::new(RwLock::new(MixNodesResult::new())), locations: Arc::new(RwLock::new(locations)), @@ -111,7 +112,7 @@ impl ThreadsafeMixNodesCache { }) } - pub(crate) async fn get_locations(&self) -> LocationCache { + pub(crate) async fn get_locations(&self) -> MixnodeLocationCache { self.locations.read().await.clone() } diff --git a/explorer-api/src/mix_nodes/utils.rs b/explorer-api/src/mix_nodes/utils.rs index 0f7eb6b991..5273d66f55 100644 --- a/explorer-api/src/mix_nodes/utils.rs +++ b/explorer-api/src/mix_nodes/utils.rs @@ -1,13 +1,13 @@ // Copyright 2022 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 +use isocountry::CountryCode; use nym_mixnet_contract_common::families::FamilyHead; use rand::Rng; use rand_pcg::Pcg64; use rand_seeder::Seeder; -use crate::mix_nodes::location::GeoLocation; -use isocountry::CountryCode; +use crate::location::GeoLocation; #[allow(dead_code)] pub(crate) fn map_2_letter_to_3_letter_country_code(geo: &GeoLocation) -> String { diff --git a/explorer-api/src/state.rs b/explorer-api/src/state.rs index 0011a924d3..8ecee92ae2 100644 --- a/explorer-api/src/state.rs +++ b/explorer-api/src/state.rs @@ -13,9 +13,10 @@ use nym_validator_client::models::MixNodeBondAnnotated; use crate::country_statistics::country_nodes_distribution::{ CountryNodesDistribution, ThreadsafeCountryNodesDistribution, }; +use crate::gateways::location::GatewayLocationCache; use crate::gateways::models::ThreadsafeGatewayCache; use crate::mix_node::models::ThreadsafeMixNodeCache; -use crate::mix_nodes::location::LocationCache; +use crate::mix_nodes::location::MixnodeLocationCache; use crate::mix_nodes::models::ThreadsafeMixNodesCache; use crate::ping::models::ThreadsafePingCache; use crate::validators::models::ThreadsafeValidatorCache; @@ -46,7 +47,8 @@ impl ExplorerApiState { #[derive(Debug, Serialize, Deserialize)] pub struct ExplorerApiStateOnDisk { pub(crate) country_node_distribution: CountryNodesDistribution, - pub(crate) location_cache: LocationCache, + pub(crate) mixnode_location_cache: MixnodeLocationCache, + pub(crate) gateway_location_cache: GatewayLocationCache, pub(crate) as_at: DateTime, } @@ -76,9 +78,13 @@ impl ExplorerApiStateContext { ThreadsafeCountryNodesDistribution::new_from_distribution( state.country_node_distribution, ), - gateways: ThreadsafeGatewayCache::new(), + gateways: ThreadsafeGatewayCache::new_with_location_cache( + state.gateway_location_cache, + ), mixnode: ThreadsafeMixNodeCache::new(), - mixnodes: ThreadsafeMixNodesCache::new_with_location_cache(state.location_cache), + mixnodes: ThreadsafeMixNodesCache::new_with_location_cache( + state.mixnode_location_cache, + ), ping: ThreadsafePingCache::new(), validators: ThreadsafeValidatorCache::new(), validator_client: ThreadsafeValidatorClient::new(), @@ -109,7 +115,8 @@ impl ExplorerApiStateContext { let file = File::create(json_file_path).expect("unable to create state json file"); let state = ExplorerApiStateOnDisk { country_node_distribution: self.inner.country_node_distribution.get_all().await, - location_cache: self.inner.mixnodes.get_locations().await, + mixnode_location_cache: self.inner.mixnodes.get_locations().await, + gateway_location_cache: self.inner.gateways.get_locations().await, as_at: Utc::now(), }; serde_json::to_writer(file, &state).expect("error writing state to disk"); diff --git a/nym-api/src/network/mod.rs b/nym-api/src/network/mod.rs index f06bc5bf16..b0264f092c 100644 --- a/nym-api/src/network/mod.rs +++ b/nym-api/src/network/mod.rs @@ -11,6 +11,8 @@ mod routes; pub(crate) fn network_routes(settings: &OpenApiSettings) -> (Vec, OpenApi) { openapi_get_routes_spec![ - settings: routes::network_details, routes::nym_contracts, routes::nym_contracts_detailed + settings: routes::network_details, + routes::nym_contracts, + routes::nym_contracts_detailed ] } From 85ab3d5c809d4f536a4379a29a43dc7cfb123a85 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bogdan-=C8=98tefan=20Neac=C8=99u?= Date: Mon, 21 Aug 2023 16:46:40 +0300 Subject: [PATCH 294/361] Include 1.1.28 upgrade for ephemera config template --- nym-api/src/support/config/helpers.rs | 18 ++ nym-api/src/support/config/mod.rs | 1 + .../src/support/config/old_config_v1_1_27.rs | 268 ++++++++++++++++++ 3 files changed, 287 insertions(+) create mode 100644 nym-api/src/support/config/old_config_v1_1_27.rs diff --git a/nym-api/src/support/config/helpers.rs b/nym-api/src/support/config/helpers.rs index 4c20088ad0..1c84a5e837 100644 --- a/nym-api/src/support/config/helpers.rs +++ b/nym-api/src/support/config/helpers.rs @@ -2,6 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 use crate::support::config::old_config_v1_1_21::ConfigV1_1_21; +use crate::support::config::old_config_v1_1_27::ConfigV1_1_27; use crate::support::config::{default_config_directory, default_data_directory, Config}; use anyhow::Result; use std::{fs, io}; @@ -22,6 +23,22 @@ fn try_upgrade_v1_1_21_config(id: &str) -> Result<()> { Ok(updated.save_to_default_location()?) } +fn try_upgrade_v1_1_27_config(id: &str) -> Result<()> { + use nym_config::legacy_helpers::nym_config::MigrationNymConfig; + + // explicitly load it as v1.1.27 (which is incompatible with the current, i.e. 1.1.28+) + let Ok(old_config) = ConfigV1_1_27::load_from_file(id) else { + // if we failed to load it, there might have been nothing to upgrade + // or maybe it was an even older file. in either way. just ignore it and carry on with our day + return Ok(()); + }; + info!("It seems the nym-api is using <= v1.1.27 config template."); + info!("It is going to get updated to the current specification."); + + let updated: Config = old_config.into(); + Ok(updated.save_to_default_location()?) +} + fn init_paths(id: &str) -> io::Result<()> { fs::create_dir_all(default_data_directory(id))?; fs::create_dir_all(default_config_directory(id)) @@ -36,6 +53,7 @@ pub(crate) fn initialise_new(id: &str) -> Result { pub(crate) fn try_load_current_config(id: &str) -> Result { try_upgrade_v1_1_21_config(id)?; + try_upgrade_v1_1_27_config(id)?; Ok(Config::read_from_default_path(id)?) } diff --git a/nym-api/src/support/config/mod.rs b/nym-api/src/support/config/mod.rs index bc4bdd7b54..a66c5ce8a7 100644 --- a/nym-api/src/support/config/mod.rs +++ b/nym-api/src/support/config/mod.rs @@ -20,6 +20,7 @@ use zeroize::{Zeroize, ZeroizeOnDrop}; pub(crate) mod helpers; pub(crate) mod old_config_v1_1_21; +pub(crate) mod old_config_v1_1_27; mod persistence; mod template; diff --git a/nym-api/src/support/config/old_config_v1_1_27.rs b/nym-api/src/support/config/old_config_v1_1_27.rs new file mode 100644 index 0000000000..3bfb4b8581 --- /dev/null +++ b/nym-api/src/support/config/old_config_v1_1_27.rs @@ -0,0 +1,268 @@ +// Copyright 2023 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use crate::support::config::persistence::{ + CoconutSignerPaths, NetworkMonitorPaths, NodeStatusAPIPaths, +}; +use crate::support::config::{ + Base, CirculatingSupplyCacher, CoconutSigner, CoconutSignerDebug, Config, Ephemera, + NetworkMonitor, NetworkMonitorDebug, NodeStatusAPI, NodeStatusAPIDebug, Rewarding, + TopologyCacher, +}; +use nym_config::legacy_helpers::nym_config::MigrationNymConfig; +use nym_validator_client::nyxd; +use serde::{Deserialize, Serialize}; +use std::path::PathBuf; +use url::Url; + +const DEFAULT_NYM_API_PORT: u16 = 8080; +const MIXNET_CONTRACT_ADDRESS: &str = + "n17srjznxl9dvzdkpwpw24gg668wc73val88a6m5ajg6ankwvz9wtst0cznr"; +const VESTING_CONTRACT_ADDRESS: &str = + "n1nc5tatafv6eyq7llkr2gv50ff9e22mnf70qgjlv737ktmt4eswrq73f2nw"; + +const DEFAULT_LOCAL_VALIDATOR: &str = "http://localhost:26657"; + +#[derive(Debug, Default, Deserialize, PartialEq, Eq, Serialize)] +#[serde(deny_unknown_fields)] +pub struct ConfigV1_1_27 { + #[serde(default)] + base: BaseV1_1_27, + + #[serde(default)] + network_monitor: NetworkMonitorV1_1_27, + + #[serde(default)] + node_status_api: NodeStatusAPIV1_1_27, + + #[serde(default)] + topology_cacher: TopologyCacher, + + #[serde(default)] + circulating_supply_cacher: CirculatingSupplyCacher, + + #[serde(default)] + rewarding: Rewarding, + + #[serde(default)] + coconut_signer: CoconutSignerV1_1_27, +} + +impl From for Config { + fn from(value: ConfigV1_1_27) -> Self { + // this value was never properly saved (probably a bug) + // so explicitly set it to the default + + Config { + base: Base { + id: value.base.id.clone(), + local_validator: value.base.local_validator, + mixnet_contract_address: value.base.mixnet_contract_address, + vesting_contract_address: value.base.vesting_contract_address, + mnemonic: value.base.mnemonic, + }, + network_monitor: NetworkMonitor { + enabled: value.network_monitor.enabled, + storage_paths: NetworkMonitorPaths { + credentials_database_path: value + .network_monitor + .storage_paths + .credentials_database_path, + }, + debug: NetworkMonitorDebug { + min_mixnode_reliability: value.network_monitor.debug.min_mixnode_reliability, + min_gateway_reliability: value.network_monitor.debug.min_gateway_reliability, + disabled_credentials_mode: value + .network_monitor + .debug + .disabled_credentials_mode, + run_interval: value.network_monitor.debug.run_interval, + gateway_ping_interval: value.network_monitor.debug.gateway_ping_interval, + gateway_sending_rate: value.network_monitor.debug.gateway_sending_rate, + max_concurrent_gateway_clients: value + .network_monitor + .debug + .max_concurrent_gateway_clients, + gateway_response_timeout: value.network_monitor.debug.gateway_response_timeout, + gateway_connection_timeout: value + .network_monitor + .debug + .gateway_connection_timeout, + packet_delivery_timeout: value.network_monitor.debug.packet_delivery_timeout, + test_routes: value.network_monitor.debug.test_routes, + minimum_test_routes: value.network_monitor.debug.minimum_test_routes, + route_test_packets: value.network_monitor.debug.route_test_packets, + per_node_test_packets: value.network_monitor.debug.per_node_test_packets, + }, + }, + node_status_api: NodeStatusAPI { + storage_paths: NodeStatusAPIPaths { + database_path: value.node_status_api.storage_paths.database_path, + }, + debug: NodeStatusAPIDebug { + caching_interval: value.node_status_api.debug.caching_interval, + }, + }, + topology_cacher: value.topology_cacher, + circulating_supply_cacher: value.circulating_supply_cacher, + rewarding: value.rewarding, + coconut_signer: CoconutSigner { + enabled: value.coconut_signer.enabled, + announce_address: value.base.announce_address, + storage_paths: CoconutSignerPaths { + dkg_persistent_state_path: value + .coconut_signer + .storage_paths + .dkg_persistent_state_path, + verification_key_path: value.coconut_signer.storage_paths.verification_key_path, + secret_key_path: value.coconut_signer.storage_paths.secret_key_path, + decryption_key_path: value.coconut_signer.storage_paths.decryption_key_path, + public_key_with_proof_path: value + .coconut_signer + .storage_paths + .public_key_with_proof_path, + }, + debug: CoconutSignerDebug { + dkg_contract_polling_rate: value.coconut_signer.debug.dkg_contract_polling_rate, + }, + }, + ephemera: Ephemera::new_default(&value.base.id), + } + } +} + +impl MigrationNymConfig for ConfigV1_1_27 { + fn default_root_directory() -> PathBuf { + dirs::home_dir() + .expect("Failed to evaluate $HOME value") + .join(".nym") + .join("nym-api") + } +} + +#[derive(Debug, Deserialize, PartialEq, Eq, Serialize)] +#[serde(default)] +#[serde(deny_unknown_fields)] +pub struct BaseV1_1_27 { + /// ID specifies the human readable ID of this particular nym-api. + id: String, + + local_validator: Url, + + /// Address announced to the directory server for the clients to connect to. + // It is useful, say, in NAT scenarios or wanting to more easily update actual IP address + // later on by using name resolvable with a DNS query, such as `nymtech.net`. + announce_address: Url, + + /// Address of the validator contract managing the network + mixnet_contract_address: nyxd::AccountId, + + /// Address of the vesting contract holding locked tokens + vesting_contract_address: nyxd::AccountId, + + /// Mnemonic used for rewarding and/or multisig operations + mnemonic: bip39::Mnemonic, +} + +impl Default for BaseV1_1_27 { + fn default() -> Self { + let default_validator: Url = DEFAULT_LOCAL_VALIDATOR + .parse() + .expect("default local validator is malformed!"); + let mut default_announce_address = default_validator.clone(); + default_announce_address + .set_port(Some(DEFAULT_NYM_API_PORT)) + .expect("default local validator is malformed!"); + + BaseV1_1_27 { + id: String::default(), + local_validator: default_validator, + announce_address: default_announce_address, + mixnet_contract_address: MIXNET_CONTRACT_ADDRESS.parse().unwrap(), + vesting_contract_address: VESTING_CONTRACT_ADDRESS.parse().unwrap(), + mnemonic: bip39::Mnemonic::generate(24).unwrap(), + } + } +} + +#[derive(Debug, Deserialize, PartialEq, Eq, Serialize)] +#[serde(default)] +#[serde(deny_unknown_fields)] +pub struct NetworkMonitorV1_1_27 { + /// Specifies whether network monitoring service is enabled in this process. + pub enabled: bool, + + pub storage_paths: NetworkMonitorPaths, + + #[serde(default)] + pub debug: NetworkMonitorDebug, +} + +impl Default for NetworkMonitorV1_1_27 { + fn default() -> Self { + NetworkMonitorV1_1_27 { + enabled: false, + storage_paths: NetworkMonitorPaths { + credentials_database_path: Default::default(), + }, + debug: Default::default(), + } + } +} + +#[derive(Debug, Deserialize, PartialEq, Eq, Serialize)] +#[serde(default)] +#[serde(deny_unknown_fields)] +pub struct NodeStatusAPIV1_1_27 { + // pub enabled: bool, + pub storage_paths: NodeStatusAPIPaths, + + #[serde(default)] + pub debug: NodeStatusAPIDebug, +} + +impl Default for NodeStatusAPIV1_1_27 { + fn default() -> Self { + NodeStatusAPIV1_1_27 { + storage_paths: NodeStatusAPIPaths { + database_path: Default::default(), + }, + debug: Default::default(), + } + } +} + +#[derive(Debug, Deserialize, PartialEq, Eq, Serialize)] +#[serde(default)] +#[serde(deny_unknown_fields)] +pub struct CoconutSignerV1_1_27 { + /// Specifies whether rewarding service is enabled in this process. + pub enabled: bool, + + pub announce_address: Url, + + pub storage_paths: CoconutSignerPaths, + + #[serde(default)] + pub debug: CoconutSignerDebug, +} + +impl Default for CoconutSignerV1_1_27 { + fn default() -> Self { + let announce_address: Url = DEFAULT_LOCAL_VALIDATOR + .parse() + .expect("default local validator is malformed!"); + CoconutSignerV1_1_27 { + enabled: Default::default(), + announce_address, + storage_paths: CoconutSignerPaths { + dkg_persistent_state_path: Default::default(), + verification_key_path: Default::default(), + secret_key_path: Default::default(), + decryption_key_path: Default::default(), + public_key_with_proof_path: Default::default(), + }, + debug: Default::default(), + } + } +} From 5b86646bd8a3605ea6a9318892030c7505ef4df4 Mon Sep 17 00:00:00 2001 From: mfahampshire Date: Mon, 21 Aug 2023 15:55:38 +0200 Subject: [PATCH 295/361] added optional arg for updating minimum rust version --- documentation/bump_versions.sh | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/documentation/bump_versions.sh b/documentation/bump_versions.sh index 507af1492e..123705ee7a 100755 --- a/documentation/bump_versions.sh +++ b/documentation/bump_versions.sh @@ -4,6 +4,9 @@ # # e.g if the upcoming platform release was v1.1.29 and the release version 1.2.9 you'd run this as: # `./bump_versions.sh "1.1.29" "1.2.9"` +# +# you can also set the minumum rust version by passing an optional 3rd argument: +# `./bump_versions.sh "1.1.29" "1.2.9" "1.67"` # array of project dirs declare -a projects=("docs" "dev-portal" "operators") @@ -12,6 +15,12 @@ declare -a projects=("docs" "dev-portal" "operators") for i in "${projects[@]}" do # sed the vars in the book.toml file for each project + echo "setting platform and wallet versions in $i" sed -i 's/platform_release_version =.*/platform_release_version = "'$1'"/' "$i"/book.toml sed -i 's/wallet_release_version =.*/wallet_release_version = "'$2'"/' "$i"/book.toml + if [ "$3" ] + then + echo "setting minimum rust version in $i" + sed -i 's/minimum_rust_version = .*/minimum_rust_version = "'$3'"/' "$i"/book.toml + fi done \ No newline at end of file From 5a0255fd017571668da28099715a925593b7c5a1 Mon Sep 17 00:00:00 2001 From: mfahampshire Date: Mon, 21 Aug 2023 16:30:16 +0200 Subject: [PATCH 296/361] added some checks --- documentation/build_all_to_dist.sh | 72 ++++++++++++++++-------------- documentation/bump_versions.sh | 41 +++++++++++------ 2 files changed, 66 insertions(+), 47 deletions(-) diff --git a/documentation/build_all_to_dist.sh b/documentation/build_all_to_dist.sh index 71310bbb5e..13e5f549e5 100755 --- a/documentation/build_all_to_dist.sh +++ b/documentation/build_all_to_dist.sh @@ -1,41 +1,45 @@ #!/bin/bash # this is a script called by the github CI and CD workflows to build all 3 docs projects # and move them to /dist/ in the root of the monorepo. They are rsynced to various servers -# from there by the CI and CD workflows. -# -# these commands assume the script is run from `nym/documentation/` +# from there by subsequent workflow tasks. # array of project dirs declare -a projects=("docs" "dev-portal" "operators") -## now loop through the above array -for i in "${projects[@]}" -do - # cd to project dir - cd "./$i" && - # little sanity checks - echo $(pwd) && echo $(mdbook --version) && - # clean old book - echo "cleaning old book" - rm -rf ./book/ - # build book - mdbook build - # check for destination, if ! then mkdir & check again else echo thumbs up - if [ ! -d ../../dist/docs/$i ]; then - echo "dest doesn't exist: creating dir" - mkdir -p ../../dist/docs/$i - fi - if [ -d ../../dist/docs/$i ]; then - echo "cp destination exists, all good" - fi - # clean old dist/$i - rm -rf ../../dist/docs/$i - # move newly rendered book/ to dist - rsync -r ./book/html/ ../../dist/docs/$i - # sanity check - ls -laF ../../dist/docs/ - # cd back to ../documentation/ - cd ../ -done -# rename for server paths -mv ../dist/docs/dev-portal ../dist/docs/developers +# check you're calling from the right place +if [ $(pwd | awk -F/ '{print $NF}') != "documentation" ] +then + echo "failure: please run script from documentation/" +else + for i in "${projects[@]}" + do + # cd to project dir + cd "./$i" && + # little sanity checks + echo $(pwd) && echo $(mdbook --version) && + # clean old book + echo "cleaning old book" + rm -rf ./book/ + # build book + mdbook build + # check for destination, if ! then mkdir & check again else echo thumbs up + if [ ! -d ../../dist/docs/$i ]; then + echo "dest doesn't exist: creating dir" + mkdir -p ../../dist/docs/$i + fi + if [ -d ../../dist/docs/$i ]; then + echo "cp destination exists, all good" + fi + # clean old dist/$i + rm -rf ../../dist/docs/$i + # move newly rendered book/ to dist + rsync -r ./book/html/ ../../dist/docs/$i + # sanity check + ls -laF ../../dist/docs/ + # cd back to ../documentation/ + cd ../ + done + # rename for server paths + mv ../dist/docs/dev-portal ../dist/docs/developers +fi + diff --git a/documentation/bump_versions.sh b/documentation/bump_versions.sh index 123705ee7a..a3ddeb56b5 100755 --- a/documentation/bump_versions.sh +++ b/documentation/bump_versions.sh @@ -11,16 +11,31 @@ # array of project dirs declare -a projects=("docs" "dev-portal" "operators") -## now loop through the above array sed-ing the variable values in the book.toml files -for i in "${projects[@]}" -do - # sed the vars in the book.toml file for each project - echo "setting platform and wallet versions in $i" - sed -i 's/platform_release_version =.*/platform_release_version = "'$1'"/' "$i"/book.toml - sed -i 's/wallet_release_version =.*/wallet_release_version = "'$2'"/' "$i"/book.toml - if [ "$3" ] - then - echo "setting minimum rust version in $i" - sed -i 's/minimum_rust_version = .*/minimum_rust_version = "'$3'"/' "$i"/book.toml - fi -done \ No newline at end of file +# if called with no args then exit +if [[ $# -eq 0 ]] ; then + echo 'calling with no args: failure' + exit 0 +fi + +# check you're calling from the right place +if [ $(pwd | awk -F/ '{print $NF}') != "documentation" ] +then + echo "failure: please run script from documentation/" +else + ## now loop through the above array sed-ing the variable values in the book.toml files + for i in "${projects[@]}" + do + # sed the vars in the book.toml file for each project + echo "setting platform and wallet versions in $i" + sed -i 's/platform_release_version =.*/platform_release_version = "'$1'"/' "$i"/book.toml + if [ "$2" ] + then + sed -i 's/wallet_release_version =.*/wallet_release_version = "'$2'"/' "$i"/book.toml + fi + if [ "$3" ] + then + echo "setting minimum rust version in $i" + sed -i 's/minimum_rust_version = .*/minimum_rust_version = "'$3'"/' "$i"/book.toml + fi + done +fi From b6febc51a38cafbb2d6b4436e1f31c9e18be6fad Mon Sep 17 00:00:00 2001 From: mfahampshire Date: Mon, 21 Aug 2023 16:42:34 +0200 Subject: [PATCH 297/361] added check for # of args --- documentation/bump_versions.sh | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/documentation/bump_versions.sh b/documentation/bump_versions.sh index a3ddeb56b5..b2f5e64a36 100755 --- a/documentation/bump_versions.sh +++ b/documentation/bump_versions.sh @@ -11,9 +11,11 @@ # array of project dirs declare -a projects=("docs" "dev-portal" "operators") -# if called with no args then exit -if [[ $# -eq 0 ]] ; then - echo 'calling with no args: failure' +# check number of args passed +if [ "$#" -lt 2 ] || [ "$#" -gt 3 ]; +then + echo "failure: please pass at least 2 and at most 3 args: " + echo "./bump_version.sh [OPTIONAL]" exit 0 fi @@ -26,15 +28,12 @@ else for i in "${projects[@]}" do # sed the vars in the book.toml file for each project - echo "setting platform and wallet versions in $i" + echo "setting platform and wallet versions in $i/" sed -i 's/platform_release_version =.*/platform_release_version = "'$1'"/' "$i"/book.toml - if [ "$2" ] - then - sed -i 's/wallet_release_version =.*/wallet_release_version = "'$2'"/' "$i"/book.toml - fi + sed -i 's/wallet_release_version =.*/wallet_release_version = "'$2'"/' "$i"/book.toml if [ "$3" ] then - echo "setting minimum rust version in $i" + echo "setting minimum rust version in $i/" sed -i 's/minimum_rust_version = .*/minimum_rust_version = "'$3'"/' "$i"/book.toml fi done From 8004d54d5efbfb256a43efe312fbb2a5110b7da3 Mon Sep 17 00:00:00 2001 From: mfahampshire Date: Mon, 21 Aug 2023 16:43:52 +0200 Subject: [PATCH 298/361] added exit to conditional failure --- documentation/bump_versions.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/documentation/bump_versions.sh b/documentation/bump_versions.sh index b2f5e64a36..6201119a77 100755 --- a/documentation/bump_versions.sh +++ b/documentation/bump_versions.sh @@ -23,6 +23,7 @@ fi if [ $(pwd | awk -F/ '{print $NF}') != "documentation" ] then echo "failure: please run script from documentation/" + exit 0 else ## now loop through the above array sed-ing the variable values in the book.toml files for i in "${projects[@]}" From ffac0a1f92e7c9ba78320881473e4612db1addf5 Mon Sep 17 00:00:00 2001 From: mfahampshire Date: Mon, 21 Aug 2023 18:01:04 +0200 Subject: [PATCH 299/361] updated readme w info re: scripts --- documentation/README.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/documentation/README.md b/documentation/README.md index e074f7cdfe..9e490e4d57 100644 --- a/documentation/README.md +++ b/documentation/README.md @@ -1,7 +1,13 @@ # Documentation +## Doc projects +Each directory contains a readme with more information about running and contributing to the projects. Each is built with [`mdbook`](https://rust-lang.github.io/mdBook/index.html) - use `mdbook serve` to build and serve them (defaults to `localhost:3000`). * `docs` contains technical documentation hosted at [https://nymtech.net/docs](https://nymtech.net/docs) * `dev-portal` contains developer documentation hosted at [https://nymtech.net/developers](https://nymtech.net/developers) * `operators` contains node setup and maintenance guides hosted at [https://nymtech.net/operators](https://nymtech.net/operators) -Each directory contains a readme with more information about running and contributing to the projects. Each is built with [`mdbook`](https://rust-lang.github.io/mdBook/index.html) - use `mdbook serve` to build and serve them (defaults to `localhost:3000`). +## Scripts +* `bump_versions.sh` allows you to update the `platform_release_version` and `wallet_release_version` variables in the `book.toml` of each mdbook project at once. You can also optionally update the `minimum_rust_version` as well. Helpful for lazy-updating when cutting a new version of the docs. + * `build_all_to_dist.sh` is used by the `ci-dev.yml` and `cd-dev.yml` scripts for building all mdbook projects and moving the rendered html to `../dist/` to be rsynced with various servers. + + From 29593ef3c9657504d9bc5f4b58aac616159f3a90 Mon Sep 17 00:00:00 2001 From: Tommy Verrall Date: Tue, 22 Aug 2023 09:27:49 +0200 Subject: [PATCH 300/361] removing placeholders for the timebeing to enable windows build --- package.json | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index a18fc8c35a..a967dafdee 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,6 @@ "ts-packages/*", "nym-wallet", "nym-connect/**", - "nym-browser-extension", "explorer", "types", "clients/validator" @@ -29,9 +28,8 @@ "tsc": "lerna run tsc --stream", "types:lint:fix": "lerna run lint:fix --scope @nymproject/types --scope @nymproject/nym-wallet-app", "audit:fix": "npm_config_yes=true npx yarn-audit-fix -- --dry-run", - "preinstall": "yarn install:copy-placeholders && yarn install:copy-storage-placeholders", - "install:copy-placeholders": "cp scripts/build/yarn/wasm-placeholder/package.json sdk/typescript/packages/nym-client-wasm", - "install:copy-storage-placeholders": "mkdir -p nym-browser-extension/storage/pkg && cp scripts/build/yarn/storage-placeholder/package.json nym-browser-extension/storage/pkg" + "preinstall": "yarn install:copy-placeholders", + "install:copy-placeholders": "cp scripts/build/yarn/wasm-placeholder/package.json sdk/typescript/packages/nym-client-wasm" }, "devDependencies": { "lerna": "^6.6.2", From 22246d0d5d4f91ad474ae69e635802431ef67337 Mon Sep 17 00:00:00 2001 From: benedettadavico Date: Tue, 22 Aug 2023 09:54:51 +0200 Subject: [PATCH 301/361] bumping versions and updating changelog --- CHANGELOG.md | 15 +++++++++++++-- Cargo.lock | 18 +++++++++--------- clients/native/Cargo.toml | 2 +- clients/socks5/Cargo.toml | 2 +- explorer-api/Cargo.toml | 2 +- gateway/Cargo.toml | 2 +- mixnode/Cargo.toml | 2 +- nym-api/Cargo.toml | 2 +- nym-connect/desktop/CHANGELOG.md | 8 +++++++- nym-connect/desktop/package.json | 4 ++-- nym-connect/desktop/src-tauri/Cargo.toml | 2 +- nym-connect/desktop/src-tauri/tauri.conf.json | 2 +- nym-wallet/CHANGELOG.md | 3 +-- service-providers/network-requester/Cargo.toml | 2 +- .../network-statistics/Cargo.toml | 2 +- tools/nym-cli/Cargo.toml | 2 +- 16 files changed, 43 insertions(+), 27 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c04cce1b0a..e728e00506 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,9 +4,20 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https:// ## [Unreleased] -- Add Ephemera functionality in nym-api ([#3731]) +## [1.1.28] (2023-08-22) + +- [final step3]: add [rust] support to nyxd client in wasm ([#3743]) +- Feature/ephemera upgrade ([#3791]) +- [rust-sdk] feat: make it more convenient to send and receive messages in different tasks ([#3756]) +- feat: validator client refactoring + wasm compatible nyxd client ([#3726]) +- feat: retain connection between client init and run ([#3767]) + +[#3743]: https://github.com/nymtech/nym/issues/3743 +[#3791]: https://github.com/nymtech/nym/pull/3791 +[#3756]: https://github.com/nymtech/nym/pull/3756 +[#3726]: https://github.com/nymtech/nym/pull/3726 +[#3767]: https://github.com/nymtech/nym/pull/3767 -[#3731]: https://github.com/nymtech/nym/pull/3731 ## [1.1.27] (2023-08-16) diff --git a/Cargo.lock b/Cargo.lock index f14848f51b..cc54eee9dc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2761,7 +2761,7 @@ checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" [[package]] name = "explorer-api" -version = "1.1.25" +version = "1.1.26" dependencies = [ "chrono", "clap 4.3.21", @@ -5226,7 +5226,7 @@ dependencies = [ [[package]] name = "nym-api" -version = "1.1.26" +version = "1.1.27" dependencies = [ "actix-web", "anyhow", @@ -5373,7 +5373,7 @@ dependencies = [ [[package]] name = "nym-cli" -version = "1.1.25" +version = "1.1.26" dependencies = [ "anyhow", "base64 0.13.1", @@ -5439,7 +5439,7 @@ dependencies = [ [[package]] name = "nym-client" -version = "1.1.25" +version = "1.1.26" dependencies = [ "clap 4.3.21", "dirs 4.0.0", @@ -5737,7 +5737,7 @@ dependencies = [ [[package]] name = "nym-gateway" -version = "1.1.25" +version = "1.1.26" dependencies = [ "anyhow", "async-trait", @@ -5892,7 +5892,7 @@ dependencies = [ [[package]] name = "nym-mixnode" -version = "1.1.26" +version = "1.1.27" dependencies = [ "anyhow", "bs58 0.4.0", @@ -6010,7 +6010,7 @@ dependencies = [ [[package]] name = "nym-network-requester" -version = "1.1.25" +version = "1.1.26" dependencies = [ "anyhow", "async-file-watcher", @@ -6057,7 +6057,7 @@ dependencies = [ [[package]] name = "nym-network-statistics" -version = "1.1.25" +version = "1.1.26" dependencies = [ "dirs 4.0.0", "log", @@ -6215,7 +6215,7 @@ dependencies = [ [[package]] name = "nym-socks5-client" -version = "1.1.25" +version = "1.1.26" dependencies = [ "clap 4.3.21", "lazy_static", diff --git a/clients/native/Cargo.toml b/clients/native/Cargo.toml index 9bf16e89ad..b94b1e7b76 100644 --- a/clients/native/Cargo.toml +++ b/clients/native/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "nym-client" -version = "1.1.25" +version = "1.1.26" authors = ["Dave Hrycyszyn ", "Jędrzej Stuczyński "] description = "Implementation of the Nym Client" edition = "2021" diff --git a/clients/socks5/Cargo.toml b/clients/socks5/Cargo.toml index a574f28985..8532f9d2b7 100644 --- a/clients/socks5/Cargo.toml +++ b/clients/socks5/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "nym-socks5-client" -version = "1.1.25" +version = "1.1.26" authors = ["Dave Hrycyszyn "] description = "A SOCKS5 localhost proxy that converts incoming messages to Sphinx and sends them to a Nym address" edition = "2021" diff --git a/explorer-api/Cargo.toml b/explorer-api/Cargo.toml index 72724ce6eb..a39e3cb52b 100644 --- a/explorer-api/Cargo.toml +++ b/explorer-api/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "explorer-api" -version = "1.1.25" +version = "1.1.26" edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/gateway/Cargo.toml b/gateway/Cargo.toml index 25e2b62375..2b71aa1542 100644 --- a/gateway/Cargo.toml +++ b/gateway/Cargo.toml @@ -3,7 +3,7 @@ [package] name = "nym-gateway" -version = "1.1.25" +version = "1.1.26" authors = [ "Dave Hrycyszyn ", "Jędrzej Stuczyński ", diff --git a/mixnode/Cargo.toml b/mixnode/Cargo.toml index 101ac4d513..ec0b3f5f94 100644 --- a/mixnode/Cargo.toml +++ b/mixnode/Cargo.toml @@ -3,7 +3,7 @@ [package] name = "nym-mixnode" -version = "1.1.26" +version = "1.1.27" authors = [ "Dave Hrycyszyn ", "Jędrzej Stuczyński ", diff --git a/nym-api/Cargo.toml b/nym-api/Cargo.toml index cc55f92afc..c9a0b8e8e4 100644 --- a/nym-api/Cargo.toml +++ b/nym-api/Cargo.toml @@ -3,7 +3,7 @@ [package] name = "nym-api" -version = "1.1.26" +version = "1.1.27" authors = [ "Dave Hrycyszyn ", "Jędrzej Stuczyński ", diff --git a/nym-connect/desktop/CHANGELOG.md b/nym-connect/desktop/CHANGELOG.md index 13f449269e..20534c13ef 100644 --- a/nym-connect/desktop/CHANGELOG.md +++ b/nym-connect/desktop/CHANGELOG.md @@ -2,7 +2,13 @@ ## [Unreleased] -## [1.1.27] (2023-08-16) +## [1.1.18] (2023-08-22) + +- refactor(nc-desktop): use userdata storage to save user gateway&sp ([#3723]) + +[#3723]: https://github.com/nymtech/nym/pull/3723 + +## [1.1.17] (2023-08-16) - Add a "Send us your feedback" section in NC (on the main screen) to collect user feedback using Sentry ([#3619]) - NC native android - deploy on FDroid ([#3483]) diff --git a/nym-connect/desktop/package.json b/nym-connect/desktop/package.json index ede5727153..a3a74ab218 100644 --- a/nym-connect/desktop/package.json +++ b/nym-connect/desktop/package.json @@ -1,6 +1,6 @@ { "name": "@nym/nym-connect", - "version": "1.1.17", + "version": "1.1.18", "main": "index.js", "license": "MIT", "scripts": { @@ -115,4 +115,4 @@ "webpack-merge": "^5.8.0", "yaml-loader": "^0.8.0" } -} +} \ No newline at end of file diff --git a/nym-connect/desktop/src-tauri/Cargo.toml b/nym-connect/desktop/src-tauri/Cargo.toml index 192b1bb391..1844688af9 100644 --- a/nym-connect/desktop/src-tauri/Cargo.toml +++ b/nym-connect/desktop/src-tauri/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "nym-connect" -version = "1.1.17" +version = "1.1.18" description = "nym-connect" authors = ["Nym Technologies SA"] license = "" diff --git a/nym-connect/desktop/src-tauri/tauri.conf.json b/nym-connect/desktop/src-tauri/tauri.conf.json index 49311b295d..bfbceb21be 100644 --- a/nym-connect/desktop/src-tauri/tauri.conf.json +++ b/nym-connect/desktop/src-tauri/tauri.conf.json @@ -1,7 +1,7 @@ { "package": { "productName": "nym-connect", - "version": "1.1.17" + "version": "1.1.18" }, "build": { "distDir": "../dist", diff --git a/nym-wallet/CHANGELOG.md b/nym-wallet/CHANGELOG.md index 87928a2574..d0b6d0aa6f 100644 --- a/nym-wallet/CHANGELOG.md +++ b/nym-wallet/CHANGELOG.md @@ -2,10 +2,9 @@ ## [Unreleased] -## [1.1.27] (2023-08-17) +## [1.2.7] (2023-08-17) - release due to schema changes in the contract - ## [v1.2.6] (2023-07-18) - [wallet] bugfix: don't send funds for pledge decrease simulation ([#3676]) diff --git a/service-providers/network-requester/Cargo.toml b/service-providers/network-requester/Cargo.toml index 713552f812..9f1fc07c97 100644 --- a/service-providers/network-requester/Cargo.toml +++ b/service-providers/network-requester/Cargo.toml @@ -3,7 +3,7 @@ [package] name = "nym-network-requester" -version = "1.1.25" +version = "1.1.26" authors.workspace = true edition.workspace = true rust-version = "1.65" diff --git a/service-providers/network-statistics/Cargo.toml b/service-providers/network-statistics/Cargo.toml index 8c5d7784f0..7ff628cdc6 100644 --- a/service-providers/network-statistics/Cargo.toml +++ b/service-providers/network-statistics/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "nym-network-statistics" -version = "1.1.25" +version = "1.1.26" edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/tools/nym-cli/Cargo.toml b/tools/nym-cli/Cargo.toml index a693b9aa12..e63dcc8225 100644 --- a/tools/nym-cli/Cargo.toml +++ b/tools/nym-cli/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "nym-cli" -version = "1.1.25" +version = "1.1.26" authors.workspace = true edition = "2021" From 5d454f2efcb2a0cf5d8ba3345b004f96e175ff63 Mon Sep 17 00:00:00 2001 From: mfahampshire Date: Tue, 22 Aug 2023 10:03:08 +0200 Subject: [PATCH 302/361] fixed issue with already existing directory during mv --- documentation/build_all_to_dist.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/documentation/build_all_to_dist.sh b/documentation/build_all_to_dist.sh index 13e5f549e5..b414e92df6 100755 --- a/documentation/build_all_to_dist.sh +++ b/documentation/build_all_to_dist.sh @@ -40,6 +40,6 @@ else cd ../ done # rename for server paths + rm -rf ../dist/docs/developers mv ../dist/docs/dev-portal ../dist/docs/developers fi - From c1e4b87744c68bcfa882b9cb2b37301054be31ff Mon Sep 17 00:00:00 2001 From: Tommy Verrall Date: Tue, 22 Aug 2023 10:31:37 +0200 Subject: [PATCH 303/361] change ci to run against sandbox for tests --- .github/workflows/run-api-tests.yml | 2 +- nym-api/tests/package.json | 2 +- nym-api/tests/src/config/config.yaml | 11 ++++++----- nym-api/tests/src/config/configHandler.ts | 4 ++-- 4 files changed, 10 insertions(+), 9 deletions(-) diff --git a/.github/workflows/run-api-tests.yml b/.github/workflows/run-api-tests.yml index 1d2afec2bd..fbc3d63eea 100644 --- a/.github/workflows/run-api-tests.yml +++ b/.github/workflows/run-api-tests.yml @@ -32,5 +32,5 @@ jobs: run: yarn - name: Run tests - run: yarn test:qa + run: yarn test:sandbox working-directory: nym-api/tests diff --git a/nym-api/tests/package.json b/nym-api/tests/package.json index e93160b7b6..31731d6b16 100644 --- a/nym-api/tests/package.json +++ b/nym-api/tests/package.json @@ -7,7 +7,7 @@ "test": "test" }, "scripts": { - "test:qa": "TEST_ENV=qa jest --forceExit --detectOpenHandles --passWithNoTests", + "test:sandbox": "TEST_ENV=sandbox jest --forceExit --detectOpenHandles --passWithNoTests", "test:prod": "TEST_ENV=prod jest --forceExit --detectOpenHandles --passWithNoTests", "build": "tsc", "lint": "eslint --ext .js,.ts,.tsx .", diff --git a/nym-api/tests/src/config/config.yaml b/nym-api/tests/src/config/config.yaml index d0f51eff0c..85603a7efa 100644 --- a/nym-api/tests/src/config/config.yaml +++ b/nym-api/tests/src/config/config.yaml @@ -2,12 +2,13 @@ common: request_headers: Accept: application/json Content-Type: application/json -qa: - api_base_url: https://qwerty-validator-api.qa.nymte.ch/api/v1 - mix_id: 63 - identity_key: 4Yr4qmEHd9sgsuQ83191FR2hD88RfsbMmB4tzhhZWriz - gateway_identity: 336yuXAeGEgedRfqTJZsG2YV7P13QH1bHv1SjCZYarc9 +sandbox: + api_base_url: https://sandbox-nym-api1.nymtech.net/api/v1 + mix_id: 7 + identity_key: 2wEzyW6Pihk19xbCK9kB81vYuDFUvTzTAr77arvxdfSg + gateway_identity: HjNEDJuotWV8VD4ufeA1jeheTnfNJ7Jorevp57hgaZua log_level: error + time_zone: utc prod: api_base_url: https://validator.nymtech.net/api/v1 mix_id: 730 diff --git a/nym-api/tests/src/config/configHandler.ts b/nym-api/tests/src/config/configHandler.ts index 44177f0866..1d29402012 100644 --- a/nym-api/tests/src/config/configHandler.ts +++ b/nym-api/tests/src/config/configHandler.ts @@ -6,7 +6,7 @@ import YAML from "yaml"; class ConfigHandler { private static instance: ConfigHandler; - private validEnvironments = ["qa", "prod"]; + private validEnvironments = ["sandbox", "prod"]; public commonConfig: { request_headers: object }; @@ -23,7 +23,7 @@ class ConfigHandler { private constructor() { this.setCommonConfig(); - this.setEnvironmentConfig(process.env.TEST_ENV || "qa" || "prod"); + this.setEnvironmentConfig(process.env.TEST_ENV || "sandbox" || "prod"); } public static getInstance(): ConfigHandler { From 3c69f9c2f991b485e910beff34f434d147425548 Mon Sep 17 00:00:00 2001 From: Tommy Verrall <60836166+tommyv1987@users.noreply.github.com> Date: Tue, 22 Aug 2023 10:35:46 +0200 Subject: [PATCH 304/361] Update nym-connect-publish-windows10.yml --- .github/workflows/nym-connect-publish-windows10.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/nym-connect-publish-windows10.yml b/.github/workflows/nym-connect-publish-windows10.yml index 12c296a16d..f90ce1697d 100644 --- a/.github/workflows/nym-connect-publish-windows10.yml +++ b/.github/workflows/nym-connect-publish-windows10.yml @@ -100,6 +100,7 @@ jobs: nym-connect/desktop/target/release/bundle/msi/*.msi.zip* - id: release-info name: Prepare release info + shell: bash run: | ref=${{ github.ref_name }} semver="${ref##nym-connect-}" && semver="${semver##v}" From 0a5a2c67474736ca093c7f8adce4a1421a8e30d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C4=99drzej=20Stuczy=C5=84ski?= Date: Tue, 22 Aug 2023 10:56:23 +0100 Subject: [PATCH 305/361] removed needless_pass_by_ref_mut in mixnet contract tests (#3798) --- contracts/mixnet/src/interval/storage.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/mixnet/src/interval/storage.rs b/contracts/mixnet/src/interval/storage.rs index 0e35f3908d..04f3aa999a 100644 --- a/contracts/mixnet/src/interval/storage.rs +++ b/contracts/mixnet/src/interval/storage.rs @@ -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 { + fn read_entire_set(storage: &dyn Storage) -> HashMap { REWARDED_SET .range(storage, None, None, Order::Ascending) .map(|r| r.unwrap()) From f88622ac0805479e50164dcccca69c1f0bf8999e Mon Sep 17 00:00:00 2001 From: Tommy Verrall <60836166+tommyv1987@users.noreply.github.com> Date: Tue, 22 Aug 2023 12:21:44 +0200 Subject: [PATCH 306/361] Delete upload-nyxd-ci.yml not needed anymore removing --- .github/workflows/upload-nyxd-ci.yml | 79 ---------------------------- 1 file changed, 79 deletions(-) delete mode 100644 .github/workflows/upload-nyxd-ci.yml diff --git a/.github/workflows/upload-nyxd-ci.yml b/.github/workflows/upload-nyxd-ci.yml deleted file mode 100644 index 1668bf3184..0000000000 --- a/.github/workflows/upload-nyxd-ci.yml +++ /dev/null @@ -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/" From b429c64168c6dd873f0ee7c66e3efb3626032a7d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C4=99drzej=20Stuczy=C5=84ski?= Date: Tue, 22 Aug 2023 12:12:13 +0100 Subject: [PATCH 307/361] [demo] libp2p example with nym-sdk (#3763) * imported libp2p to monorepo * fixed vanilla ping example * added libp2p client to workspace * naively replaced dockerised client with the sdk * moved libp2p code to sdk examples * reduced number of dependencies required for libp2p example * updated readmes * added protobuf compiler to build dependencies * added protoc dependency to readme for chat and ping examples --------- Co-authored-by: mfahampshire --- .../build-and-upload-binaries-ci.yml | 2 +- .github/workflows/build.yml | 2 +- .github/workflows/nightly_build.yml | 2 +- .github/workflows/nightly_build_release.yml | 2 +- Cargo.lock | 654 +++++++++++-- sdk/rust/nym-sdk/Cargo.toml | 10 + .../nym-sdk/examples/libp2p_chat/README.md | 64 ++ sdk/rust/nym-sdk/examples/libp2p_chat/main.rs | 171 ++++ .../nym-sdk/examples/libp2p_ping/README.md | 79 ++ sdk/rust/nym-sdk/examples/libp2p_ping/main.rs | 142 +++ .../examples/libp2p_shared/connection.rs | 418 ++++++++ .../nym-sdk/examples/libp2p_shared/error.rs | 64 ++ .../nym-sdk/examples/libp2p_shared/lib.rs | 10 + .../nym-sdk/examples/libp2p_shared/message.rs | 335 +++++++ .../nym-sdk/examples/libp2p_shared/mixnet.rs | 164 ++++ .../nym-sdk/examples/libp2p_shared/queue.rs | 140 +++ .../examples/libp2p_shared/substream.rs | 422 ++++++++ .../examples/libp2p_shared/transport.rs | 897 ++++++++++++++++++ 18 files changed, 3504 insertions(+), 74 deletions(-) create mode 100644 sdk/rust/nym-sdk/examples/libp2p_chat/README.md create mode 100644 sdk/rust/nym-sdk/examples/libp2p_chat/main.rs create mode 100644 sdk/rust/nym-sdk/examples/libp2p_ping/README.md create mode 100644 sdk/rust/nym-sdk/examples/libp2p_ping/main.rs create mode 100644 sdk/rust/nym-sdk/examples/libp2p_shared/connection.rs create mode 100644 sdk/rust/nym-sdk/examples/libp2p_shared/error.rs create mode 100644 sdk/rust/nym-sdk/examples/libp2p_shared/lib.rs create mode 100644 sdk/rust/nym-sdk/examples/libp2p_shared/message.rs create mode 100644 sdk/rust/nym-sdk/examples/libp2p_shared/mixnet.rs create mode 100644 sdk/rust/nym-sdk/examples/libp2p_shared/queue.rs create mode 100644 sdk/rust/nym-sdk/examples/libp2p_shared/substream.rs create mode 100644 sdk/rust/nym-sdk/examples/libp2p_shared/transport.rs diff --git a/.github/workflows/build-and-upload-binaries-ci.yml b/.github/workflows/build-and-upload-binaries-ci.yml index 849bd32a17..f317f90110 100644 --- a/.github/workflows/build-and-upload-binaries-ci.yml +++ b/.github/workflows/build-and-upload-binaries-ci.yml @@ -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 diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index d346df6296..ce9f7820b0 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -42,7 +42,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 diff --git a/.github/workflows/nightly_build.yml b/.github/workflows/nightly_build.yml index 1ee391d6a7..84ea9fabec 100644 --- a/.github/workflows/nightly_build.yml +++ b/.github/workflows/nightly_build.yml @@ -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' diff --git a/.github/workflows/nightly_build_release.yml b/.github/workflows/nightly_build_release.yml index fa1c465c97..6ddee762e1 100644 --- a/.github/workflows/nightly_build_release.yml +++ b/.github/workflows/nightly_build_release.yml @@ -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' diff --git a/Cargo.lock b/Cargo.lock index cc54eee9dc..26d7b17568 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -19,7 +19,7 @@ dependencies = [ "futures-core", "futures-sink", "memchr", - "pin-project-lite", + "pin-project-lite 0.2.12", "tokio", "tokio-util", "tracing", @@ -54,7 +54,7 @@ dependencies = [ "local-channel", "mime", "percent-encoding", - "pin-project-lite", + "pin-project-lite 0.2.12", "rand 0.8.5", "sha1", "smallvec", @@ -123,7 +123,7 @@ checksum = "3b894941f818cfdc7ccc4b9e60fa7e53b5042a2e8567270f9147d5591893373a" dependencies = [ "futures-core", "paste", - "pin-project-lite", + "pin-project-lite 0.2.12", ] [[package]] @@ -133,7 +133,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "88a1dcdff1466e3c2488e1cb5c36a71822750ad43839937f85d2f4d9f8b705d8" dependencies = [ "local-waker", - "pin-project-lite", + "pin-project-lite 0.2.12", ] [[package]] @@ -166,7 +166,7 @@ dependencies = [ "log", "mime", "once_cell", - "pin-project-lite", + "pin-project-lite 0.2.12", "regex", "serde", "serde_json", @@ -569,6 +569,12 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "asn1_der" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "155a5a185e42c6b77ac7b88a15143d930a9e9727a5b7b77eed417404ab15c247" + [[package]] name = "assert_matches" version = "1.5.0" @@ -633,7 +639,7 @@ checksum = "cd56dd203fef61ac097dd65721a419ddccb106b2d2b70ba60a6b529f03961a51" dependencies = [ "async-stream-impl", "futures-core", - "pin-project-lite", + "pin-project-lite 0.2.12", ] [[package]] @@ -668,7 +674,7 @@ dependencies = [ "futures-sink", "futures-util", "memchr", - "pin-project-lite", + "pin-project-lite 0.2.12", ] [[package]] @@ -746,7 +752,7 @@ dependencies = [ "memchr", "mime", "percent-encoding", - "pin-project-lite", + "pin-project-lite 0.2.12", "rustversion", "serde", "sync_wrapper", @@ -883,8 +889,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "93f2635620bf0b9d4576eb7bb9a38a55df78bd1205d26fa994b25911a69f212f" dependencies = [ "bitcoin_hashes", - "rand 0.6.5", - "rand_core 0.4.2", + "rand 0.8.5", + "rand_core 0.6.4", "serde", "unicode-normalization", "zeroize", @@ -2699,10 +2705,10 @@ dependencies = [ "futures", "futures-util", "lazy_static", - "libp2p", + "libp2p 0.51.3", "libp2p-identity", "log", - "lru", + "lru 0.10.0", "nym-config", "nym-ephemera-common", "nym-task", @@ -2933,6 +2939,12 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "fixedbitset" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" + [[package]] name = "flate2" version = "1.0.26" @@ -2940,6 +2952,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3b9429470923de8e8cbd4d2dc513535400b4b3fef0319fb5c4e1f520a7bef743" dependencies = [ "crc32fast", + "libz-sys", "miniz_oxide", ] @@ -3093,7 +3106,7 @@ dependencies = [ "futures-io", "memchr", "parking", - "pin-project-lite", + "pin-project-lite 0.2.12", "waker-fn", ] @@ -3150,7 +3163,7 @@ dependencies = [ "futures-sink", "futures-task", "memchr", - "pin-project-lite", + "pin-project-lite 0.2.12", "pin-utils", "slab", ] @@ -3610,7 +3623,7 @@ checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1" dependencies = [ "bytes", "http", - "pin-project-lite", + "pin-project-lite 0.2.12", ] [[package]] @@ -3666,7 +3679,7 @@ dependencies = [ "httparse", "httpdate", "itoa", - "pin-project-lite", + "pin-project-lite 0.2.12", "socket2 0.4.9", "tokio", "tower-service", @@ -3718,7 +3731,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbb958482e8c7be4bc3cf272a766a2b0bf1a6755e7a6ae777f017a31d11b13b1" dependencies = [ "hyper", - "pin-project-lite", + "pin-project-lite 0.2.12", "tokio", "tokio-io-timeout", ] @@ -4262,6 +4275,35 @@ version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f7012b1bbb0719e1097c47611d3898568c546d597c2e74d66f6087edd5233ff4" +[[package]] +name = "libp2p" +version = "0.51.0" +source = "git+https://github.com/ChainSafe/rust-libp2p.git?rev=e3440d25681df380c9f0f8cfdcfd5ecc0a4f2fb6#e3440d25681df380c9f0f8cfdcfd5ecc0a4f2fb6" +dependencies = [ + "bytes", + "futures", + "futures-timer", + "getrandom 0.2.10", + "instant", + "libp2p-core 0.39.0", + "libp2p-dns 0.39.0 (git+https://github.com/ChainSafe/rust-libp2p.git?rev=e3440d25681df380c9f0f8cfdcfd5ecc0a4f2fb6)", + "libp2p-gossipsub 0.44.0", + "libp2p-identify", + "libp2p-mdns 0.43.0", + "libp2p-metrics 0.12.0 (git+https://github.com/ChainSafe/rust-libp2p.git?rev=e3440d25681df380c9f0f8cfdcfd5ecc0a4f2fb6)", + "libp2p-mplex", + "libp2p-noise 0.42.0", + "libp2p-ping", + "libp2p-quic 0.7.0-alpha.2", + "libp2p-swarm 0.42.0", + "libp2p-tcp 0.39.0 (git+https://github.com/ChainSafe/rust-libp2p.git?rev=e3440d25681df380c9f0f8cfdcfd5ecc0a4f2fb6)", + "libp2p-webrtc 0.4.0-alpha.2", + "libp2p-websocket", + "libp2p-yamux 0.43.0", + "multiaddr 0.17.0", + "pin-project", +] + [[package]] name = "libp2p" version = "0.51.3" @@ -4275,21 +4317,21 @@ dependencies = [ "instant", "libp2p-allow-block-list", "libp2p-connection-limits", - "libp2p-core", - "libp2p-dns", - "libp2p-gossipsub", + "libp2p-core 0.39.2", + "libp2p-dns 0.39.0 (registry+https://github.com/rust-lang/crates.io-index)", + "libp2p-gossipsub 0.44.4", "libp2p-identity", "libp2p-kad", - "libp2p-mdns", - "libp2p-metrics", - "libp2p-noise", - "libp2p-quic", + "libp2p-mdns 0.43.1", + "libp2p-metrics 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)", + "libp2p-noise 0.42.2", + "libp2p-quic 0.7.0-alpha.3", "libp2p-request-response", - "libp2p-swarm", - "libp2p-tcp", - "libp2p-webrtc", - "libp2p-yamux", - "multiaddr", + "libp2p-swarm 0.42.2", + "libp2p-tcp 0.39.0 (registry+https://github.com/rust-lang/crates.io-index)", + "libp2p-webrtc 0.4.0-alpha.4", + "libp2p-yamux 0.43.1", + "multiaddr 0.17.1", "pin-project", ] @@ -4299,9 +4341,9 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "510daa05efbc25184458db837f6f9a5143888f1caa742426d92e1833ddd38a50" dependencies = [ - "libp2p-core", + "libp2p-core 0.39.2", "libp2p-identity", - "libp2p-swarm", + "libp2p-swarm 0.42.2", "void", ] @@ -4311,12 +4353,45 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4caa33f1d26ed664c4fe2cca81a08c8e07d4c1c04f2f4ac7655c2dd85467fda0" dependencies = [ - "libp2p-core", + "libp2p-core 0.39.2", "libp2p-identity", - "libp2p-swarm", + "libp2p-swarm 0.42.2", "void", ] +[[package]] +name = "libp2p-core" +version = "0.39.0" +source = "git+https://github.com/ChainSafe/rust-libp2p.git?rev=e3440d25681df380c9f0f8cfdcfd5ecc0a4f2fb6#e3440d25681df380c9f0f8cfdcfd5ecc0a4f2fb6" +dependencies = [ + "asn1_der", + "bs58 0.4.0", + "ed25519-dalek", + "either", + "fnv", + "futures", + "futures-timer", + "instant", + "log", + "multiaddr 0.17.0", + "multihash", + "multistream-select 0.12.1 (git+https://github.com/ChainSafe/rust-libp2p.git?rev=e3440d25681df380c9f0f8cfdcfd5ecc0a4f2fb6)", + "once_cell", + "parking_lot 0.12.1", + "pin-project", + "prost", + "prost-build", + "rand 0.8.5", + "rw-stream-sink 0.3.0 (git+https://github.com/ChainSafe/rust-libp2p.git?rev=e3440d25681df380c9f0f8cfdcfd5ecc0a4f2fb6)", + "sec1 0.3.0", + "sha2 0.10.7", + "smallvec", + "thiserror", + "unsigned-varint", + "void", + "zeroize", +] + [[package]] name = "libp2p-core" version = "0.39.2" @@ -4330,15 +4405,15 @@ dependencies = [ "instant", "libp2p-identity", "log", - "multiaddr", + "multiaddr 0.17.1", "multihash", - "multistream-select", + "multistream-select 0.12.1 (registry+https://github.com/rust-lang/crates.io-index)", "once_cell", "parking_lot 0.12.1", "pin-project", "quick-protobuf", "rand 0.8.5", - "rw-stream-sink", + "rw-stream-sink 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", "serde", "smallvec", "thiserror", @@ -4353,13 +4428,55 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "146ff7034daae62077c415c2376b8057368042df6ab95f5432ad5e88568b1554" dependencies = [ "futures", - "libp2p-core", + "libp2p-core 0.39.2", "log", "parking_lot 0.12.1", "smallvec", "trust-dns-resolver", ] +[[package]] +name = "libp2p-dns" +version = "0.39.0" +source = "git+https://github.com/ChainSafe/rust-libp2p.git?rev=e3440d25681df380c9f0f8cfdcfd5ecc0a4f2fb6#e3440d25681df380c9f0f8cfdcfd5ecc0a4f2fb6" +dependencies = [ + "futures", + "libp2p-core 0.39.0", + "log", + "parking_lot 0.12.1", + "smallvec", + "trust-dns-resolver", +] + +[[package]] +name = "libp2p-gossipsub" +version = "0.44.0" +source = "git+https://github.com/ChainSafe/rust-libp2p.git?rev=e3440d25681df380c9f0f8cfdcfd5ecc0a4f2fb6#e3440d25681df380c9f0f8cfdcfd5ecc0a4f2fb6" +dependencies = [ + "asynchronous-codec", + "base64 0.21.2", + "byteorder", + "bytes", + "fnv", + "futures", + "hex_fmt", + "instant", + "libp2p-core 0.39.0", + "libp2p-swarm 0.42.0", + "log", + "prometheus-client", + "prost", + "prost-build", + "prost-codec", + "rand 0.8.5", + "regex", + "sha2 0.10.7", + "smallvec", + "thiserror", + "unsigned-varint", + "wasm-timer", +] + [[package]] name = "libp2p-gossipsub" version = "0.44.4" @@ -4375,9 +4492,9 @@ dependencies = [ "futures", "hex_fmt", "instant", - "libp2p-core", + "libp2p-core 0.39.2", "libp2p-identity", - "libp2p-swarm", + "libp2p-swarm 0.42.2", "log", "prometheus-client", "quick-protobuf", @@ -4393,6 +4510,27 @@ dependencies = [ "wasm-timer", ] +[[package]] +name = "libp2p-identify" +version = "0.42.0" +source = "git+https://github.com/ChainSafe/rust-libp2p.git?rev=e3440d25681df380c9f0f8cfdcfd5ecc0a4f2fb6#e3440d25681df380c9f0f8cfdcfd5ecc0a4f2fb6" +dependencies = [ + "asynchronous-codec", + "either", + "futures", + "futures-timer", + "libp2p-core 0.39.0", + "libp2p-swarm 0.42.0", + "log", + "lru 0.9.0", + "prost", + "prost-build", + "prost-codec", + "smallvec", + "thiserror", + "void", +] + [[package]] name = "libp2p-identity" version = "0.1.2" @@ -4402,7 +4540,7 @@ dependencies = [ "bs58 0.4.0", "ed25519-dalek", "log", - "multiaddr", + "multiaddr 0.17.1", "multihash", "quick-protobuf", "rand 0.8.5", @@ -4426,9 +4564,9 @@ dependencies = [ "futures", "futures-timer", "instant", - "libp2p-core", + "libp2p-core 0.39.2", "libp2p-identity", - "libp2p-swarm", + "libp2p-swarm 0.42.2", "log", "quick-protobuf", "rand 0.8.5", @@ -4441,6 +4579,25 @@ dependencies = [ "void", ] +[[package]] +name = "libp2p-mdns" +version = "0.43.0" +source = "git+https://github.com/ChainSafe/rust-libp2p.git?rev=e3440d25681df380c9f0f8cfdcfd5ecc0a4f2fb6#e3440d25681df380c9f0f8cfdcfd5ecc0a4f2fb6" +dependencies = [ + "data-encoding", + "futures", + "if-watch", + "libp2p-core 0.39.0", + "libp2p-swarm 0.42.0", + "log", + "rand 0.8.5", + "smallvec", + "socket2 0.4.9", + "tokio", + "trust-dns-proto", + "void", +] + [[package]] name = "libp2p-mdns" version = "0.43.1" @@ -4450,9 +4607,9 @@ dependencies = [ "data-encoding", "futures", "if-watch", - "libp2p-core", + "libp2p-core 0.39.2", "libp2p-identity", - "libp2p-swarm", + "libp2p-swarm 0.42.2", "log", "rand 0.8.5", "smallvec", @@ -4468,13 +4625,65 @@ version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a42ec91e227d7d0dafa4ce88b333cdf5f277253873ab087555c92798db2ddd46" dependencies = [ - "libp2p-core", - "libp2p-gossipsub", + "libp2p-core 0.39.2", + "libp2p-gossipsub 0.44.4", "libp2p-kad", - "libp2p-swarm", + "libp2p-swarm 0.42.2", "prometheus-client", ] +[[package]] +name = "libp2p-metrics" +version = "0.12.0" +source = "git+https://github.com/ChainSafe/rust-libp2p.git?rev=e3440d25681df380c9f0f8cfdcfd5ecc0a4f2fb6#e3440d25681df380c9f0f8cfdcfd5ecc0a4f2fb6" +dependencies = [ + "libp2p-core 0.39.0", + "libp2p-gossipsub 0.44.0", + "libp2p-identify", + "libp2p-ping", + "libp2p-swarm 0.42.0", + "prometheus-client", +] + +[[package]] +name = "libp2p-mplex" +version = "0.39.0" +source = "git+https://github.com/ChainSafe/rust-libp2p.git?rev=e3440d25681df380c9f0f8cfdcfd5ecc0a4f2fb6#e3440d25681df380c9f0f8cfdcfd5ecc0a4f2fb6" +dependencies = [ + "asynchronous-codec", + "bytes", + "futures", + "libp2p-core 0.39.0", + "log", + "nohash-hasher", + "parking_lot 0.12.1", + "rand 0.8.5", + "smallvec", + "unsigned-varint", +] + +[[package]] +name = "libp2p-noise" +version = "0.42.0" +source = "git+https://github.com/ChainSafe/rust-libp2p.git?rev=e3440d25681df380c9f0f8cfdcfd5ecc0a4f2fb6#e3440d25681df380c9f0f8cfdcfd5ecc0a4f2fb6" +dependencies = [ + "bytes", + "curve25519-dalek 3.2.0", + "futures", + "libp2p-core 0.39.0", + "log", + "once_cell", + "prost", + "prost-build", + "rand 0.8.5", + "sha2 0.10.7", + "snow", + "static_assertions", + "thiserror", + "x25519-dalek 1.1.1", + "zeroize", +] + [[package]] name = "libp2p-noise" version = "0.42.2" @@ -4484,7 +4693,7 @@ dependencies = [ "bytes", "curve25519-dalek 3.2.0", "futures", - "libp2p-core", + "libp2p-core 0.39.2", "libp2p-identity", "log", "once_cell", @@ -4498,6 +4707,42 @@ dependencies = [ "zeroize", ] +[[package]] +name = "libp2p-ping" +version = "0.42.0" +source = "git+https://github.com/ChainSafe/rust-libp2p.git?rev=e3440d25681df380c9f0f8cfdcfd5ecc0a4f2fb6#e3440d25681df380c9f0f8cfdcfd5ecc0a4f2fb6" +dependencies = [ + "either", + "futures", + "futures-timer", + "instant", + "libp2p-core 0.39.0", + "libp2p-swarm 0.42.0", + "log", + "rand 0.8.5", + "void", +] + +[[package]] +name = "libp2p-quic" +version = "0.7.0-alpha.2" +source = "git+https://github.com/ChainSafe/rust-libp2p.git?rev=e3440d25681df380c9f0f8cfdcfd5ecc0a4f2fb6#e3440d25681df380c9f0f8cfdcfd5ecc0a4f2fb6" +dependencies = [ + "bytes", + "futures", + "futures-timer", + "if-watch", + "libp2p-core 0.39.0", + "libp2p-tls 0.1.0-alpha.2", + "log", + "parking_lot 0.12.1", + "quinn-proto", + "rand 0.8.5", + "rustls 0.20.8", + "thiserror", + "tokio", +] + [[package]] name = "libp2p-quic" version = "0.7.0-alpha.3" @@ -4508,9 +4753,9 @@ dependencies = [ "futures", "futures-timer", "if-watch", - "libp2p-core", + "libp2p-core 0.39.2", "libp2p-identity", - "libp2p-tls", + "libp2p-tls 0.1.0", "log", "parking_lot 0.12.1", "quinn-proto", @@ -4529,13 +4774,32 @@ dependencies = [ "async-trait", "futures", "instant", - "libp2p-core", + "libp2p-core 0.39.2", "libp2p-identity", - "libp2p-swarm", + "libp2p-swarm 0.42.2", "rand 0.8.5", "smallvec", ] +[[package]] +name = "libp2p-swarm" +version = "0.42.0" +source = "git+https://github.com/ChainSafe/rust-libp2p.git?rev=e3440d25681df380c9f0f8cfdcfd5ecc0a4f2fb6#e3440d25681df380c9f0f8cfdcfd5ecc0a4f2fb6" +dependencies = [ + "either", + "fnv", + "futures", + "futures-timer", + "instant", + "libp2p-core 0.39.0", + "libp2p-swarm-derive 0.32.0 (git+https://github.com/ChainSafe/rust-libp2p.git?rev=e3440d25681df380c9f0f8cfdcfd5ecc0a4f2fb6)", + "log", + "rand 0.8.5", + "smallvec", + "tokio", + "void", +] + [[package]] name = "libp2p-swarm" version = "0.42.2" @@ -4547,9 +4811,9 @@ dependencies = [ "futures", "futures-timer", "instant", - "libp2p-core", + "libp2p-core 0.39.2", "libp2p-identity", - "libp2p-swarm-derive", + "libp2p-swarm-derive 0.32.0 (registry+https://github.com/rust-lang/crates.io-index)", "log", "rand 0.8.5", "smallvec", @@ -4568,6 +4832,16 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "libp2p-swarm-derive" +version = "0.32.0" +source = "git+https://github.com/ChainSafe/rust-libp2p.git?rev=e3440d25681df380c9f0f8cfdcfd5ecc0a4f2fb6#e3440d25681df380c9f0f8cfdcfd5ecc0a4f2fb6" +dependencies = [ + "heck 0.4.1", + "quote", + "syn 1.0.109", +] + [[package]] name = "libp2p-tcp" version = "0.39.0" @@ -4578,12 +4852,44 @@ dependencies = [ "futures-timer", "if-watch", "libc", - "libp2p-core", + "libp2p-core 0.39.2", "log", "socket2 0.4.9", "tokio", ] +[[package]] +name = "libp2p-tcp" +version = "0.39.0" +source = "git+https://github.com/ChainSafe/rust-libp2p.git?rev=e3440d25681df380c9f0f8cfdcfd5ecc0a4f2fb6#e3440d25681df380c9f0f8cfdcfd5ecc0a4f2fb6" +dependencies = [ + "futures", + "futures-timer", + "if-watch", + "libc", + "libp2p-core 0.39.0", + "log", + "socket2 0.4.9", + "tokio", +] + +[[package]] +name = "libp2p-tls" +version = "0.1.0-alpha.2" +source = "git+https://github.com/ChainSafe/rust-libp2p.git?rev=e3440d25681df380c9f0f8cfdcfd5ecc0a4f2fb6#e3440d25681df380c9f0f8cfdcfd5ecc0a4f2fb6" +dependencies = [ + "futures", + "futures-rustls", + "libp2p-core 0.39.0", + "rcgen 0.10.0", + "ring", + "rustls 0.20.8", + "thiserror", + "webpki 0.22.0", + "x509-parser 0.14.0", + "yasna", +] + [[package]] name = "libp2p-tls" version = "0.1.0" @@ -4592,7 +4898,7 @@ checksum = "ff08d13d0dc66e5e9ba6279c1de417b84fa0d0adc3b03e5732928c180ec02781" dependencies = [ "futures", "futures-rustls", - "libp2p-core", + "libp2p-core 0.39.2", "libp2p-identity", "rcgen 0.10.0", "ring", @@ -4603,6 +4909,36 @@ dependencies = [ "yasna", ] +[[package]] +name = "libp2p-webrtc" +version = "0.4.0-alpha.2" +source = "git+https://github.com/ChainSafe/rust-libp2p.git?rev=e3440d25681df380c9f0f8cfdcfd5ecc0a4f2fb6#e3440d25681df380c9f0f8cfdcfd5ecc0a4f2fb6" +dependencies = [ + "async-trait", + "asynchronous-codec", + "bytes", + "futures", + "futures-timer", + "hex", + "if-watch", + "libp2p-core 0.39.0", + "libp2p-noise 0.42.0", + "log", + "multihash", + "prost", + "prost-build", + "prost-codec", + "rand 0.8.5", + "rcgen 0.9.3", + "serde", + "stun", + "thiserror", + "tinytemplate", + "tokio", + "tokio-util", + "webrtc", +] + [[package]] name = "libp2p-webrtc" version = "0.4.0-alpha.4" @@ -4616,9 +4952,9 @@ dependencies = [ "futures-timer", "hex", "if-watch", - "libp2p-core", + "libp2p-core 0.39.2", "libp2p-identity", - "libp2p-noise", + "libp2p-noise 0.42.2", "log", "multihash", "quick-protobuf", @@ -4634,6 +4970,36 @@ dependencies = [ "webrtc", ] +[[package]] +name = "libp2p-websocket" +version = "0.41.0" +source = "git+https://github.com/ChainSafe/rust-libp2p.git?rev=e3440d25681df380c9f0f8cfdcfd5ecc0a4f2fb6#e3440d25681df380c9f0f8cfdcfd5ecc0a4f2fb6" +dependencies = [ + "either", + "futures", + "futures-rustls", + "libp2p-core 0.39.0", + "log", + "parking_lot 0.12.1", + "quicksink", + "rw-stream-sink 0.3.0 (git+https://github.com/ChainSafe/rust-libp2p.git?rev=e3440d25681df380c9f0f8cfdcfd5ecc0a4f2fb6)", + "soketto", + "url", + "webpki-roots 0.22.6", +] + +[[package]] +name = "libp2p-yamux" +version = "0.43.0" +source = "git+https://github.com/ChainSafe/rust-libp2p.git?rev=e3440d25681df380c9f0f8cfdcfd5ecc0a4f2fb6#e3440d25681df380c9f0f8cfdcfd5ecc0a4f2fb6" +dependencies = [ + "futures", + "libp2p-core 0.39.0", + "log", + "thiserror", + "yamux", +] + [[package]] name = "libp2p-yamux" version = "0.43.1" @@ -4641,7 +5007,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4dcd21d950662700a385d4c6d68e2f5f54d778e97068cdd718522222ef513bda" dependencies = [ "futures", - "libp2p-core", + "libp2p-core 0.39.2", "log", "thiserror", "yamux", @@ -4765,6 +5131,15 @@ dependencies = [ "tracing-subscriber", ] +[[package]] +name = "lru" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71e7d46de488603ffdd5f30afbc64fbba2378214a2c3a2fb83abf3d33126df17" +dependencies = [ + "hashbrown 0.13.2", +] + [[package]] name = "lru" version = "0.10.0" @@ -4944,6 +5319,23 @@ dependencies = [ "version_check", ] +[[package]] +name = "multiaddr" +version = "0.17.0" +source = "git+https://github.com/ChainSafe/rust-multiaddr.git?rev=2d21365f880622adf1c8e594b72e671dbac9dd64#2d21365f880622adf1c8e594b72e671dbac9dd64" +dependencies = [ + "arrayref", + "byteorder", + "data-encoding", + "multibase", + "multihash", + "percent-encoding", + "serde", + "static_assertions", + "unsigned-varint", + "url", +] + [[package]] name = "multiaddr" version = "0.17.1" @@ -5003,6 +5395,12 @@ dependencies = [ "synstructure", ] +[[package]] +name = "multimap" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5ce46fe64a9d73be07dcbe690a38ce1b293be448fd8ce1e6c1b8062c9f72c6a" + [[package]] name = "multistream-select" version = "0.12.1" @@ -5017,6 +5415,19 @@ dependencies = [ "unsigned-varint", ] +[[package]] +name = "multistream-select" +version = "0.12.1" +source = "git+https://github.com/ChainSafe/rust-libp2p.git?rev=e3440d25681df380c9f0f8cfdcfd5ecc0a4f2fb6#e3440d25681df380c9f0f8cfdcfd5ecc0a4f2fb6" +dependencies = [ + "bytes", + "futures", + "log", + "pin-project", + "smallvec", + "unsigned-varint", +] + [[package]] name = "native-tls" version = "0.2.11" @@ -6159,6 +6570,8 @@ dependencies = [ "bip39", "dotenvy", "futures", + "hex", + "libp2p 0.51.0", "log", "nym-bandwidth-controller", "nym-bin-common", @@ -6173,12 +6586,15 @@ dependencies = [ "nym-task", "nym-topology", "nym-validator-client", + "parking_lot 0.12.1", "pretty_env_logger", "rand 0.7.3", "reqwest", "tap", "thiserror", "tokio", + "tokio-stream", + "tokio-util", "toml 0.5.11", "url", ] @@ -6821,7 +7237,7 @@ dependencies = [ "futures-util", "indexmap 1.9.3", "once_cell", - "pin-project-lite", + "pin-project-lite 0.2.12", "thiserror", "urlencoding", ] @@ -7147,6 +7563,16 @@ dependencies = [ "sha2 0.10.7", ] +[[package]] +name = "petgraph" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1d3afd2628e69da2be385eb6f2fd57c8ac7977ceeff6dc166ff1657b0e386a9" +dependencies = [ + "fixedbitset", + "indexmap 2.0.0", +] + [[package]] name = "pin-project" version = "1.1.3" @@ -7167,6 +7593,12 @@ dependencies = [ "syn 2.0.28", ] +[[package]] +name = "pin-project-lite" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "257b64915a082f7811703966789728173279bdebb956b143dbcd23f6f970a777" + [[package]] name = "pin-project-lite" version = "0.2.12" @@ -7251,7 +7683,7 @@ dependencies = [ "concurrent-queue", "libc", "log", - "pin-project-lite", + "pin-project-lite 0.2.12", "windows-sys 0.48.0", ] @@ -7416,6 +7848,40 @@ dependencies = [ "prost-derive", ] +[[package]] +name = "prost-build" +version = "0.11.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "119533552c9a7ffacc21e099c24a0ac8bb19c2a2a3f363de84cd9b844feab270" +dependencies = [ + "bytes", + "heck 0.4.1", + "itertools", + "lazy_static", + "log", + "multimap", + "petgraph", + "prettyplease", + "prost", + "prost-types", + "regex", + "syn 1.0.109", + "tempfile", + "which", +] + +[[package]] +name = "prost-codec" +version = "0.3.0" +source = "git+https://github.com/ChainSafe/rust-libp2p.git?rev=e3440d25681df380c9f0f8cfdcfd5ecc0a4f2fb6#e3440d25681df380c9f0f8cfdcfd5ecc0a4f2fb6" +dependencies = [ + "asynchronous-codec", + "bytes", + "prost", + "thiserror", + "unsigned-varint", +] + [[package]] name = "prost-derive" version = "0.11.9" @@ -7483,6 +7949,17 @@ dependencies = [ "unsigned-varint", ] +[[package]] +name = "quicksink" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77de3c815e5a160b1539c6592796801df2043ae35e123b46d73380cfa57af858" +dependencies = [ + "futures-core", + "futures-sink", + "pin-project-lite 0.1.12", +] + [[package]] name = "quinn-proto" version = "0.9.3" @@ -7937,7 +8414,7 @@ dependencies = [ "native-tls", "once_cell", "percent-encoding", - "pin-project-lite", + "pin-project-lite 0.2.12", "serde", "serde_json", "serde_urlencoded", @@ -8028,7 +8505,7 @@ dependencies = [ "multer", "num_cpus", "parking_lot 0.12.1", - "pin-project-lite", + "pin-project-lite 0.2.12", "rand 0.8.5", "ref-cast", "rocket_codegen", @@ -8093,7 +8570,7 @@ dependencies = [ "memchr", "pear", "percent-encoding", - "pin-project-lite", + "pin-project-lite 0.2.12", "ref-cast", "serde", "smallvec", @@ -8362,6 +8839,16 @@ dependencies = [ "static_assertions", ] +[[package]] +name = "rw-stream-sink" +version = "0.3.0" +source = "git+https://github.com/ChainSafe/rust-libp2p.git?rev=e3440d25681df380c9f0f8cfdcfd5ecc0a4f2fb6#e3440d25681df380c9f0f8cfdcfd5ecc0a4f2fb6" +dependencies = [ + "futures", + "pin-project", + "static_assertions", +] + [[package]] name = "ryu" version = "1.0.15" @@ -8911,6 +9398,22 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "soketto" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d1c5305e39e09653383c2c7244f2f78b3bcae37cf50c64cb4789c9f5096ec2" +dependencies = [ + "base64 0.13.1", + "bytes", + "flate2", + "futures", + "httparse", + "log", + "rand 0.8.5", + "sha-1 0.9.8", +] + [[package]] name = "sphinx-packet" version = "0.1.0" @@ -9638,7 +10141,7 @@ dependencies = [ "mio", "num_cpus", "parking_lot 0.12.1", - "pin-project-lite", + "pin-project-lite 0.2.12", "signal-hook-registry", "socket2 0.5.3", "tokio-macros", @@ -9652,7 +10155,7 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "30b74022ada614a1b4834de765f9bb43877f910cc8ce4be40e89042c9223a8bf" dependencies = [ - "pin-project-lite", + "pin-project-lite 0.2.12", "tokio", ] @@ -9718,7 +10221,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "397c988d37662c7dda6d2208364a706264bf3d6138b11d436cbac0ad38832842" dependencies = [ "futures-core", - "pin-project-lite", + "pin-project-lite 0.2.12", "tokio", "tokio-util", ] @@ -9785,7 +10288,7 @@ dependencies = [ "futures-sink", "futures-util", "hashbrown 0.12.3", - "pin-project-lite", + "pin-project-lite 0.2.12", "slab", "tokio", "tracing", @@ -9872,7 +10375,7 @@ dependencies = [ "futures-util", "indexmap 1.9.3", "pin-project", - "pin-project-lite", + "pin-project-lite 0.2.12", "rand 0.8.5", "slab", "tokio", @@ -9902,7 +10405,7 @@ checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8" dependencies = [ "cfg-if", "log", - "pin-project-lite", + "pin-project-lite 0.2.12", "tracing-attributes", "tracing-core", ] @@ -10871,6 +11374,17 @@ dependencies = [ "winapi", ] +[[package]] +name = "which" +version = "4.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2441c784c52b289a054b7201fc93253e288f094e2f4be9058343127c4226a269" +dependencies = [ + "either", + "libc", + "once_cell", +] + [[package]] name = "widestring" version = "0.5.1" diff --git a/sdk/rust/nym-sdk/Cargo.toml b/sdk/rust/nym-sdk/Cargo.toml index e2b5d86d7e..f3d1d47aae 100644 --- a/sdk/rust/nym-sdk/Cargo.toml +++ b/sdk/rust/nym-sdk/Cargo.toml @@ -36,3 +36,13 @@ reqwest = { workspace = true, features = ["json", "socks"] } thiserror = { workspace = true } tokio = { version = "1", features = ["full"] } nym-bin-common = { path = "../../../common/bin-common" } + +# extra dependencies for libp2p examples +libp2p = { git = "https://github.com/ChainSafe/rust-libp2p.git", rev = "e3440d25681df380c9f0f8cfdcfd5ecc0a4f2fb6", features = [ "identify", "macros", "ping", "tokio", "tcp", "dns", "websocket", "noise", "mplex", "yamux", "gossipsub" ]} +tokio-stream = "0.1.12" +tokio-util = { version = "0.7", features = ["codec"] } +parking_lot = "0.12" +hex = "0.4" + +[features] +libp2p-vanilla = [] \ No newline at end of file diff --git a/sdk/rust/nym-sdk/examples/libp2p_chat/README.md b/sdk/rust/nym-sdk/examples/libp2p_chat/README.md new file mode 100644 index 0000000000..0287329769 --- /dev/null +++ b/sdk/rust/nym-sdk/examples/libp2p_chat/README.md @@ -0,0 +1,64 @@ +# rust-libp2p-nym + +This repo contains an example implementation of a libp2p transport using the Nym mixnet. It relies on the ChainSafe's fork of libp2p: https://github.com/ChainSafe/rust-libp2p + +## Requirements + +- Rust 1.68.2 +- `Protoc` protobuf compiler. On Debian/Ubuntu distributed via `apt` as `protobuf-compiler` & on Arch/Manjaro via AUR as `[python-protobuf-compiler](https://aur.archlinux.org/packages/python-protobuf-compiler)`. + +## Usage + +To instantiate a libp2p swarm using the transport: + +```rust +use libp2p::core::{muxing::StreamMuxerBox, transport::Transport}; +use libp2p::swarm::{keep_alive::Behaviour, SwarmBuilder}; +use libp2p::{identity, PeerId}; +use nym_sdk::mixnet::MixnetClient; +use rust_libp2p_nym::transport::NymTransport; +use rust_libp2p_nym::test_utils::create_nym_client; +use std::error::Error; + +#[tokio::main] +async fn main() -> Result<(), Box> { + let local_key = identity::Keypair::generate_ed25519(); + let local_peer_id = PeerId::from(local_key.public()); + info!("Local peer id: {local_peer_id:?}"); + + let nym_id = rand::random::().to_string(); + let nym_client = MixnetClient::connect_new().await.unwrap(); + let transport = NymTransport::new(nym_client, local_key.clone()).await?; + let _swarm = SwarmBuilder::with_tokio_executor( + transport + .map(|a, _| (a.0, StreamMuxerBox::new(a.1))) + .boxed(), + Behaviour::default(), + local_peer_id, + ) + .build(); + Ok(()) +} +``` + +## Chat example + +To run the libp2p chat example, run the following in one terminal: +```bash +cargo run --example libp2p_chat +# Local peer id: PeerId("12D3KooWLukBu6q2FerWPFhFFhiYaJkhn2sBmceh9UCaXe6hJf5D") +# Listening on "/nym/FhtkzizQg2JbZ19kGkRKXdjV2QnFbT5ww88ZAKaD4nkF.7Remi4UVYzn1yL3qYtEcQBGh6tzTYxMdYB4uqyHVc5Z4@62F81C9GrHDRja9WCqozemRFSzFPMecY85MbGwn6efve" +``` + +In another terminal, run ping again, passing the Nym multiaddress printed previously: +```bash +cargo run --example libp2p_chat -- /nym/FhtkzizQg2JbZ19kGkRKXdjV2QnFbT5ww88ZAKaD4nkF.7Remi4UVYzn1yL3qYtEcQBGh6tzTYxMdYB4uqyHVc5Z4@62F81C9GrHDRja9WCqozemRFSzFPMecY85MbGwn6efve +# Local peer id: PeerId("12D3KooWNsuRwG6DHnFJCDR8B3zdvja6xLcfnbtKCsQWJ8eppyWC") +# Dialed /nym/FhtkzizQg2JbZ19kGkRKXdjV2QnFbT5ww88ZAKaD4nkF.7Remi4UVYzn1yL3qYtEcQBGh6tzTYxMdYB4uqyHVc5Z4@62F81C9GrHDRja9WCqozemRFSzFPMecY85MbGwn6efve +# Listening on "/nym/2oiRW5C9ivyF3Bo3Gpm4H9EqSKH7A6GpcrRRwVSDVUQ9.EajgCnhzimsP6KskUwKcEj8VFCmHR78s2J6FHWcZ4etR@Fo4f4SQLdoyoGkFae5TpVhRVoXCF8UiypLVGtGjujVPf" +``` + +You should see that the nodes connected and sent messages to each other: +```bash +# 2023-08-10T14:06:28.116Z INFO libp2p_chat > Got message: 'hello world' with id: 37393732353836333838333537303637303237 from peer: 12D3KooWB6k8ZGDF44N4FMRhgVBNihwk1wMYSumosxiZq9pUTbAz +``` diff --git a/sdk/rust/nym-sdk/examples/libp2p_chat/main.rs b/sdk/rust/nym-sdk/examples/libp2p_chat/main.rs new file mode 100644 index 0000000000..04e1ecca57 --- /dev/null +++ b/sdk/rust/nym-sdk/examples/libp2p_chat/main.rs @@ -0,0 +1,171 @@ +// Copyright 2018 Parity Technologies (UK) Ltd. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +//! A basic chat application with logs demonstrating libp2p and the gossipsub protocol +//! combined with mDNS for the discovery of peers to gossip with. +//! +//! Using two terminal windows, start two instances, typing the following in each: +//! +//! ```sh +//! cargo run +//! ``` +//! +//! Mutual mDNS discovery may take a few seconds. When each peer does discover the other +//! it will print a message like: +//! +//! ```sh +//! mDNS discovered a new peer: {peerId} +//! ``` +//! +//! Type a message and hit return: the message is sent and printed in the other terminal. +//! Close with Ctrl-c. +//! +//! You can open more terminal windows and add more peers using the same line above. +//! +//! Once an additional peer is mDNS discovered it can participate in the conversation +//! and all peers will receive messages sent from it. +//! +//! If a participant exits (Control-C or otherwise) the other peers will receive an mDNS expired +//! event and remove the expired peer from the list of known peers. + +use crate::rust_libp2p_nym::transport::NymTransport; +use futures::{prelude::*, select}; +use libp2p::Multiaddr; +use libp2p::{ + core::muxing::StreamMuxerBox, + gossipsub, identity, + swarm::NetworkBehaviour, + swarm::{SwarmBuilder, SwarmEvent}, + PeerId, Transport, +}; +use log::{error, info, LevelFilter}; +use nym_sdk::mixnet::MixnetClient; +use std::collections::hash_map::DefaultHasher; +use std::error::Error; +use std::hash::{Hash, Hasher}; +use std::time::Duration; +use tokio::io; +use tokio_util::codec; + +#[path = "../libp2p_shared/lib.rs"] +mod rust_libp2p_nym; + +// We create a custom network behaviour that uses Gossipsub +#[derive(NetworkBehaviour)] +struct Behaviour { + gossipsub: gossipsub::Behaviour, +} + +#[tokio::main] +async fn main() -> Result<(), Box> { + pretty_env_logger::formatted_timed_builder() + .filter_level(LevelFilter::Warn) + .filter(Some("libp2p_chat"), LevelFilter::Info) + .init(); + + // Create a random PeerId + let id_keys = identity::Keypair::generate_ed25519(); + let local_peer_id = PeerId::from(id_keys.public()); + info!("Local peer id: {local_peer_id}"); + + // To content-address message, we can take the hash of message and use it as an ID. + let message_id_fn = |message: &gossipsub::Message| { + let mut s = DefaultHasher::new(); + message.data.hash(&mut s); + gossipsub::MessageId::from(s.finish().to_string()) + }; + + // Set a custom gossipsub configuration + let gossipsub_config = gossipsub::ConfigBuilder::default() + .heartbeat_interval(Duration::from_secs(10)) // This is set to aid debugging by not cluttering the log space + .validation_mode(gossipsub::ValidationMode::Strict) // This sets the kind of message validation. The default is Strict (enforce message signing) + .message_id_fn(message_id_fn) // content-address messages. No two messages of the same content will be propagated. + .build() + .expect("Valid config"); + + // build a gossipsub network behaviour + let mut gossipsub = gossipsub::Behaviour::new( + gossipsub::MessageAuthenticity::Signed(id_keys), + gossipsub_config, + ) + .expect("Correct configuration"); + // Create a Gossipsub topic + let topic = gossipsub::IdentTopic::new("test-net"); + // subscribes to our topic + gossipsub.subscribe(&topic)?; + + let client = MixnetClient::connect_new().await.unwrap(); + info!("client address: {}", client.nym_address()); + + let local_key = identity::Keypair::generate_ed25519(); + let local_peer_id = PeerId::from(local_key.public()); + info!("Local peer id: {local_peer_id:?}"); + + let transport = NymTransport::new(client, local_key).await?; + + let mut swarm = SwarmBuilder::with_tokio_executor( + transport + .map(|a, _| (a.0, StreamMuxerBox::new(a.1))) + .boxed(), + Behaviour { gossipsub }, + local_peer_id, + ) + .build(); + + if let Some(addr) = std::env::args().nth(1) { + let remote: Multiaddr = addr.parse()?; + swarm.dial(remote)?; + info!("Dialed {addr}") + } + + // Read full lines from stdin + let mut stdin = codec::FramedRead::new(io::stdin(), codec::LinesCodec::new()).fuse(); + + info!("Enter messages via STDIN and they will be sent to connected peers using Gossipsub"); + + // Kick it off + loop { + select! { + line = stdin.select_next_some() => { + if let Err(e) = swarm + .behaviour_mut().gossipsub + .publish(topic.clone(), line.expect("Stdin not to close").as_bytes()) { + error!("Publish error: {e:?}"); + } + }, + event = swarm.select_next_some() => { + match event { + SwarmEvent::Behaviour(BehaviourEvent::Gossipsub(gossipsub::Event::Message { + propagation_source: peer_id, + message_id: id, + message, + })) => info!( + "Got message: '{}' with id: {id} from peer: {peer_id}", + String::from_utf8_lossy(&message.data), + ), + SwarmEvent::NewListenAddr { address, .. } => { + info!("Local node is listening on {address}"); + } + other => {info!("other event: {:?}", other)} + } + } + } + } +} diff --git a/sdk/rust/nym-sdk/examples/libp2p_ping/README.md b/sdk/rust/nym-sdk/examples/libp2p_ping/README.md new file mode 100644 index 0000000000..2218793e89 --- /dev/null +++ b/sdk/rust/nym-sdk/examples/libp2p_ping/README.md @@ -0,0 +1,79 @@ +# rust-libp2p-nym + +This repo contains an example implementation of a libp2p transport using the Nym mixnet. It relies on the ChainSafe's fork of libp2p: https://github.com/ChainSafe/rust-libp2p + +## Requirements + +- Rust 1.68.2 +- `Protoc` protobuf compiler. On Debian/Ubuntu distributed via `apt` as `protobuf-compiler` & on Arch/Manjaro via AUR as `[python-protobuf-compiler](https://aur.archlinux.org/packages/python-protobuf-compiler)`. + +## Usage + +To instantiate a libp2p swarm using the transport: + +```rust +use libp2p::core::{muxing::StreamMuxerBox, transport::Transport}; +use libp2p::swarm::{keep_alive::Behaviour, SwarmBuilder}; +use libp2p::{identity, PeerId}; +use nym_sdk::mixnet::MixnetClient; +use rust_libp2p_nym::transport::NymTransport; +use rust_libp2p_nym::test_utils::create_nym_client; +use std::error::Error; + +#[tokio::main] +async fn main() -> Result<(), Box> { + let local_key = identity::Keypair::generate_ed25519(); + let local_peer_id = PeerId::from(local_key.public()); + info!("Local peer id: {local_peer_id:?}"); + + let nym_id = rand::random::().to_string(); + let nym_client = MixnetClient::connect_new().await.unwrap(); + let transport = NymTransport::new(nym_client, local_key.clone()).await?; + let _swarm = SwarmBuilder::with_tokio_executor( + transport + .map(|a, _| (a.0, StreamMuxerBox::new(a.1))) + .boxed(), + Behaviour::default(), + local_peer_id, + ) + .build(); + Ok(()) +} +``` + +## Ping example + +To run the libp2p ping example, run the following in one terminal: +```bash +cargo run --example libp2p_ping +# Local peer id: PeerId("12D3KooWLukBu6q2FerWPFhFFhiYaJkhn2sBmceh9UCaXe6hJf5D") +# Listening on "/nym/FhtkzizQg2JbZ19kGkRKXdjV2QnFbT5ww88ZAKaD4nkF.7Remi4UVYzn1yL3qYtEcQBGh6tzTYxMdYB4uqyHVc5Z4@62F81C9GrHDRja9WCqozemRFSzFPMecY85MbGwn6efve" +``` + +In another terminal, run ping again, passing the Nym multiaddress printed previously: +```bash +cargo run --example libp2p_ping -- /nym/FhtkzizQg2JbZ19kGkRKXdjV2QnFbT5ww88ZAKaD4nkF.7Remi4UVYzn1yL3qYtEcQBGh6tzTYxMdYB4uqyHVc5Z4@62F81C9GrHDRja9WCqozemRFSzFPMecY85MbGwn6efve +# Local peer id: PeerId("12D3KooWNsuRwG6DHnFJCDR8B3zdvja6xLcfnbtKCsQWJ8eppyWC") +# Dialed /nym/FhtkzizQg2JbZ19kGkRKXdjV2QnFbT5ww88ZAKaD4nkF.7Remi4UVYzn1yL3qYtEcQBGh6tzTYxMdYB4uqyHVc5Z4@62F81C9GrHDRja9WCqozemRFSzFPMecY85MbGwn6efve +# Listening on "/nym/2oiRW5C9ivyF3Bo3Gpm4H9EqSKH7A6GpcrRRwVSDVUQ9.EajgCnhzimsP6KskUwKcEj8VFCmHR78s2J6FHWcZ4etR@Fo4f4SQLdoyoGkFae5TpVhRVoXCF8UiypLVGtGjujVPf" +``` + +You should see that the nodes connected and pinged each other: +```bash +# Mar 30 22:56:36.400 INFO ping: BehaviourEvent: Event { peer: PeerId("12D3KooWGf2oYd6U2nrLzfDrN9zxsjSQjPsMh2oDJPUQ9hiHMNtf"), result: Ok(Ping { rtt: 1.06836675s }) } +``` +```bash +# Mar 30 22:56:35.595 INFO ping: BehaviourEvent: Event { peer: PeerId("12D3KooWMd5ak31DXuZq7x1JuFSR6toA5RDQrPaHrfXEhy7vqqpC"), result: Ok(Pong) } +``` + +In order to run the ping example with vanilla libp2p, which uses tcp, pass the +`--features libp2p-vanilla` flag to the example and follow the instructions on the +rust-libp2p project as usual. + +```bash +RUST_LOG=ping=debug cargo run --example ping --features libp2p-vanilla +``` + +```bash +RUST_LOG=ping=debug cargo run --example ping --features libp2p-vanilla -- "/ip4/127.0.0.1/tcp/$PORT" +``` diff --git a/sdk/rust/nym-sdk/examples/libp2p_ping/main.rs b/sdk/rust/nym-sdk/examples/libp2p_ping/main.rs new file mode 100644 index 0000000000..24ea089c10 --- /dev/null +++ b/sdk/rust/nym-sdk/examples/libp2p_ping/main.rs @@ -0,0 +1,142 @@ +// Copyright 2018 Parity Technologies (UK) Ltd. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +//! Ping example +//! +//! See ../src/tutorial.rs for a step-by-step guide building the example below. +//! +//! In the first terminal window, run: +//! +//! ```sh +//! cargo run --example ping --features=full +//! ``` +//! +//! It will print the PeerId and the listening addresses, e.g. `Listening on +//! "/ip4/0.0.0.0/tcp/24915"` +//! +//! In the second terminal window, start a new instance of the example with: +//! +//! ```sh +//! cargo run --example ping --features=full -- /ip4/127.0.0.1/tcp/24915 +//! ``` +//! +//! The two nodes establish a connection, negotiate the ping protocol +//! and begin pinging each other. + +use libp2p::futures::StreamExt; +use libp2p::ping::Success; +use libp2p::swarm::{keep_alive, NetworkBehaviour, SwarmEvent}; +use libp2p::{identity, ping, Multiaddr, PeerId}; +use log::{debug, info, LevelFilter}; +use nym_sdk::mixnet::MixnetClient; +use std::error::Error; +use std::time::Duration; + +#[path = "../libp2p_shared/lib.rs"] +mod rust_libp2p_nym; + +#[tokio::main] +async fn main() -> Result<(), Box> { + pretty_env_logger::formatted_timed_builder() + .filter_level(LevelFilter::Warn) + .filter(Some("libp2p_ping"), LevelFilter::Debug) + .init(); + + let local_key = identity::Keypair::generate_ed25519(); + let local_peer_id = PeerId::from(local_key.public()); + info!("Local peer id: {local_peer_id:?}"); + + #[cfg(not(feature = "libp2p-vanilla"))] + let mut swarm = { + debug!("Running `ping` example using NymTransport"); + use libp2p::core::{muxing::StreamMuxerBox, transport::Transport}; + use libp2p::swarm::SwarmBuilder; + use rust_libp2p_nym::transport::NymTransport; + + let client = MixnetClient::connect_new().await.unwrap(); + + let transport = NymTransport::new(client, local_key.clone()).await?; + SwarmBuilder::with_tokio_executor( + transport + .map(|a, _| (a.0, StreamMuxerBox::new(a.1))) + .boxed(), + Behaviour::default(), + local_peer_id, + ) + .build() + }; + + #[cfg(feature = "libp2p-vanilla")] + let mut swarm = { + debug!("Running `ping` example using the vanilla libp2p tokio_development_transport"); + let transport = libp2p::tokio_development_transport(local_key)?; + let mut swarm = + libp2p::Swarm::with_tokio_executor(transport, Behaviour::default(), local_peer_id); + swarm.listen_on("/ip4/0.0.0.0/tcp/0".parse()?)?; + swarm + }; + + // Dial the peer identified by the multi-address given as the second + // command-line argument, if any. + if let Some(addr) = std::env::args().nth(1) { + let remote: Multiaddr = addr.parse()?; + swarm.dial(remote)?; + info!("Dialed {addr}") + } + + let mut total_ping_rtt: Duration = Duration::from_micros(0); + let mut counter: u128 = 0; + loop { + match swarm.select_next_some().await { + SwarmEvent::NewListenAddr { address, .. } => info!("Listening on {address:?}"), + SwarmEvent::Behaviour(event) => { + // Get the round-trip duration for the pings. + // This value is already captured in the BehaviourEvent::Ping's `Success::Ping` + // field. + debug!("{event:?}"); + if let BehaviourEvent::Ping(ping_event) = event { + let result: Success = ping_event.result?; + match result { + Success::Ping { rtt } => { + counter += 1; + total_ping_rtt += rtt; + let average_ping_rtt = Duration::from_micros( + (total_ping_rtt.as_micros() / counter).try_into().unwrap(), + ); + info!("Ping RTT: {rtt:?} AVERAGE RTT: ({counter} pings): {average_ping_rtt:?}"); + } + Success::Pong => info!("Pong Event"), + } + } + } + _ => {} + } + } +} + +/// Our network behaviour. +/// +/// For illustrative purposes, this includes the [`KeepAlive`](behaviour::KeepAlive) behaviour so a continuous sequence of +/// pings can be observed. +#[derive(NetworkBehaviour, Default)] +struct Behaviour { + keep_alive: keep_alive::Behaviour, + ping: ping::Behaviour, +} diff --git a/sdk/rust/nym-sdk/examples/libp2p_shared/connection.rs b/sdk/rust/nym-sdk/examples/libp2p_shared/connection.rs new file mode 100644 index 0000000000..fd5c41ccd1 --- /dev/null +++ b/sdk/rust/nym-sdk/examples/libp2p_shared/connection.rs @@ -0,0 +1,418 @@ +use libp2p::core::{muxing::StreamMuxerEvent, PeerId, StreamMuxer}; +use log::debug; +use nym_sphinx::addressing::clients::Recipient; +use std::{ + collections::{HashMap, HashSet}, + pin::Pin, + sync::{ + atomic::{AtomicU64, Ordering}, + Arc, + }, + task::{Context, Poll, Waker}, +}; +use tokio::sync::{ + mpsc::{unbounded_channel, UnboundedReceiver, UnboundedSender}, + oneshot, +}; + +use super::error::Error; +use super::message::{ + ConnectionId, Message, OutboundMessage, SubstreamId, SubstreamMessage, SubstreamMessageType, + TransportMessage, +}; +use super::substream::Substream; + +/// Connection represents the result of a connection setup process. +/// It implements `StreamMuxer` and thus has stream multiplexing built in. +#[derive(Debug)] +pub struct Connection { + pub(crate) peer_id: PeerId, + pub(crate) remote_recipient: Recipient, + pub(crate) id: ConnectionId, + + /// receive inbound messages from the `InnerConnection` + pub(crate) inbound_rx: UnboundedReceiver, + + /// substream ID -> outbound pending substream exists + /// the key is deleted when the response is received, or the request times out + pending_substreams: HashSet, + + /// substream ID -> substream's inbound_tx channel + substream_inbound_txs: HashMap>>, + + /// substream ID -> substream's close_tx channel + substream_close_txs: HashMap>, + + /// send messages to the mixnet + /// used for sending `SubstreamMessageType::OpenRequest` messages + /// also passed to each substream so they can write to the mixnet + pub(crate) mixnet_outbound_tx: UnboundedSender, + + /// inbound substream open requests; used in poll_inbound + inbound_open_tx: UnboundedSender, + inbound_open_rx: UnboundedReceiver, + + /// closed substream IDs; used in poll_close + close_tx: UnboundedSender, + close_rx: UnboundedReceiver, + + /// message nonce contains the next nonce that should be used when + /// sending a message over the connection + pub(crate) message_nonce: Arc, + + waker: Option, +} + +impl Connection { + pub(crate) fn new( + peer_id: PeerId, + remote_recipient: Recipient, + id: ConnectionId, + inbound_rx: UnboundedReceiver, + mixnet_outbound_tx: UnboundedSender, + ) -> Self { + let (inbound_open_tx, inbound_open_rx) = unbounded_channel(); + let (close_tx, close_rx) = unbounded_channel(); + + Connection { + peer_id, + remote_recipient, + id, + inbound_rx, + pending_substreams: HashSet::new(), + substream_inbound_txs: HashMap::new(), + substream_close_txs: HashMap::new(), + mixnet_outbound_tx, + inbound_open_tx, + inbound_open_rx, + close_tx, + close_rx, + message_nonce: Arc::new(AtomicU64::new(1)), + waker: None, + } + } + + fn new_outbound_substream(&mut self) -> Result { + let substream_id = SubstreamId::generate(); + let nonce = self.message_nonce.fetch_add(1, Ordering::SeqCst); + + // send the substream open request that requests to open a substream with the given ID + self.mixnet_outbound_tx + .send(OutboundMessage { + recipient: self.remote_recipient, + message: Message::TransportMessage(TransportMessage { + nonce, + id: self.id.clone(), + message: SubstreamMessage { + substream_id: substream_id.clone(), + message_type: SubstreamMessageType::OpenRequest, + }, + }), + }) + .map_err(|e| Error::OutboundSendError(e.to_string()))?; + + // track pending outbound substreams + // TODO we should probably lock this? storing map values should be atomic + let res = self.new_substream(substream_id.clone()); + if res.is_ok() { + self.pending_substreams.insert(substream_id); + } + res + } + + // creates a new substream instance with the given ID. + fn new_substream(&mut self, id: SubstreamId) -> Result { + // check we don't already have a substream with this ID + if self.substream_inbound_txs.get(&id).is_some() { + return Err(Error::SubstreamIdExists(id)); + } + + let (inbound_tx, inbound_rx) = unbounded_channel::>(); + let (close_tx, close_rx) = oneshot::channel::<()>(); + self.substream_inbound_txs.insert(id.clone(), inbound_tx); + self.substream_close_txs.insert(id.clone(), close_tx); + + if let Some(waker) = self.waker.take() { + waker.wake(); + } + + Ok(Substream::new( + self.remote_recipient, + self.id.clone(), + id, + inbound_rx, + self.mixnet_outbound_tx.clone(), + close_rx, + self.message_nonce.clone(), + )) + } + + fn handle_close(&mut self, substream_id: SubstreamId) -> Result<(), Error> { + if self.substream_inbound_txs.remove(&substream_id).is_none() { + return Err(Error::SubstreamIdDoesNotExist(substream_id)); + } + + // notify substream that it's closed + let close_tx = self.substream_close_txs.remove(&substream_id); + close_tx.unwrap().send(()).unwrap(); + + // notify poll_close that the substream is closed + self.close_tx + .send(substream_id) + .map_err(|e| Error::InboundSendError(e.to_string())) + } +} + +impl StreamMuxer for Connection { + type Substream = Substream; + type Error = Error; + + fn poll_inbound( + mut self: Pin<&mut Self>, + cx: &mut Context<'_>, + ) -> Poll> { + if let Poll::Ready(Some(substream)) = self.inbound_open_rx.poll_recv(cx) { + return Poll::Ready(Ok(substream)); + } + + Poll::Pending + } + + fn poll_outbound( + mut self: Pin<&mut Self>, + _cx: &mut Context<'_>, + ) -> Poll> { + Poll::Ready(self.new_outbound_substream()) + } + + fn poll_close(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + if let Poll::Ready(Some(_)) = self.close_rx.poll_recv(cx) { + return Poll::Ready(Ok(())); + } + + Poll::Pending + } + + fn poll( + mut self: Pin<&mut Self>, + cx: &mut Context<'_>, + ) -> Poll> { + while let Poll::Ready(Some(msg)) = self.inbound_rx.poll_recv(cx) { + match msg.message_type { + SubstreamMessageType::OpenRequest => { + // create a new substream with the given ID + let substream = self.new_substream(msg.substream_id.clone())?; + let nonce = self.message_nonce.fetch_add(1, Ordering::SeqCst); + + // send the response to the remote peer + self.mixnet_outbound_tx + .send(OutboundMessage { + recipient: self.remote_recipient, + message: Message::TransportMessage(TransportMessage { + nonce, + id: self.id.clone(), + message: SubstreamMessage { + substream_id: msg.substream_id.clone(), + message_type: SubstreamMessageType::OpenResponse, + }, + }), + }) + .map_err(|e| Error::OutboundSendError(e.to_string()))?; + debug!("wrote OpenResponse for substream: {:?}", &msg.substream_id); + + // send the substream to our own channel to be returned in poll_inbound + self.inbound_open_tx + .send(substream) + .map_err(|e| Error::InboundSendError(e.to_string()))?; + + debug!("new inbound substream: {:?}", &msg.substream_id); + } + SubstreamMessageType::OpenResponse => { + if !self.pending_substreams.remove(&msg.substream_id) { + debug!( + "SubstreamMessageType::OpenResponse no substream pending for ID: {:?}", + &msg.substream_id + ); + } + } + SubstreamMessageType::Close => { + self.handle_close(msg.substream_id)?; + } + SubstreamMessageType::Data(data) => { + debug!("SubstreamMessageType::Data: {:?}", &data); + let inbound_tx = self + .substream_inbound_txs + .get_mut(&msg.substream_id) + .expect("must have a substream channel for substream"); + + // NOTE: this ignores channel closed errors, which is fine because the substream + // might have been closed/dropped + inbound_tx.send(data).ok(); + } + } + } + + self.waker = Some(cx.waker().clone()); + Poll::Pending + } +} + +/// PendingConnection represents a connection that's been initiated, but not completed. +pub(crate) struct PendingConnection { + pub(crate) remote_recipient: Recipient, + pub(crate) connection_tx: oneshot::Sender, +} + +impl PendingConnection { + pub(crate) fn new( + remote_recipient: Recipient, + connection_tx: oneshot::Sender, + ) -> Self { + PendingConnection { + remote_recipient, + connection_tx, + } + } +} + +#[cfg(test)] +mod test { + use super::super::message::InboundMessage; + use super::super::mixnet::initialize_mixnet; + use super::*; + use futures::future::poll_fn; + use futures::{AsyncReadExt, AsyncWriteExt, FutureExt}; + use nym_sdk::mixnet::MixnetClient; + + async fn inbound_receive_and_send( + connection_id: ConnectionId, + mixnet_inbound_rx: &mut UnboundedReceiver, + inbound_tx: &UnboundedSender, + expected_nonce: u64, + ) { + let recv_msg = mixnet_inbound_rx.recv().await.unwrap(); + match recv_msg.0 { + Message::TransportMessage(TransportMessage { + nonce, + id, + message: msg, + }) => { + assert_eq!(nonce, expected_nonce); + assert_eq!(id, connection_id); + inbound_tx.send(msg).unwrap(); + } + _ => panic!("unexpected message"), + } + } + + #[tokio::test] + async fn test_connection_stream_muxer() { + let client = MixnetClient::connect_new().await.unwrap(); + let (sender_address, mut sender_mixnet_inbound_rx, sender_outbound_tx) = + initialize_mixnet(client, None).await.unwrap(); + + let client2 = MixnetClient::connect_new().await.unwrap(); + + let (recipient_address, mut recipient_mixnet_inbound_rx, recipient_outbound_tx) = + initialize_mixnet(client2, None).await.unwrap(); + + let connection_id = ConnectionId::generate(); + + let recipient_peer_id = PeerId::random(); + let sender_peer_id = PeerId::random(); + + // create the connections + let (sender_inbound_tx, sender_inbound_rx) = unbounded_channel::(); + let mut sender_connection = Connection::new( + recipient_peer_id, + recipient_address, + connection_id.clone(), + sender_inbound_rx, + sender_outbound_tx, + ); + let (recipient_inbound_tx, recipient_inbound_rx) = unbounded_channel::(); + let mut recipient_connection = Connection::new( + sender_peer_id, + sender_address, + connection_id.clone(), + recipient_inbound_rx, + recipient_outbound_tx, + ); + + // send the substream OpenRequest to the mixnet + let mut sender_substream = sender_connection.new_outbound_substream().unwrap(); + assert!(sender_connection + .pending_substreams + .contains(&sender_substream.substream_id)); + assert_eq!(sender_connection.message_nonce.load(Ordering::SeqCst), 2); + + // poll the recipient inbound stream; should receive the OpenRequest and create the substream + inbound_receive_and_send( + connection_id.clone(), + &mut recipient_mixnet_inbound_rx, + &recipient_inbound_tx, + 1, + ) + .await; + poll_fn(|cx| Pin::new(&mut recipient_connection).as_mut().poll(cx)).now_or_never(); + assert_eq!(recipient_connection.message_nonce.load(Ordering::SeqCst), 2); + + // poll recipient's poll_inbound to receive the substream + let maybe_recipient_substream = poll_fn(|cx| { + Pin::new(&mut recipient_connection) + .as_mut() + .poll_inbound(cx) + }) + .now_or_never(); + let mut recipient_substream = maybe_recipient_substream.unwrap().unwrap(); + + // poll sender's connection to receive the OpenResponse and send it to the Connection inbound channel + inbound_receive_and_send( + connection_id.clone(), + &mut sender_mixnet_inbound_rx, + &sender_inbound_tx, + 1, + ) + .await; + + // poll sender's poll_outbound to get the substream + poll_fn(|cx| Pin::new(&mut sender_connection).as_mut().poll(cx)).now_or_never(); + assert!(sender_connection.pending_substreams.is_empty()); + + // finally, write message to the substream + let data = b"hello world"; + sender_substream.write_all(data).await.unwrap(); + assert_eq!(sender_connection.message_nonce.load(Ordering::SeqCst), 3); + + // receive message from the mixnet, push to the recipient Connection inbound channel + inbound_receive_and_send( + connection_id.clone(), + &mut recipient_mixnet_inbound_rx, + &recipient_inbound_tx, + 2, + ) + .await; + + // poll the sender's connection to send the msg from the connection inbound channel to the substream's + poll_fn(|cx| Pin::new(&mut sender_connection).as_mut().poll(cx)).now_or_never(); + + // poll the recipient's connection to read the msg from the mixnet and mux it into the substream + poll_fn(|cx| Pin::new(&mut recipient_connection).as_mut().poll(cx)).now_or_never(); + + let mut buf = [0u8; 11]; + let n = recipient_substream.read(&mut buf).await.unwrap(); + assert_eq!(n, 11); + assert_eq!(buf, data[..]); + + // test closing the stream; assert the stream is closed on both sides + sender_substream.close().await.unwrap(); + assert_eq!(sender_connection.message_nonce.load(Ordering::SeqCst), 4); + inbound_receive_and_send( + connection_id.clone(), + &mut recipient_mixnet_inbound_rx, + &recipient_inbound_tx, + 3, + ) + .await; + } +} diff --git a/sdk/rust/nym-sdk/examples/libp2p_shared/error.rs b/sdk/rust/nym-sdk/examples/libp2p_shared/error.rs new file mode 100644 index 0000000000..bf1ebc8175 --- /dev/null +++ b/sdk/rust/nym-sdk/examples/libp2p_shared/error.rs @@ -0,0 +1,64 @@ +use libp2p::core::multiaddr; +use nym_sphinx::addressing::clients::RecipientFormattingError; + +use super::message::SubstreamId; + +#[derive(Debug, thiserror::Error)] +pub enum Error { + #[error("unimplemented")] + Unimplemented, + #[error("failed to format multiaddress from nym address")] + FailedToFormatMultiaddr(#[from] multiaddr::Error), + #[error("unexpected protocol in multiaddress")] + InvalidProtocolForMultiaddr, + #[error("failed to decode message")] + InvalidMessageBytes, + #[error("no connection found for ConnectionResponse")] + NoConnectionForResponse, + #[error("received ConnectionResponse but connection was already established")] + ConnectionAlreadyEstablished, + #[error("received None recipient in ConnectionRequest")] + NoneRecipientInConnectionRequest, + #[error("cannot handle connection request; already have connection with given ID")] + ConnectionIDExists, + #[error("no connection found for TransportMessage")] + NoConnectionForTransportMessage, + #[error("failed to decode ConnectionMessage; too short")] + ConnectionMessageBytesTooShort, + #[error("failed to decode ConnectionMessage; no recipient")] + ConnectionMessageBytesNoRecipient, + #[error("failed to decode ConnectionMessage; no peer ID")] + ConnectionMessageBytesNoPeerId, + #[error("invalid peer ID bytes")] + InvalidPeerIdBytes, + #[error("invalid recipient bytes")] + InvalidRecipientBytes(#[from] RecipientFormattingError), + #[error("invalid recipient prefix byte")] + InvalidRecipientPrefixByte, + #[error("failed to decode TransportMessage; too short")] + TransportMessageBytesTooShort, + #[error("failed to decode TransportMessage; invalid nonce")] + InvalidNonce, + #[error("invalid substream ID")] + InvalidSubstreamMessageBytes, + #[error("invalid substream message type byte")] + InvalidSubstreamMessageType, + #[error("substrean with given ID already exists")] + SubstreamIdExists(SubstreamId), + #[error("no substream found for given ID")] + SubstreamIdDoesNotExist(SubstreamId), + #[error("recv error: channel closed")] + OneshotRecvError(#[from] tokio::sync::oneshot::error::RecvError), + #[error("recv error: channel closed")] + RecvError, + #[error("outbound send error")] + OutboundSendError(String), + #[error("inbound send error")] + InboundSendError(String), + #[error("failed to send new connection; receiver dropped")] + ConnectionSendError, + #[error("failed to send initial TransportEvent::NewAddress")] + SendErrorTransportEvent, + #[error("dial timed out")] + DialTimeout(#[from] tokio::time::error::Elapsed), +} diff --git a/sdk/rust/nym-sdk/examples/libp2p_shared/lib.rs b/sdk/rust/nym-sdk/examples/libp2p_shared/lib.rs new file mode 100644 index 0000000000..ba33f1d91f --- /dev/null +++ b/sdk/rust/nym-sdk/examples/libp2p_shared/lib.rs @@ -0,0 +1,10 @@ +pub(crate) mod connection; +pub mod error; +pub(crate) mod message; +pub(crate) mod mixnet; +pub(crate) mod queue; +pub mod substream; +pub mod transport; + +/// The deafult timeout secs for [`transport::Upgrade`] future. +const DEFAULT_HANDSHAKE_TIMEOUT_SECS: u64 = 5; diff --git a/sdk/rust/nym-sdk/examples/libp2p_shared/message.rs b/sdk/rust/nym-sdk/examples/libp2p_shared/message.rs new file mode 100644 index 0000000000..9dc4c7085b --- /dev/null +++ b/sdk/rust/nym-sdk/examples/libp2p_shared/message.rs @@ -0,0 +1,335 @@ +use libp2p::core::PeerId; +use nym_sphinx::addressing::clients::Recipient; +use rand::rngs::OsRng; +use rand::RngCore; +use std::fmt::{Debug, Formatter}; + +use super::error::Error; + +const RECIPIENT_LENGTH: usize = Recipient::LEN; +const CONNECTION_ID_LENGTH: usize = 32; +const SUBSTREAM_ID_LENGTH: usize = 32; + +const NONCE_BYTES_LEN: usize = 8; // length of u64 +const MIN_CONNECTION_MESSAGE_LEN: usize = CONNECTION_ID_LENGTH + NONCE_BYTES_LEN; + +/// ConnectionId is a unique, randomly-generated per-connection ID that's used to +/// identify which connection a message belongs to. +#[derive(Clone, Default, Eq, Hash, PartialEq)] +pub(crate) struct ConnectionId([u8; 32]); + +impl ConnectionId { + pub(crate) fn generate() -> Self { + let mut bytes = [0u8; 32]; + OsRng.fill_bytes(&mut bytes); + ConnectionId(bytes) + } + + fn from_bytes(bytes: &[u8]) -> Self { + let mut id = [0u8; 32]; + id[..].copy_from_slice(&bytes[0..CONNECTION_ID_LENGTH]); + ConnectionId(id) + } +} + +impl Debug for ConnectionId { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", hex::encode(self.0)) + } +} + +/// SubstreamId is a unique, randomly-generated per-substream ID that's used to +/// identify which substream a message belongs to. +#[derive(Clone, Default, Eq, Hash, PartialEq)] +pub struct SubstreamId(pub(crate) [u8; 32]); + +impl SubstreamId { + pub(crate) fn generate() -> Self { + let mut bytes = [0u8; 32]; + OsRng.fill_bytes(&mut bytes); + SubstreamId(bytes) + } + + fn from_bytes(bytes: &[u8]) -> Self { + let mut id = [0u8; 32]; + id[..].copy_from_slice(&bytes[0..SUBSTREAM_ID_LENGTH]); + SubstreamId(id) + } +} + +impl Debug for SubstreamId { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", hex::encode(self.0)) + } +} + +#[derive(Debug)] +#[allow(clippy::enum_variant_names)] +pub(crate) enum Message { + ConnectionRequest(ConnectionMessage), + ConnectionResponse(ConnectionMessage), + TransportMessage(TransportMessage), +} + +/// ConnectionMessage is exchanged to open a new connection. +#[derive(Debug)] +pub(crate) struct ConnectionMessage { + pub(crate) peer_id: PeerId, + pub(crate) id: ConnectionId, + /// recipient is the sender's Nym address. + /// only required if this is a ConnectionRequest. + pub(crate) recipient: Option, +} + +/// TransportMessage is sent over a connection after establishment. +#[derive(Debug, Clone)] +pub(crate) struct TransportMessage { + /// increments by 1 for every TransportMessage sent over a connection. + /// required for ordering, since Nym does not guarantee ordering. + /// ConnectionMessages do not need nonces, as we know that they will + /// be the first messages sent over a connection. + /// the first TransportMessage sent over a connection will have nonce 1. + pub(crate) nonce: u64, + pub(crate) message: SubstreamMessage, + pub(crate) id: ConnectionId, +} + +impl Message { + fn try_from_bytes(bytes: Vec) -> Result { + if bytes.len() < 2 { + return Err(Error::InvalidMessageBytes); + } + + Ok(match bytes[0] { + 0 => Message::ConnectionRequest(ConnectionMessage::try_from_bytes(&bytes[1..])?), + 1 => Message::ConnectionResponse(ConnectionMessage::try_from_bytes(&bytes[1..])?), + 2 => Message::TransportMessage(TransportMessage::try_from_bytes(&bytes[1..])?), + _ => return Err(Error::InvalidMessageBytes), + }) + } +} + +impl ConnectionMessage { + fn to_bytes(&self) -> Vec { + let mut bytes = self.id.0.to_vec(); + match self.recipient { + Some(recipient) => { + bytes.push(1u8); + bytes.append(&mut recipient.to_bytes().to_vec()); + } + None => bytes.push(0u8), + } + bytes.append(&mut self.peer_id.to_bytes()); + bytes + } + + fn try_from_bytes(bytes: &[u8]) -> Result { + if bytes.len() < CONNECTION_ID_LENGTH + 1 { + return Err(Error::ConnectionMessageBytesTooShort); + } + + let id = ConnectionId::from_bytes(&bytes[0..CONNECTION_ID_LENGTH]); + let recipient = match bytes[CONNECTION_ID_LENGTH] { + 0u8 => None, + 1u8 => { + if bytes.len() < CONNECTION_ID_LENGTH + 1 + RECIPIENT_LENGTH { + return Err(Error::ConnectionMessageBytesNoRecipient); + } + + let mut recipient_bytes = [0u8; RECIPIENT_LENGTH]; + recipient_bytes[..].copy_from_slice( + &bytes[CONNECTION_ID_LENGTH + 1..CONNECTION_ID_LENGTH + 1 + RECIPIENT_LENGTH], + ); + Some( + Recipient::try_from_bytes(recipient_bytes) + .map_err(Error::InvalidRecipientBytes)?, + ) + } + _ => { + return Err(Error::InvalidRecipientPrefixByte); + } + }; + let peer_id = match recipient { + Some(_) => { + if bytes.len() < CONNECTION_ID_LENGTH + RECIPIENT_LENGTH + 2 { + return Err(Error::ConnectionMessageBytesNoPeerId); + } + PeerId::from_bytes(&bytes[CONNECTION_ID_LENGTH + 1 + RECIPIENT_LENGTH..]) + .map_err(|_| Error::InvalidPeerIdBytes)? + } + None => { + if bytes.len() < CONNECTION_ID_LENGTH + 2 { + return Err(Error::ConnectionMessageBytesNoPeerId); + } + PeerId::from_bytes(&bytes[CONNECTION_ID_LENGTH + 1..]) + .map_err(|_| Error::InvalidPeerIdBytes)? + } + }; + Ok(ConnectionMessage { + peer_id, + recipient, + id, + }) + } +} + +impl TransportMessage { + fn to_bytes(&self) -> Vec { + let mut bytes = self.nonce.to_be_bytes().to_vec(); + bytes.extend_from_slice(self.id.0.as_ref()); + bytes.extend_from_slice(&self.message.to_bytes()); + bytes + } + + fn try_from_bytes(bytes: &[u8]) -> Result { + if bytes.len() < MIN_CONNECTION_MESSAGE_LEN + 1 { + return Err(Error::TransportMessageBytesTooShort); + } + + let nonce = u64::from_be_bytes( + bytes[0..NONCE_BYTES_LEN] + .to_vec() + .try_into() + .map_err(|_| Error::InvalidNonce)?, + ); + let id = ConnectionId::from_bytes(&bytes[NONCE_BYTES_LEN..MIN_CONNECTION_MESSAGE_LEN]); + let message = SubstreamMessage::try_from_bytes(&bytes[MIN_CONNECTION_MESSAGE_LEN..])?; + Ok(TransportMessage { nonce, message, id }) + } +} + +impl Ord for TransportMessage { + fn cmp(&self, other: &Self) -> std::cmp::Ordering { + self.nonce.cmp(&other.nonce) + } +} + +impl std::cmp::PartialOrd for TransportMessage { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl std::cmp::Eq for TransportMessage {} + +impl std::cmp::PartialEq for TransportMessage { + fn eq(&self, other: &Self) -> bool { + self.nonce == other.nonce + } +} + +#[derive(Debug, Clone)] +pub(crate) enum SubstreamMessageType { + OpenRequest, + OpenResponse, + Close, + Data(Vec), +} + +impl SubstreamMessageType { + fn to_u8(&self) -> u8 { + match self { + SubstreamMessageType::OpenRequest => 0, + SubstreamMessageType::OpenResponse => 1, + SubstreamMessageType::Close => 2, + SubstreamMessageType::Data(_) => 3, + } + } +} + +/// SubstreamMessage is a message sent over a substream. +#[derive(Debug, Clone)] +pub(crate) struct SubstreamMessage { + pub(crate) substream_id: SubstreamId, + pub(crate) message_type: SubstreamMessageType, +} + +impl SubstreamMessage { + pub(crate) fn new_with_data(substream_id: SubstreamId, message: Vec) -> Self { + SubstreamMessage { + substream_id, + message_type: SubstreamMessageType::Data(message), + } + } + + pub(crate) fn new_close(substream_id: SubstreamId) -> Self { + SubstreamMessage { + substream_id, + message_type: SubstreamMessageType::Close, + } + } + + pub(crate) fn to_bytes(&self) -> Vec { + let mut bytes = self.substream_id.0.clone().to_vec(); + bytes.push(self.message_type.to_u8()); + if let SubstreamMessageType::Data(message) = &self.message_type { + bytes.extend_from_slice(message); + } + bytes + } + + pub(crate) fn try_from_bytes(bytes: &[u8]) -> Result { + if bytes.len() < SUBSTREAM_ID_LENGTH + 1 { + return Err(Error::InvalidSubstreamMessageBytes); + } + + let substream_id = SubstreamId::from_bytes(&bytes[0..SUBSTREAM_ID_LENGTH]); + let message_type = match bytes[SUBSTREAM_ID_LENGTH] { + 0 => SubstreamMessageType::OpenRequest, + 1 => SubstreamMessageType::OpenResponse, + 2 => SubstreamMessageType::Close, + 3 => { + if bytes.len() < SUBSTREAM_ID_LENGTH + 2 { + return Err(Error::InvalidSubstreamMessageBytes); + } + SubstreamMessageType::Data(bytes[SUBSTREAM_ID_LENGTH + 1..].to_vec()) + } + _ => return Err(Error::InvalidSubstreamMessageType), + }; + + Ok(SubstreamMessage { + substream_id, + message_type, + }) + } +} + +impl Message { + pub(crate) fn to_bytes(&self) -> Vec { + match self { + Message::ConnectionRequest(msg) => { + let mut bytes = 0_u8.to_be_bytes().to_vec(); + bytes.append(&mut msg.to_bytes()); + bytes + } + Message::ConnectionResponse(msg) => { + let mut bytes = 1_u8.to_be_bytes().to_vec(); + bytes.append(&mut msg.to_bytes()); + bytes + } + Message::TransportMessage(msg) => { + let mut bytes = 2_u8.to_be_bytes().to_vec(); + bytes.append(&mut msg.to_bytes()); + bytes + } + } + } +} + +/// InboundMessage represents an inbound mixnet message. +pub(crate) struct InboundMessage(pub(crate) Message); + +/// OutboundMessage represents an outbound mixnet message. +#[derive(Debug)] +pub(crate) struct OutboundMessage { + pub(crate) message: Message, + pub(crate) recipient: Recipient, +} + +pub(crate) fn parse_message_data(data: &[u8]) -> Result { + if data.len() < 2 { + return Err(Error::InvalidMessageBytes); + } + let msg = Message::try_from_bytes(data.to_vec())?; + Ok(InboundMessage(msg)) +} diff --git a/sdk/rust/nym-sdk/examples/libp2p_shared/mixnet.rs b/sdk/rust/nym-sdk/examples/libp2p_shared/mixnet.rs new file mode 100644 index 0000000000..f75048203b --- /dev/null +++ b/sdk/rust/nym-sdk/examples/libp2p_shared/mixnet.rs @@ -0,0 +1,164 @@ +use futures::{pin_mut, select}; +use futures::{FutureExt, StreamExt}; +use log::debug; +use nym_sdk::mixnet::{IncludedSurbs, MixnetClient, MixnetClientSender, MixnetMessageSender}; +use nym_sphinx::addressing::clients::Recipient; +use nym_sphinx::receiver::ReconstructedMessage; +use tokio::sync::mpsc::{unbounded_channel, UnboundedReceiver, UnboundedSender}; + +use super::error::Error; +use super::message::*; + +/// initialize_mixnet initializes a read/write connection to a Nym websockets endpoint. +/// It starts a task that listens for inbound messages from the endpoint and writes outbound messages to the endpoint. +pub(crate) async fn initialize_mixnet( + client: MixnetClient, + notify_inbound_tx: Option>, +) -> Result< + ( + Recipient, + UnboundedReceiver, + UnboundedSender, + ), + Error, +> { + let recipient = *client.nym_address(); + + // a channel of inbound messages from the mixnet.. + // the transport reads from (listens) to the inbound_rx. + // TODO: this is probably a DOS vector; we should limit the size of the channel. + let (inbound_tx, inbound_rx) = unbounded_channel::(); + + // a channel of outbound messages to be written to the mixnet. + // the transport writes to outbound_tx. + let (outbound_tx, mut outbound_rx) = unbounded_channel::(); + + let sink = client.split_sender(); + let mut stream = client; + + tokio::task::spawn(async move { + loop { + let t1 = check_inbound(&mut stream, &inbound_tx, ¬ify_inbound_tx).fuse(); + let t2 = check_outbound(&sink, &mut outbound_rx).fuse(); + + pin_mut!(t1, t2); + + select! { + _ = t1 => {}, + _ = t2 => {}, + }; + } + }); + + Ok((recipient, inbound_rx, outbound_tx)) +} + +async fn check_inbound( + client: &mut MixnetClient, + inbound_tx: &UnboundedSender, + notify_inbound_tx: &Option>, +) -> Result<(), Error> { + if let Some(msg) = client.next().await { + if let Some(notify_tx) = notify_inbound_tx { + notify_tx + .send(()) + .map_err(|e| Error::InboundSendError(e.to_string()))?; + } + + handle_inbound(msg, inbound_tx).await?; + } + + Err(Error::Unimplemented) +} + +async fn handle_inbound( + msg: ReconstructedMessage, + inbound_tx: &UnboundedSender, +) -> Result<(), Error> { + let data = parse_message_data(&msg.message)?; + inbound_tx + .send(data) + .map_err(|e| Error::InboundSendError(e.to_string()))?; + Ok(()) +} + +async fn check_outbound( + mixnet_sender: &MixnetClientSender, + outbound_rx: &mut UnboundedReceiver, +) -> Result<(), Error> { + match outbound_rx.recv().await { + Some(message) => { + write_bytes( + mixnet_sender, + message.recipient, + &message.message.to_bytes(), + ) + .await + } + None => Err(Error::RecvError), + } +} + +async fn write_bytes( + mixnet_sender: &MixnetClientSender, + recipient: Recipient, + message: &[u8], +) -> Result<(), Error> { + if let Err(_err) = mixnet_sender + .send_message(recipient, message, IncludedSurbs::ExposeSelfAddress) + .await + { + return Err(Error::Unimplemented); + } + + debug!( + "wrote message to mixnet: recipient: {:?}", + recipient.to_string() + ); + Ok(()) +} + +#[cfg(test)] +mod test { + use super::super::message::{ + self, ConnectionId, Message, SubstreamId, SubstreamMessage, SubstreamMessageType, + TransportMessage, + }; + use super::super::mixnet::initialize_mixnet; + use nym_sdk::mixnet::MixnetClient; + + #[tokio::test] + async fn test_mixnet_poll_inbound_and_outbound() { + let client = MixnetClient::connect_new().await.unwrap(); + let (self_address, mut inbound_rx, outbound_tx) = + initialize_mixnet(client, None).await.unwrap(); + let msg_inner = "hello".as_bytes(); + let substream_id = SubstreamId::generate(); + let msg = Message::TransportMessage(TransportMessage { + nonce: 1, // arbitrary + id: ConnectionId::generate(), + message: SubstreamMessage::new_with_data(substream_id.clone(), msg_inner.to_vec()), + }); + + // send a message to ourselves through the mixnet + let out_msg = message::OutboundMessage { + message: msg, + recipient: self_address, + }; + + outbound_tx.send(out_msg).unwrap(); + + // receive the message from ourselves over the mixnet + let received_msg = inbound_rx.recv().await.unwrap(); + if let Message::TransportMessage(recv_msg) = received_msg.0 { + assert_eq!(substream_id, recv_msg.message.substream_id); + if let SubstreamMessageType::Data(data) = recv_msg.message.message_type { + assert_eq!(msg_inner, data.as_slice()); + } else { + panic!("expected SubstreamMessage::Data") + } + } else { + panic!("expected Message::TransportMessage") + } + } +} diff --git a/sdk/rust/nym-sdk/examples/libp2p_shared/queue.rs b/sdk/rust/nym-sdk/examples/libp2p_shared/queue.rs new file mode 100644 index 0000000000..2b59c82877 --- /dev/null +++ b/sdk/rust/nym-sdk/examples/libp2p_shared/queue.rs @@ -0,0 +1,140 @@ +use log::{debug, warn}; +use std::collections::BTreeSet; + +use super::message::TransportMessage; + +/// MessageQueue is a queue of messages, ordered by nonce, that we've +/// received but are not yet able to process because we're waiting for +/// a message with the next expected nonce first. +/// This is required because Nym does not guarantee any sort of message +/// ordering, only delivery. +/// TODO: is there a DOS vector here where a malicious peer sends us +/// messages only with nonce higher than the next expected nonce? +pub(crate) struct MessageQueue { + /// nonce of the next message we expect to receive on the + /// connection. + /// any messages with a nonce greater than this are pushed into + /// the queue. + /// if we get a message with a nonce equal to this, then we + /// immediately handle it in the transport and increment the nonce. + next_expected_nonce: u64, + + /// the actual queue of messages, ordered by nonce. + /// the head of the queue's nonce is always greater + /// than the next expected nonce. + queue: BTreeSet, +} + +impl MessageQueue { + pub(crate) fn new() -> Self { + MessageQueue { + next_expected_nonce: 0, + queue: BTreeSet::new(), + } + } + + pub(crate) fn print_nonces(&self) { + let nonces = self.queue.iter().map(|msg| msg.nonce).collect::>(); + debug!("MessageQueue: {:?}", nonces); + } + + /// sets the next expected nonce to 1, indicating that we've received + /// a ConnectionRequest or ConnectionResponse. + pub(crate) fn set_connection_message_received(&mut self) { + if self.next_expected_nonce != 0 { + panic!("connection message received twice"); + } + + self.next_expected_nonce = self.next_expected_nonce.wrapping_add(1); + } + + /// tries to push a message into the queue. + /// if the message has the next expected nonce, then the message is returned, + /// and should be processed by the caller. + /// in that case, the internal queue's next expected nonce is incremented. + pub(crate) fn try_push(&mut self, msg: TransportMessage) -> Option { + if msg.nonce == self.next_expected_nonce { + self.next_expected_nonce = self.next_expected_nonce.wrapping_add(1); + Some(msg) + } else { + if msg.nonce < self.next_expected_nonce { + // this shouldn't happen normally, only if the other node + // is not following the protocol + warn!("received a message with a nonce that is too low"); + return None; + } + + if !self.queue.insert(msg) { + // this shouldn't happen normally, only if the other node + // is not following the protocol + warn!("received a message with a duplicate nonce"); + return None; + } + + None + } + } + + pub(crate) fn pop(&mut self) -> Option { + let Some(head) = self.queue.first() else { + return None; + }; + + if head.nonce == self.next_expected_nonce { + self.next_expected_nonce = self.next_expected_nonce.wrapping_add(1); + Some(self.queue.pop_first().unwrap()) + } else { + None + } + } +} + +#[cfg(test)] +mod test { + use super::super::message::{ConnectionId, SubstreamId, SubstreamMessage}; + + use super::*; + + impl TransportMessage { + fn new(nonce: u64, message: SubstreamMessage, id: ConnectionId) -> Self { + TransportMessage { nonce, message, id } + } + } + + #[test] + fn test_message_queue() { + let mut queue = MessageQueue::new(); + + let test_substream_message = + SubstreamMessage::new_with_data(SubstreamId::generate(), vec![1, 2, 3]); + let connection_id = ConnectionId::generate(); + + let msg1 = TransportMessage::new(1, test_substream_message.clone(), connection_id.clone()); + let msg2 = TransportMessage::new(2, test_substream_message.clone(), connection_id.clone()); + let msg3 = TransportMessage::new(3, test_substream_message.clone(), connection_id.clone()); + + assert_eq!(queue.try_push(msg1.clone()), None); + assert_eq!(queue.try_push(msg3.clone()), None); + assert_eq!(queue.try_push(msg2.clone()), None); + + assert_eq!(queue.pop(), None); + + // set expected nonce to 1 + queue.set_connection_message_received(); + assert_eq!(queue.pop(), Some(msg1)); + + let msg4 = TransportMessage::new(4, test_substream_message.clone(), connection_id.clone()); + assert_eq!(queue.try_push(msg4.clone()), None); + + assert_eq!(queue.pop(), Some(msg2)); + assert_eq!(queue.pop(), Some(msg3)); + assert_eq!(queue.pop(), Some(msg4)); + assert_eq!(queue.pop(), None); + assert_eq!(queue.next_expected_nonce, 5); + + // should just return the message and increment nonce when message nonce = next expected nonce + let msg5 = TransportMessage::new(5, test_substream_message, connection_id); + assert_eq!(queue.try_push(msg5.clone()), Some(msg5)); + assert_eq!(queue.next_expected_nonce, 6); + } +} diff --git a/sdk/rust/nym-sdk/examples/libp2p_shared/substream.rs b/sdk/rust/nym-sdk/examples/libp2p_shared/substream.rs new file mode 100644 index 0000000000..f7620a835c --- /dev/null +++ b/sdk/rust/nym-sdk/examples/libp2p_shared/substream.rs @@ -0,0 +1,422 @@ +use super::message::{ + ConnectionId, Message, OutboundMessage, SubstreamId, SubstreamMessage, TransportMessage, +}; +use futures::{ + io::{Error as IoError, ErrorKind}, + AsyncRead, AsyncWrite, +}; +use log::debug; +use nym_sphinx::addressing::clients::Recipient; +use parking_lot::Mutex; +use std::{ + pin::Pin, + sync::{ + atomic::{AtomicU64, Ordering}, + Arc, + }, + task::{Context, Poll}, +}; +use tokio::sync::{ + mpsc::{UnboundedReceiver, UnboundedSender}, + oneshot::Receiver, +}; + +#[derive(Debug)] +pub struct Substream { + remote_recipient: Recipient, + connection_id: ConnectionId, + pub(crate) substream_id: SubstreamId, + + /// inbound messages; inbound_tx is in the corresponding Connection + pub(crate) inbound_rx: UnboundedReceiver>, + + /// outbound messages; go directly to the mixnet + outbound_tx: UnboundedSender, + + /// used to signal when the substream is closed + close_rx: Receiver<()>, + closed: Mutex, + + // buffer of data that's been written to the stream, + // but not yet read by the application. + unread_data: Mutex>, + + message_nonce: Arc, +} + +impl Substream { + pub(crate) fn new( + remote_recipient: Recipient, + connection_id: ConnectionId, + substream_id: SubstreamId, + inbound_rx: UnboundedReceiver>, + outbound_tx: UnboundedSender, + close_rx: Receiver<()>, + message_nonce: Arc, + ) -> Self { + Substream { + remote_recipient, + connection_id, + substream_id, + inbound_rx, + outbound_tx, + close_rx, + closed: Mutex::new(false), + unread_data: Mutex::new(vec![]), + message_nonce, + } + } + + fn check_closed(mut self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Result<(), IoError> { + let closed_err = IoError::new(ErrorKind::Other, "stream closed"); + + // close_rx will return an error if the channel is closed (ie. sender was dropped), + // or if it's empty + let received_closed = self.close_rx.try_recv(); + + let mut closed = self.closed.lock(); + if *closed { + return Err(closed_err); + } + + if received_closed.is_ok() { + *closed = true; + return Err(closed_err); + } + + Ok(()) + } +} + +impl AsyncRead for Substream { + fn poll_read( + mut self: Pin<&mut Self>, + cx: &mut Context<'_>, + buf: &mut [u8], + ) -> Poll> { + let closed_result = self.as_mut().check_closed(cx); + if let Err(e) = closed_result { + return Poll::Ready(Err(e)); + } + + let inbound_rx_data = self.inbound_rx.poll_recv(cx); + + // first, write any previously unread data to the buf + let mut unread_data = self.unread_data.lock(); + let filled_len = if unread_data.len() > 0 { + let unread_len = unread_data.len(); + let buf_len = buf.len(); + let copy_len = std::cmp::min(unread_len, buf_len); + buf[..copy_len].copy_from_slice(&unread_data[..copy_len]); + *unread_data = unread_data[copy_len..].to_vec(); + copy_len + } else { + 0 + }; + + if let Poll::Ready(Some(data)) = inbound_rx_data { + if filled_len == buf.len() { + // we've filled the buffer, so we'll have to save the rest for later + let mut new = vec![]; + new.extend(unread_data.drain(..)); + new.extend(data.iter()); + *unread_data = new; + return Poll::Ready(Ok(filled_len)); + } + + // otherwise, there's still room in the buffer, so we'll copy the rest of the data + let remaining_len = buf.len() - filled_len; + let data_len = data.len(); + + // we have more data than buffer room remaining, save the extra for later + if remaining_len < data_len { + unread_data.extend_from_slice(&data[remaining_len..]); + } + + let copied = std::cmp::min(remaining_len, data_len); + buf[filled_len..filled_len + copied].copy_from_slice(&data[..copied]); + debug!("poll_read copied {} bytes: data {:?}", copied, buf); + return Poll::Ready(Ok(copied)); + } + + if filled_len > 0 { + debug!("poll_read copied {} bytes: data {:?}", filled_len, buf); + return Poll::Ready(Ok(filled_len)); + } + + Poll::Pending + } +} + +impl AsyncWrite for Substream { + fn poll_write( + mut self: Pin<&mut Self>, + cx: &mut Context<'_>, + buf: &[u8], + ) -> Poll> { + if let Err(e) = self.as_mut().check_closed(cx) { + return Poll::Ready(Err(e)); + } + + let nonce = self.message_nonce.fetch_add(1, Ordering::SeqCst); + + self.outbound_tx + .send(OutboundMessage { + recipient: self.remote_recipient, + message: Message::TransportMessage(TransportMessage { + nonce, + id: self.connection_id.clone(), + message: SubstreamMessage::new_with_data( + self.substream_id.clone(), + buf.to_vec(), + ), + }), + }) + .map_err(|e| { + IoError::new( + ErrorKind::Other, + format!("poll_write outbound_tx error: {}", e), + ) + })?; + + Poll::Ready(Ok(buf.len())) + } + + fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + if let Err(e) = self.check_closed(cx) { + return Poll::Ready(Err(e)); + } + + Poll::Ready(Ok(())) + } + + fn poll_close(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll> { + let nonce = self.message_nonce.fetch_add(1, Ordering::SeqCst); + + let mut closed = self.closed.lock(); + if *closed { + return Poll::Ready(Err(IoError::new(ErrorKind::Other, "stream closed"))); + } + + *closed = true; + + // send a close message to the mixnet + self.outbound_tx + .send(OutboundMessage { + recipient: self.remote_recipient, + message: Message::TransportMessage(TransportMessage { + nonce, + id: self.connection_id.clone(), + message: SubstreamMessage::new_close(self.substream_id.clone()), + }), + }) + .map_err(|e| { + IoError::new( + ErrorKind::Other, + format!("poll_close outbound_rx error: {}", e), + ) + })?; + + Poll::Ready(Ok(())) + } +} + +#[cfg(test)] +mod test { + use super::super::message::{ + ConnectionId, Message, SubstreamId, SubstreamMessage, TransportMessage, + }; + use super::super::mixnet::initialize_mixnet; + use super::Substream; + use futures::{AsyncReadExt, AsyncWriteExt}; + use nym_sdk::mixnet::MixnetClient; + use nym_sphinx::addressing::clients::Recipient; + use std::sync::atomic::AtomicU64; + use std::sync::Arc; + + #[tokio::test] + async fn test_substream_poll_read_unread_data() { + let (outbound_tx, _) = tokio::sync::mpsc::unbounded_channel(); + let connection_id = ConnectionId::generate(); + let substream_id = SubstreamId::generate(); + + let (inbound_tx, inbound_rx) = tokio::sync::mpsc::unbounded_channel(); + let (_, close_rx) = tokio::sync::oneshot::channel(); + + let mut substream = Substream::new( + Recipient::try_from_base58_string("D1rrpsysCGCYXy9saP8y3kmNpGtJZUXN9SvFoUcqAsM9.9Ssso1ea5NfkbMASdiseDSjTN1fSWda5SgEVjdSN4CvV@GJqd3ZxpXWSNxTfx7B1pPtswpetH4LnJdFeLeuY5KUuN").unwrap(), + connection_id, + substream_id, + inbound_rx, + outbound_tx, + close_rx, + Arc::new(AtomicU64::new(1)), + ); + + // test writing and reading w/ same length data + let data = b"hello".to_vec(); + inbound_tx.send(data.clone()).unwrap(); + let mut buf = [0u8; 5]; + let read_len = substream.read(&mut buf).await.unwrap(); + assert_eq!(read_len, data.len()); + assert_eq!(buf.to_vec(), data); + + // test writing data longer than read buffer + let data = b"nootwashere".to_vec(); + inbound_tx.send(data.clone()).unwrap(); + + let mut buf = [0u8; 4]; + let read_len = substream.read(&mut buf).await.unwrap(); + assert_eq!(read_len, buf.len()); + assert_eq!(buf.to_vec(), b"noot".to_vec()); + + let mut buf = [0u8; 7]; + let read_len = substream.read(&mut buf).await.unwrap(); + assert_eq!(read_len, buf.len()); + assert_eq!(buf.to_vec(), b"washere".to_vec()); + + // test read buffer larger than written data + let data = b"nootwashere".to_vec(); + inbound_tx.send(data.clone()).unwrap(); + let mut buf = [0u8; 16]; + let read_len = substream.read(&mut buf).await.unwrap(); + assert_eq!(read_len, data.len()); + assert_eq!(buf[..data.len()], data); + assert_eq!(buf[data.len()..].to_vec(), vec![0u8; 16 - data.len()]); + + // test writing data longer than read buffer multiple times + let data = b"nootwashere".to_vec(); + inbound_tx.send(data.clone()).unwrap(); + + let mut buf = [0u8; 4]; + let read_len = substream.read(&mut buf).await.unwrap(); + assert_eq!(read_len, buf.len()); + assert_eq!(buf.to_vec(), b"noot".to_vec()); + + let data = b"asdf".to_vec(); + inbound_tx.send(data.clone()).unwrap(); + + let mut buf = [0u8; 4]; + let read_len = substream.read(&mut buf).await.unwrap(); + assert_eq!(read_len, buf.len()); + assert_eq!(buf.to_vec(), b"wash".to_vec()); + + let mut buf = [0u8; 8]; + let read_len = substream.read(&mut buf).await.unwrap(); + assert_eq!(read_len, 7); + assert_eq!(buf[..7], b"ereasdf".to_vec()); + } + + #[tokio::test] + async fn test_substream_read_write() { + let client = MixnetClient::connect_new().await.unwrap(); + let (self_address, mut mixnet_inbound_rx, outbound_tx) = + initialize_mixnet(client, None).await.unwrap(); + + const MSG_INNER: &[u8] = "hello".as_bytes(); + let connection_id = ConnectionId::generate(); + let substream_id = SubstreamId::generate(); + + let (inbound_tx, inbound_rx) = tokio::sync::mpsc::unbounded_channel(); + let (_, close_rx) = tokio::sync::oneshot::channel(); + + let mut substream = Substream::new( + self_address, + connection_id, + substream_id, + inbound_rx, + outbound_tx, + close_rx, + Arc::new(AtomicU64::new(1)), + ); + + // send message to ourselves over the mixnet + substream.write_all(MSG_INNER).await.unwrap(); + + // receive full message over the mixnet + let recv_msg = mixnet_inbound_rx.recv().await.unwrap(); + match recv_msg.0 { + Message::TransportMessage(TransportMessage { + nonce, + id: _, + message: + SubstreamMessage { + substream_id: _, + message_type: msg, + }, + }) => { + assert_eq!(nonce, 1); + match msg { + super::super::message::SubstreamMessageType::Data(data) => { + assert_eq!(data, MSG_INNER); + // send message to substream inbound channel + inbound_tx.send(data).unwrap(); + } + _ => panic!("unexpected message type"), + } + } + _ => panic!("unexpected message"), + } + + // read message from substream + let mut buf = [0u8; MSG_INNER.len()]; + substream.read_exact(&mut buf).await.unwrap(); + assert_eq!(buf, MSG_INNER); + + // close substream + substream.close().await.unwrap(); + + // try to read/write to closed substream; should error + substream.write_all(MSG_INNER).await.unwrap_err(); + substream.read_exact(&mut buf).await.unwrap_err(); + + // assert a close message was sent over the mixnet + let recv_msg = mixnet_inbound_rx.recv().await.unwrap(); + match recv_msg.0 { + Message::TransportMessage(TransportMessage { + nonce: _, + id: _, + message: + SubstreamMessage { + substream_id: _, + message_type: msg, + }, + }) => match msg { + super::super::message::SubstreamMessageType::Close => {} + _ => panic!("unexpected message type"), + }, + _ => panic!("unexpected message: {:?}", recv_msg.0), + } + } + + #[tokio::test] + async fn test_substream_recv_close() { + let client = MixnetClient::connect_new().await.unwrap(); + let (self_address, _, outbound_tx) = initialize_mixnet(client, None).await.unwrap(); + + const MSG_INNER: &[u8] = "hello".as_bytes(); + let connection_id = ConnectionId::generate(); + let substream_id = SubstreamId::generate(); + + let (_, inbound_rx) = tokio::sync::mpsc::unbounded_channel(); + let (close_tx, close_rx) = tokio::sync::oneshot::channel(); + + let mut substream = Substream::new( + self_address, + connection_id, + substream_id, + inbound_rx, + outbound_tx, + close_rx, + Arc::new(AtomicU64::new(1)), + ); + + // close substream + close_tx.send(()).unwrap(); + + // try to read/write to closed substream; should error + substream.write_all(MSG_INNER).await.unwrap_err(); + let mut buf = [0u8; MSG_INNER.len()]; + substream.read_exact(&mut buf).await.unwrap_err(); + } +} diff --git a/sdk/rust/nym-sdk/examples/libp2p_shared/transport.rs b/sdk/rust/nym-sdk/examples/libp2p_shared/transport.rs new file mode 100644 index 0000000000..88142cc37f --- /dev/null +++ b/sdk/rust/nym-sdk/examples/libp2p_shared/transport.rs @@ -0,0 +1,897 @@ +use futures::prelude::*; +use libp2p::core::{ + identity::Keypair, + multiaddr::{Multiaddr, Protocol}, + transport::{ListenerId, TransportError, TransportEvent}, + PeerId, Transport, +}; +use log::debug; +use nym_sdk::mixnet::MixnetClient; +use nym_sphinx::addressing::clients::Recipient; +use std::{ + collections::HashMap, + pin::Pin, + str::FromStr, + task::{Context, Poll, Waker}, +}; +use tokio::{ + sync::{ + mpsc::{unbounded_channel, UnboundedReceiver, UnboundedSender}, + oneshot, + }, + time::{timeout, Duration}, +}; +use tokio_stream::wrappers::UnboundedReceiverStream; + +use super::connection::{Connection, PendingConnection}; +use super::error::Error; +use super::message::{ + ConnectionId, ConnectionMessage, InboundMessage, Message, OutboundMessage, SubstreamMessage, + TransportMessage, +}; +use super::mixnet::initialize_mixnet; +use super::queue::MessageQueue; +use super::DEFAULT_HANDSHAKE_TIMEOUT_SECS; + +/// InboundTransportEvent represents an inbound event from the mixnet. +pub enum InboundTransportEvent { + ConnectionRequest(Upgrade), + ConnectionResponse, + TransportMessage, +} + +/// NymTransport implements the Transport trait using the Nym mixnet. +pub struct NymTransport { + /// our Nym address + self_address: Recipient, + pub(crate) listen_addr: Multiaddr, + pub(crate) listener_id: ListenerId, + + /// our libp2p keypair; currently not really used + keypair: Keypair, + + /// established connections -> channel which sends messages received from + /// the mixnet to the corresponding Connection + connections: HashMap>, + + /// outbound pending dials + pending_dials: HashMap, + + /// connection message queues + message_queues: HashMap, + + /// inbound mixnet messages + inbound_stream: UnboundedReceiverStream, + + /// outbound mixnet messages + outbound_tx: UnboundedSender, + + /// inbound messages for Transport.poll() + poll_rx: UnboundedReceiver>, + + /// outbound messages to Transport.poll() + poll_tx: UnboundedSender>, + + waker: Option, + + /// Timeout for the [`Upgrade`] future. + handshake_timeout: Duration, +} + +impl NymTransport { + /// New transport. + pub async fn new(client: MixnetClient, keypair: Keypair) -> Result { + Self::new_maybe_with_notify_inbound(client, keypair, None, None).await + } + + /// New transport with a timeout. + #[allow(dead_code)] + pub async fn new_with_timeout( + client: MixnetClient, + keypair: Keypair, + timeout: Duration, + ) -> Result { + Self::new_maybe_with_notify_inbound(client, keypair, None, Some(timeout)).await + } + + /// Add timeout to transport and return self. + #[allow(dead_code)] + pub fn with_timeout(mut self, timeout: Duration) -> Self { + self.handshake_timeout = timeout; + self + } + + async fn new_maybe_with_notify_inbound( + client: MixnetClient, + keypair: Keypair, + notify_inbound_tx: Option>, + timeout: Option, + ) -> Result { + let (self_address, inbound_rx, outbound_tx) = + initialize_mixnet(client, notify_inbound_tx).await?; + let listen_addr = nym_address_to_multiaddress(self_address)?; + let listener_id = ListenerId::new(); + + let (poll_tx, poll_rx) = unbounded_channel::>(); + + poll_tx + .send(TransportEvent::NewAddress { + listener_id, + listen_addr: listen_addr.clone(), + }) + .map_err(|_| Error::SendErrorTransportEvent)?; + + let inbound_stream = UnboundedReceiverStream::new(inbound_rx); + let handshake_timeout = + timeout.unwrap_or_else(|| Duration::from_secs(DEFAULT_HANDSHAKE_TIMEOUT_SECS)); + + Ok(Self { + self_address, + listen_addr, + listener_id, + keypair, + connections: HashMap::new(), + pending_dials: HashMap::new(), + message_queues: HashMap::new(), + inbound_stream, + outbound_tx, + poll_rx, + poll_tx, + waker: None, + handshake_timeout, + }) + } + + pub(crate) fn peer_id(&self) -> PeerId { + PeerId::from_public_key(&self.keypair.public()) + } + + fn handle_message_queue_on_connection_initiation( + &mut self, + id: &ConnectionId, + ) -> Result<(), Error> { + debug!("handle_message_queue_on_connection_initiation"); + let Some(inbound_tx) = self.connections.get(id) else { + // this should not happen + return Err(Error::NoConnectionForTransportMessage); + }; + + match self.message_queues.get_mut(id) { + Some(queue) => { + // update expected nonce + queue.set_connection_message_received(); + + // push pending inbound some messages in this case + while let Some(msg) = queue.pop() { + debug!( + "popped queued message with nonce {} for connection", + msg.nonce + ); + inbound_tx + .send(msg.message.clone()) + .map_err(|e| Error::InboundSendError(e.to_string()))?; + } + } + None => { + // no queue exists for this connection, create one + let queue = MessageQueue::new(); + self.message_queues.insert(id.clone(), queue); + let queue = self.message_queues.get_mut(id).unwrap(); + queue.set_connection_message_received(); + } + }; + + debug!("returning from handle_message_queue_on_connection_initiation"); + Ok(()) + } + + // handle_connection_response resolves the pending connection corresponding to the response + // (if there is one) into a Connection. + fn handle_connection_response(&mut self, msg: &ConnectionMessage) -> Result<(), Error> { + if self.connections.contains_key(&msg.id) { + return Err(Error::ConnectionAlreadyEstablished); + } + + if let Some(pending_conn) = self.pending_dials.remove(&msg.id) { + // resolve connection and put into pending_conn channel + let (conn, conn_tx) = self.create_connection_types( + msg.peer_id, + pending_conn.remote_recipient, + msg.id.clone(), + ); + + self.connections.insert(msg.id.clone(), conn_tx); + self.handle_message_queue_on_connection_initiation(&msg.id)?; + + pending_conn + .connection_tx + .send(conn) + .map_err(|_| Error::ConnectionSendError)?; + + if let Some(waker) = self.waker.take() { + waker.wake(); + } + + Ok(()) + } else { + Err(Error::NoConnectionForResponse) + } + } + + /// handle_connection_request handles an incoming connection request, sends back a + /// connection response, and finally completes the upgrade into a Connection. + fn handle_connection_request(&mut self, msg: &ConnectionMessage) -> Result { + if msg.recipient.is_none() { + return Err(Error::NoneRecipientInConnectionRequest); + } + + // ensure we don't already have a conn with the same id + if self.connections.get(&msg.id).is_some() { + return Err(Error::ConnectionIDExists); + } + + let (conn, conn_tx) = + self.create_connection_types(msg.peer_id, msg.recipient.unwrap(), msg.id.clone()); + self.connections.insert(msg.id.clone(), conn_tx); + self.handle_message_queue_on_connection_initiation(&msg.id)?; + + let resp = ConnectionMessage { + peer_id: self.peer_id(), + recipient: None, + id: msg.id.clone(), + }; + + self.outbound_tx + .send(OutboundMessage { + message: Message::ConnectionResponse(resp), + recipient: msg.recipient.unwrap(), + }) + .map_err(|e| Error::OutboundSendError(e.to_string()))?; + + if let Some(waker) = self.waker.take() { + waker.wake(); + } + + Ok(conn) + } + + fn handle_transport_message(&mut self, msg: TransportMessage) -> Result<(), Error> { + let queue = match self.message_queues.get_mut(&msg.id) { + Some(queue) => queue, + None => { + // no queue exists for this connection, create one + let queue = MessageQueue::new(); + self.message_queues.insert(msg.id.clone(), queue); + self.message_queues.get_mut(&msg.id).unwrap() + } + }; + + queue.print_nonces(); + + let nonce = msg.nonce; + let Some(msg) = queue.try_push(msg) else { + // don't push the message yet, it's been queued + debug!("message with nonce {} queued for connection", nonce); + return Ok(()); + }; + + let Some(inbound_tx) = self.connections.get(&msg.id) else { + return Err(Error::NoConnectionForTransportMessage); + }; + + // send original message + debug!( + "sending original message with nonce {} for connection", + nonce + ); + inbound_tx + .send(msg.message.clone()) + .map_err(|e| Error::InboundSendError(e.to_string()))?; + + // try to pop queued messages and send them on inbound channel + while let Some(msg) = queue.pop() { + debug!( + "popped queued message with nonce {} for connection", + msg.nonce + ); + inbound_tx + .send(msg.message.clone()) + .map_err(|e| Error::InboundSendError(e.to_string()))?; + } + + if let Some(waker) = self.waker.clone().take() { + waker.wake(); + } + + Ok(()) + } + + fn create_connection_types( + &self, + remote_peer_id: PeerId, + recipient: Recipient, + id: ConnectionId, + ) -> (Connection, UnboundedSender) { + let (inbound_tx, inbound_rx) = unbounded_channel::(); + + // representation of a connection; this contains channels for applications to read/write to. + let conn = Connection::new( + remote_peer_id, + recipient, + id, + inbound_rx, + self.outbound_tx.clone(), + ); + + // inbound_tx is what we write to when receiving messages on the mixnet, + (conn, inbound_tx) + } + + /// handle_inbound handles an inbound message from the mixnet, received via self.inbound_stream. + fn handle_inbound(&mut self, msg: Message) -> Result { + match msg { + Message::ConnectionRequest(inner) => { + debug!("got inbound connection request {:?}", inner); + match self.handle_connection_request(&inner) { + Ok(conn) => { + let (connection_tx, connection_rx) = + oneshot::channel::<(PeerId, Connection)>(); + let upgrade = Upgrade::new(connection_rx); + connection_tx + .send((inner.peer_id, conn)) + .map_err(|_| Error::ConnectionSendError)?; + Ok(InboundTransportEvent::ConnectionRequest(upgrade)) + } + Err(e) => Err(e), + } + } + Message::ConnectionResponse(msg) => { + debug!("got inbound connection response {:?}", msg); + self.handle_connection_response(&msg) + .map(|_| InboundTransportEvent::ConnectionResponse) + } + Message::TransportMessage(msg) => { + debug!("got inbound TransportMessage: {:?}", msg); + self.handle_transport_message(msg) + .map(|_| InboundTransportEvent::TransportMessage) + } + } + } +} + +/// Upgrade represents a transport listener upgrade. +/// Note: we immediately upgrade a connection request to a connection, +/// so this only contains a channel for receiving that connection. +pub struct Upgrade { + connection_tx: oneshot::Receiver<(PeerId, Connection)>, +} + +impl Upgrade { + fn new(connection_tx: oneshot::Receiver<(PeerId, Connection)>) -> Upgrade { + Upgrade { connection_tx } + } +} + +impl Future for Upgrade { + type Output = Result<(PeerId, Connection), Error>; + + // poll checks if the upgrade has turned into a connection yet + fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + self.connection_tx + .poll_unpin(cx) + .map_err(|_| Error::RecvError) + } +} + +impl Transport for NymTransport { + type Output = (PeerId, Connection); + type Error = Error; + type ListenerUpgrade = Upgrade; + type Dial = Pin> + Send>>; + + fn listen_on(&mut self, _: Multiaddr) -> Result> { + // we should only allow listening on the multiaddress containing our Nym address + Ok(self.listener_id) + } + + fn remove_listener(&mut self, id: ListenerId) -> bool { + if self.listener_id != id { + return false; + } + + // TODO: close channels? + self.poll_tx + .send(TransportEvent::ListenerClosed { + listener_id: id, + reason: Ok(()), + }) + .expect("failed to send listener closed event"); + true + } + + fn dial(&mut self, addr: Multiaddr) -> Result> { + debug!("dialing {}", addr); + + let id = ConnectionId::generate(); + + // create remote recipient address + let recipient = multiaddress_to_nym_address(addr).map_err(TransportError::Other)?; + + // create pending conn structs and store + let (connection_tx, connection_rx) = oneshot::channel::(); + + let inner_pending_conn = PendingConnection::new(recipient, connection_tx); + self.pending_dials.insert(id.clone(), inner_pending_conn); + + // put ConnectionRequest message into outbound message channel + let msg = ConnectionMessage { + peer_id: self.peer_id(), + recipient: Some(self.self_address), + id, + }; + + let outbound_tx = self.outbound_tx.clone(); + + let mut waker = self.waker.clone(); + let handshake_timeout = self.handshake_timeout; + Ok(async move { + outbound_tx + .send(OutboundMessage { + message: Message::ConnectionRequest(msg), + recipient, + }) + .map_err(|e| Error::OutboundSendError(e.to_string()))?; + + debug!("sent outbound ConnectionRequest"); + if let Some(waker) = waker.take() { + waker.wake(); + }; + + let conn = timeout(handshake_timeout, connection_rx).await??; + Ok((conn.peer_id, conn)) + } + .boxed()) + } + + // dial_as_listener currently just calls self.dial(). + fn dial_as_listener( + &mut self, + addr: Multiaddr, + ) -> Result> { + self.dial(addr) + } + + fn poll( + mut self: Pin<&mut Self>, + cx: &mut Context<'_>, + ) -> Poll> { + // new addresses + listener close events + if let Poll::Ready(Some(res)) = self.poll_rx.recv().boxed().poll_unpin(cx) { + return Poll::Ready(res); + } + + // check for and handle inbound messages + while let Poll::Ready(Some(msg)) = self.inbound_stream.poll_next_unpin(cx) { + match self.handle_inbound(msg.0) { + Ok(event) => match event { + InboundTransportEvent::ConnectionRequest(upgrade) => { + debug!("InboundTransportEvent::ConnectionRequest"); + return Poll::Ready(TransportEvent::Incoming { + listener_id: self.listener_id, + upgrade, + local_addr: self.listen_addr.clone(), + send_back_addr: self.listen_addr.clone(), + }); + } + InboundTransportEvent::ConnectionResponse => { + debug!("InboundTransportEvent::ConnectionResponse"); + } + InboundTransportEvent::TransportMessage => { + debug!("InboundTransportEvent::TransportMessage"); + } + }, + Err(e) => { + return Poll::Ready(TransportEvent::ListenerError { + listener_id: self.listener_id, + error: e, + }); + } + }; + } + + self.waker = Some(cx.waker().clone()); + Poll::Pending + } + + fn address_translation(&self, _listen: &Multiaddr, _observed: &Multiaddr) -> Option { + None + } +} + +fn nym_address_to_multiaddress(addr: Recipient) -> Result { + Multiaddr::from_str(&format!("/nym/{}", addr)).map_err(Error::FailedToFormatMultiaddr) +} + +fn multiaddress_to_nym_address(multiaddr: Multiaddr) -> Result { + let mut multiaddr = multiaddr; + match multiaddr.pop().unwrap() { + Protocol::Nym(addr) => Recipient::from_str(&addr).map_err(Error::InvalidRecipientBytes), + _ => Err(Error::InvalidProtocolForMultiaddr), + } +} + +#[cfg(test)] +mod test { + use super::super::connection::Connection; + use super::super::error::Error; + use super::super::message::{ + Message, OutboundMessage, SubstreamId, SubstreamMessage, SubstreamMessageType, + TransportMessage, + }; + use super::super::substream::Substream; + use super::{nym_address_to_multiaddress, NymTransport}; + use futures::{future::poll_fn, AsyncReadExt, AsyncWriteExt, FutureExt}; + use libp2p::core::{ + identity::Keypair, + transport::{Transport, TransportEvent}, + Multiaddr, StreamMuxer, + }; + use log::info; + use nym_bin_common::logging::setup_logging; + use nym_sdk::mixnet::MixnetClient; + use std::{pin::Pin, str::FromStr, sync::atomic::Ordering}; + use tokio::sync::mpsc::{unbounded_channel, UnboundedReceiver, UnboundedSender}; + + impl Connection { + fn write(&self, msg: SubstreamMessage) -> Result<(), Error> { + let nonce = self.message_nonce.fetch_add(1, Ordering::SeqCst); + self.mixnet_outbound_tx + .send(OutboundMessage { + recipient: self.remote_recipient, + message: Message::TransportMessage(TransportMessage { + nonce, + id: self.id.clone(), + message: msg, + }), + }) + .map_err(|e| Error::OutboundSendError(e.to_string()))?; + Ok(()) + } + } + + impl NymTransport { + async fn new_with_notify_inbound( + client: MixnetClient, + notify_inbound_tx: UnboundedSender<()>, + ) -> Result { + let local_key = Keypair::generate_ed25519(); + Self::new_maybe_with_notify_inbound(client, local_key, Some(notify_inbound_tx), None) + .await + } + } + + #[tokio::test] + async fn test_transport_connection() { + setup_logging(); + + let client = MixnetClient::connect_new().await.unwrap(); + let (dialer_notify_inbound_tx, mut dialer_notify_inbound_rx) = unbounded_channel(); + let mut dialer_transport = + NymTransport::new_with_notify_inbound(client, dialer_notify_inbound_tx) + .await + .unwrap(); + + let client2 = MixnetClient::connect_new().await.unwrap(); + let (listener_notify_inbound_tx, mut listener_notify_inbound_rx) = unbounded_channel(); + let mut listener_transport = + NymTransport::new_with_notify_inbound(client2, listener_notify_inbound_tx) + .await + .unwrap(); + let listener_multiaddr = + nym_address_to_multiaddress(listener_transport.self_address).unwrap(); + assert_new_address_event(Pin::new(&mut dialer_transport)).await; + assert_new_address_event(Pin::new(&mut listener_transport)).await; + + // dial the remote peer + let mut dial = dialer_transport.dial(listener_multiaddr).unwrap(); + + // poll the dial to send the connection request message + assert!(poll_fn(|cx| Pin::new(&mut dial).as_mut().poll_unpin(cx)) + .now_or_never() + .is_none()); + listener_notify_inbound_rx.recv().await.unwrap(); + + // should receive the connection request from the mixnet and send the connection response + let res = poll_fn(|cx| Pin::new(&mut listener_transport).as_mut().poll(cx)).await; + let mut upgrade = match res { + TransportEvent::Incoming { + listener_id, + upgrade, + local_addr, + send_back_addr, + } => { + assert_eq!(listener_id, listener_transport.listener_id); + assert_eq!(local_addr, listener_transport.listen_addr); + assert_eq!(send_back_addr, listener_transport.listen_addr); + upgrade + } + _ => panic!("expected TransportEvent::Incoming, got {:?}", res), + }; + dialer_notify_inbound_rx.recv().await.unwrap(); + + // should receive the connection response from the mixnet + assert!( + poll_fn(|cx| Pin::new(&mut dialer_transport).as_mut().poll(cx)) + .now_or_never() + .is_none() + ); + info!("waiting for connections..."); + + // should be able to resolve the connections now + let (_, mut listener_conn) = poll_fn(|cx| Pin::new(&mut upgrade).as_mut().poll_unpin(cx)) + .now_or_never() + .expect("the upgrade should be ready") + .expect("the upgrade should not error"); + let (_, mut dialer_conn) = poll_fn(|cx| Pin::new(&mut dial).as_mut().poll_unpin(cx)) + .now_or_never() + .expect("the upgrade should be ready") + .expect("the upgrade should not error"); + info!("connections established"); + + // write messages from the dialer to the listener and vice versa + send_and_receive_over_conns( + b"hello".to_vec(), + &mut dialer_conn, + &mut listener_conn, + Pin::new(&mut listener_transport), + &mut listener_notify_inbound_rx, + ) + .await; + send_and_receive_over_conns( + b"hi".to_vec(), + &mut dialer_conn, + &mut listener_conn, + Pin::new(&mut listener_transport), + &mut listener_notify_inbound_rx, + ) + .await; + send_and_receive_over_conns( + b"world".to_vec(), + &mut listener_conn, + &mut dialer_conn, + Pin::new(&mut dialer_transport), + &mut dialer_notify_inbound_rx, + ) + .await; + } + + async fn assert_new_address_event(mut transport: Pin<&mut NymTransport>) { + match poll_fn(|cx| transport.as_mut().poll(cx)).await { + TransportEvent::NewAddress { + listener_id, + listen_addr, + } => { + assert_eq!(listener_id, transport.listener_id); + assert_eq!(listen_addr, transport.listen_addr); + } + _ => panic!("expected TransportEvent::NewAddress"), + } + } + + async fn send_and_receive_over_conns( + msg: Vec, + conn1: &mut Connection, + conn2: &mut Connection, + mut transport2: Pin<&mut NymTransport>, + notify_inbound_rx: &mut UnboundedReceiver<()>, + ) { + // send message over conn1 to conn2 + let substream_id = SubstreamId::generate(); + conn1 + .write(SubstreamMessage::new_with_data( + substream_id.clone(), + msg.clone(), + )) + .unwrap(); + notify_inbound_rx.recv().await.unwrap(); + + // poll transport2 to push message from transport to connection + assert!(poll_fn(|cx| transport2.as_mut().poll(cx)) + .now_or_never() + .is_none()); + let substream_msg = conn2.inbound_rx.recv().await.unwrap(); + if let SubstreamMessageType::Data(data) = substream_msg.message_type { + assert_eq!(data, msg); + } else { + panic!("expected data message"); + } + } + + #[tokio::test] + async fn test_transport_substream() { + let client = MixnetClient::connect_new().await.unwrap(); + + let (dialer_notify_inbound_tx, mut dialer_notify_inbound_rx) = unbounded_channel(); + let mut dialer_transport = + NymTransport::new_with_notify_inbound(client, dialer_notify_inbound_tx) + .await + .unwrap(); + + let client2 = MixnetClient::connect_new().await.unwrap(); + + let (listener_notify_inbound_tx, mut listener_notify_inbound_rx) = unbounded_channel(); + let mut listener_transport = + NymTransport::new_with_notify_inbound(client2, listener_notify_inbound_tx) + .await + .unwrap(); + let listener_multiaddr = + nym_address_to_multiaddress(listener_transport.self_address).unwrap(); + assert_new_address_event(Pin::new(&mut dialer_transport)).await; + assert_new_address_event(Pin::new(&mut listener_transport)).await; + + // dial the remote peer + let mut dial = dialer_transport.dial(listener_multiaddr).unwrap(); + + // poll the dial to send the connection request message + assert!(poll_fn(|cx| Pin::new(&mut dial).as_mut().poll_unpin(cx)) + .now_or_never() + .is_none()); + listener_notify_inbound_rx.recv().await.unwrap(); + + // should receive the connection request from the mixnet and send the connection response + let res = poll_fn(|cx| Pin::new(&mut listener_transport).as_mut().poll(cx)).await; + let mut upgrade = match res { + TransportEvent::Incoming { + listener_id, + upgrade, + local_addr, + send_back_addr, + } => { + assert_eq!(listener_id, listener_transport.listener_id); + assert_eq!(local_addr, listener_transport.listen_addr); + assert_eq!(send_back_addr, listener_transport.listen_addr); + upgrade + } + _ => panic!("expected TransportEvent::Incoming, got {:?}", res), + }; + dialer_notify_inbound_rx.recv().await.unwrap(); + + // should receive the connection response from the mixnet + assert!( + poll_fn(|cx| Pin::new(&mut dialer_transport).as_mut().poll(cx)) + .now_or_never() + .is_none() + ); + info!("waiting for connections..."); + + // should be able to resolve the connections now + let (_, mut listener_conn) = poll_fn(|cx| Pin::new(&mut upgrade).as_mut().poll_unpin(cx)) + .now_or_never() + .unwrap() + .unwrap(); + let (_, mut dialer_conn) = poll_fn(|cx| Pin::new(&mut dial).as_mut().poll_unpin(cx)) + .now_or_never() + .unwrap() + .unwrap(); + info!("connections established"); + + // initiate a new substream from the dialer + let mut dialer_substream = + poll_fn(|cx| Pin::new(&mut dialer_conn).as_mut().poll_outbound(cx)) + .await + .unwrap(); + listener_notify_inbound_rx.recv().await.unwrap(); + + // accept the substream on the listener + assert!( + poll_fn(|cx| Pin::new(&mut listener_transport).as_mut().poll(cx)) + .now_or_never() + .is_none() + ); + poll_fn(|cx| Pin::new(&mut listener_conn).as_mut().poll(cx)).now_or_never(); + + // poll recipient's poll_inbound to receive the substream; sends a response to the sender + let mut listener_substream = + poll_fn(|cx| Pin::new(&mut listener_conn).as_mut().poll_inbound(cx)) + .now_or_never() + .unwrap() + .unwrap(); + info!("got listener substream"); + dialer_notify_inbound_rx.recv().await.unwrap(); + + // poll sender to finalize the substream + assert!( + poll_fn(|cx| Pin::new(&mut dialer_transport).as_mut().poll(cx)) + .now_or_never() + .is_none() + ); + poll_fn(|cx| Pin::new(&mut dialer_conn).as_mut().poll(cx)).now_or_never(); + info!("got dialer substream"); + + // write message from dialer to listener + send_and_receive_substream_message( + b"hello world".to_vec(), + Pin::new(&mut dialer_substream), + Pin::new(&mut listener_substream), + Pin::new(&mut listener_transport), + Pin::new(&mut listener_conn), + &mut listener_notify_inbound_rx, + ) + .await; + + // write message from listener to dialer + send_and_receive_substream_message( + b"hello back".to_vec(), + Pin::new(&mut listener_substream), + Pin::new(&mut dialer_substream), + Pin::new(&mut dialer_transport), + Pin::new(&mut dialer_conn), + &mut dialer_notify_inbound_rx, + ) + .await; + + // close the substream from the dialer side + info!("closing dialer substream"); + dialer_substream.close().await.unwrap(); + listener_notify_inbound_rx.recv().await.unwrap(); + info!("dialer substream closed"); + + // assert we can't read or write to either substream + dialer_substream.write_all(b"hello").await.unwrap_err(); + // poll listener transport and conn to receive the substream close message + poll_fn(|cx| Pin::new(&mut listener_transport).as_mut().poll(cx)).now_or_never(); + poll_fn(|cx| Pin::new(&mut listener_conn).as_mut().poll(cx)).now_or_never(); + listener_substream.write_all(b"hello").await.unwrap_err(); + let mut buf = vec![0u8; 5]; + dialer_substream.read(&mut buf).await.unwrap_err(); + listener_substream.read(&mut buf).await.unwrap_err(); + dialer_substream.close().await.unwrap_err(); + listener_substream.close().await.unwrap_err(); + } + + async fn send_and_receive_substream_message( + data: Vec, + mut sender_substream: Pin<&mut Substream>, + mut recipient_substream: Pin<&mut Substream>, + mut recipient_transport: Pin<&mut NymTransport>, + mut recipient_conn: Pin<&mut Connection>, + recipient_notify_inbound_rx: &mut UnboundedReceiver<()>, + ) { + // write message + sender_substream.write_all(&data).await.unwrap(); + recipient_notify_inbound_rx.recv().await.unwrap(); + + // poll recipient for message + poll_fn(|cx| recipient_transport.as_mut().poll(cx)).now_or_never(); + poll_fn(|cx| recipient_conn.as_mut().poll(cx)).now_or_never(); + let mut buf = vec![0u8; data.len()]; + let n = recipient_substream.read(&mut buf).await.unwrap(); + assert_eq!(n, data.len()); + assert_eq!(buf, data[..]); + } + + #[tokio::test] + async fn test_transport_timeout() { + let client = MixnetClient::connect_new().await.unwrap(); + + let (dialer_notify_inbound_tx, _) = unbounded_channel(); + let mut dialer_transport = + NymTransport::new_with_notify_inbound(client, dialer_notify_inbound_tx) + .await + .unwrap() + .with_timeout(std::time::Duration::from_millis(100)); + + // mock a transport that will never resolve the connection. + let empty_addr = Multiaddr::from_str( + "/nym/Hmer6Ndt3PV13YW53HM8ri4NvqqtfDQUQBhzvKqb1dag.2g478dyxtrQXGWc1Mk2VEqdPcWXpz7EhAcjhdAJtVZdA@AnnYnEtBjB2a5sHmeRCnBq43qxyHDf95Bqd7cwQyKNLR" + ) + .expect("unable to parse multiaddress"); + + let dial = dialer_transport.dial(empty_addr).unwrap(); + assert!(dial + .await + .expect_err("should have timed out") + .to_string() + .contains("dial timed out")); + } +} From 92350daca875523226a889d7d634f9ee883606af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jon=20H=C3=A4ggblad?= Date: Tue, 22 Aug 2023 14:17:03 +0200 Subject: [PATCH 308/361] network-requester: disable poisson process by default (#3783) * network-requester: disable poisson process by default * network-requester: instead add new top-level config field * Remove quoation marks in template --- contracts/name-service/src/contract.rs | 1 - .../name-service/src/integration_tests/register.rs | 1 - .../network-requester/src/allowed_hosts/hosts.rs | 2 +- .../network-requester/src/cli/init.rs | 13 +++++++------ service-providers/network-requester/src/cli/mod.rs | 7 ++++++- service-providers/network-requester/src/cli/run.rs | 1 + .../network-requester/src/config/mod.rs | 14 ++++++++++++-- .../src/config/old_config_v1_1_20_2.rs | 2 +- .../network-requester/src/config/template.rs | 10 +++++++++- 9 files changed, 37 insertions(+), 14 deletions(-) diff --git a/contracts/name-service/src/contract.rs b/contracts/name-service/src/contract.rs index 2001341212..35294382c8 100644 --- a/contracts/name-service/src/contract.rs +++ b/contracts/name-service/src/contract.rs @@ -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 { diff --git a/contracts/name-service/src/integration_tests/register.rs b/contracts/name-service/src/integration_tests/register.rs index aeed4add3d..e46499ce32 100644 --- a/contracts/name-service/src/integration_tests/register.rs +++ b/contracts/name-service/src/integration_tests/register.rs @@ -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![ diff --git a/service-providers/network-requester/src/allowed_hosts/hosts.rs b/service-providers/network-requester/src/allowed_hosts/hosts.rs index 11aa3dd276..b9d534fe89 100644 --- a/service-providers/network-requester/src/allowed_hosts/hosts.rs +++ b/service-providers/network-requester/src/allowed_hosts/hosts.rs @@ -100,7 +100,7 @@ impl HostsStore { .unwrap(); if let Err(e) = writeln!(file, "{text}") { - eprintln!("Couldn't write to file: {e}"); + log::error!("Couldn't write to file: {e}"); } } diff --git a/service-providers/network-requester/src/cli/init.rs b/service-providers/network-requester/src/cli/init.rs index a1eb89f518..30797dc26e 100644 --- a/service-providers/network-requester/src/cli/init.rs +++ b/service-providers/network-requester/src/cli/init.rs @@ -106,7 +106,7 @@ fn init_paths(id: &str) -> io::Result<()> { } pub(crate) async fn execute(args: &Init) -> Result<(), NetworkRequesterError> { - eprintln!("Initialising client..."); + log::info!("Initialising client..."); let id = &args.id; @@ -114,7 +114,7 @@ pub(crate) async fn execute(args: &Init) -> Result<(), NetworkRequesterError> { // in case we're using old config, try to upgrade it // (if we're using the current version, it's a no-op) try_upgrade_config(id)?; - eprintln!("Client \"{id}\" was already initialised before"); + log::info!("Client \"{id}\" was already initialised before"); true } else { init_paths(&args.id)?; @@ -125,7 +125,7 @@ pub(crate) async fn execute(args: &Init) -> Result<(), NetworkRequesterError> { // re-registering if wanted. let user_wants_force_register = args.force_register_gateway; if user_wants_force_register { - eprintln!("Instructed to force registering gateway. This might overwrite keys!"); + log::warn!("Instructed to force registering gateway. This might overwrite keys!"); } // If the client was already initialized, don't generate new keys and don't re-register with @@ -142,6 +142,7 @@ pub(crate) async fn execute(args: &Init) -> Result<(), NetworkRequesterError> { // Load and potentially override config let config = override_config(Config::new(id), OverrideConfig::from(args.clone())); + log::debug!("Using config: {:#?}", config); // Setup gateway by either registering a new one, or creating a new config from the selected // one but with keys kept, or reusing the gateway configuration. @@ -156,21 +157,21 @@ pub(crate) async fn execute(args: &Init) -> Result<(), NetworkRequesterError> { Some(&config.base.client.nym_api_urls), ) .await - .tap_err(|err| eprintln!("Failed to setup gateway\nError: {err}"))? + .tap_err(|err| log::error!("Failed to setup gateway\nError: {err}"))? .details; let config_save_location = config.default_location(); config.save_to_default_location().tap_err(|_| { log::error!("Failed to save the config file"); })?; - eprintln!( + log::info!( "Saved configuration file to {}", config_save_location.display() ); let address = init_details.client_address()?; - eprintln!("Client configuration completed.\n"); + log::info!("Client configuration completed.\n"); let init_results = InitResults::new(&config, &address, &init_details.gateway_details); println!("{}", args.output.format(&init_results)); diff --git a/service-providers/network-requester/src/cli/mod.rs b/service-providers/network-requester/src/cli/mod.rs index 80dd4751a5..1eeed74c28 100644 --- a/service-providers/network-requester/src/cli/mod.rs +++ b/service-providers/network-requester/src/cli/mod.rs @@ -84,7 +84,12 @@ pub(crate) struct OverrideConfig { } pub(crate) fn override_config(config: Config, args: OverrideConfig) -> Config { - let disable_cover_traffic_with_keepalive = args.medium_toggle; + // These flags have overlapping effects, meaning the order matters here. Making it a bit messy. + // Since a big chunk of these are hidden experimental flags there is hope we can remove them + // soonish and clean this up. + + let disable_cover_traffic_with_keepalive = + config.network_requester.disable_poisson_rate || args.medium_toggle; let secondary_packet_size = args.medium_toggle.then_some(PacketSize::ExtendedPacket16); let no_per_hop_delays = args.medium_toggle; diff --git a/service-providers/network-requester/src/cli/run.rs b/service-providers/network-requester/src/cli/run.rs index 3c3e31ecbb..6827c3ed82 100644 --- a/service-providers/network-requester/src/cli/run.rs +++ b/service-providers/network-requester/src/cli/run.rs @@ -84,6 +84,7 @@ pub(crate) async fn execute(args: &Run) -> Result<(), NetworkRequesterError> { let mut config = try_load_current_config(&args.id)?; config = override_config(config, OverrideConfig::from(args.clone())); + log::debug!("Using config: {:#?}", config); if !version_check(&config) { error!("failed the local version check"); diff --git a/service-providers/network-requester/src/config/mod.rs b/service-providers/network-requester/src/config/mod.rs index ee7b4ce60e..e4ba1fa9da 100644 --- a/service-providers/network-requester/src/config/mod.rs +++ b/service-providers/network-requester/src/config/mod.rs @@ -159,9 +159,19 @@ impl Config { } } -#[derive(Debug, Default, Clone, Deserialize, PartialEq, Serialize)] +#[derive(Debug, Clone, Deserialize, PartialEq, Serialize)] #[serde(default, deny_unknown_fields)] -pub struct NetworkRequester {} +pub struct NetworkRequester { + pub disable_poisson_rate: bool, +} + +impl Default for NetworkRequester { + fn default() -> Self { + NetworkRequester { + disable_poisson_rate: true, + } + } +} #[derive(Debug, Clone, Copy, Deserialize, PartialEq, Serialize)] #[serde(default, deny_unknown_fields)] diff --git a/service-providers/network-requester/src/config/old_config_v1_1_20_2.rs b/service-providers/network-requester/src/config/old_config_v1_1_20_2.rs index 92defcd702..20e3634132 100644 --- a/service-providers/network-requester/src/config/old_config_v1_1_20_2.rs +++ b/service-providers/network-requester/src/config/old_config_v1_1_20_2.rs @@ -79,7 +79,7 @@ pub struct NetworkRequesterV1_1_20_2 {} impl From for NetworkRequester { fn from(_value: NetworkRequesterV1_1_20_2) -> Self { - NetworkRequester {} + NetworkRequester::default() } } diff --git a/service-providers/network-requester/src/config/template.rs b/service-providers/network-requester/src/config/template.rs index 34df005821..811c9602f4 100644 --- a/service-providers/network-requester/src/config/template.rs +++ b/service-providers/network-requester/src/config/template.rs @@ -37,7 +37,7 @@ nym_api_urls = [ {{/each}} ] -[storage_paths] +[storage_paths] # Path to file containing private identity key. keys.private_identity_key_file = '{{ storage_paths.keys.private_identity_key_file }}' @@ -76,6 +76,14 @@ allowed_list_location = '{{ storage_paths.allowed_list_location }}' unknown_list_location = '{{ storage_paths.unknown_list_location }}' +[network_requester] + +# Disable Poisson sending rate, and only send cover traffic occasionally as keepalive messages. +# This is equivalent to setting debug.traffic.disable_main_poisson_packet_distribution = true, +# and debug.cover_traffic.loop_cover_traffic_average_delay = 5s. +disable_poisson_rate = {{ network_requester.disable_poisson_rate }} + + ##### logging configuration options ##### [logging] From af4e8241e7aa2bf872c3d9f2a3b113eda9c82721 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jon=20H=C3=A4ggblad?= Date: Wed, 23 Aug 2023 10:07:45 +0200 Subject: [PATCH 309/361] Fix rocksdb compilation (#3801) * Fix rocksdb compilation * Add ephemera to CI build * Add nym-nr-query to CI build * Fix clippy in ephemera rocksdb --- .github/workflows/build.yml | 4 ++++ Cargo.lock | 29 ++++++++++++++++++--------- ephemera/Cargo.toml | 2 +- ephemera/src/core/builder.rs | 2 +- ephemera/src/storage/rocksdb/mod.rs | 2 +- ephemera/src/storage/rocksdb/query.rs | 2 +- ephemera/src/storage/rocksdb/store.rs | 3 ++- 7 files changed, 30 insertions(+), 14 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index ce9f7820b0..df3ad85309 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -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' diff --git a/Cargo.lock b/Cargo.lock index 26d7b17568..9024fa7ad6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -846,9 +846,9 @@ dependencies = [ [[package]] name = "bindgen" -version = "0.64.0" +version = "0.65.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4243e6031260db77ede97ad86c27e501d646a27ab57b59a574f725d98ab1fb4" +checksum = "cfdf7b466f9a4903edc73f95d6d2bcd5baf8ae620638762244d3f60143643cc5" dependencies = [ "bitflags 1.3.2", "cexpr", @@ -856,12 +856,13 @@ dependencies = [ "lazy_static", "lazycell", "peeking_take_while", + "prettyplease 0.2.12", "proc-macro2", "quote", "regex", "rustc-hash", "shlex", - "syn 1.0.109", + "syn 2.0.28", ] [[package]] @@ -5015,9 +5016,9 @@ dependencies = [ [[package]] name = "librocksdb-sys" -version = "0.10.0+7.9.2" +version = "0.11.0+8.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fe4d5874f5ff2bc616e55e8c6086d478fcda13faf9495768a4aa1c22042d30b" +checksum = "d3386f101bcb4bd252d8e9d2fb41ec3b0862a15a62b478c355b2982efa469e3e" dependencies = [ "bindgen", "bzip2-sys", @@ -7759,6 +7760,16 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "prettyplease" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c64d9ba0963cdcea2e1b2230fbae2bab30eb25a174be395c41e764bfb65dd62" +dependencies = [ + "proc-macro2", + "syn 2.0.28", +] + [[package]] name = "proc-macro-crate" version = "1.1.3" @@ -7861,7 +7872,7 @@ dependencies = [ "log", "multimap", "petgraph", - "prettyplease", + "prettyplease 0.1.25", "prost", "prost-types", "regex", @@ -8612,9 +8623,9 @@ dependencies = [ [[package]] name = "rocksdb" -version = "0.20.1" +version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "015439787fce1e75d55f279078d33ff14b4af5d93d995e8838ee4631301c8a99" +checksum = "bb6f170a4041d50a0ce04b0d2e14916d6ca863ea2e422689a5b694395d299ffe" dependencies = [ "libc", "librocksdb-sys", @@ -8879,7 +8890,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b08f58cf71a58bda5734758eb20051cdb66c06c9243badbc45092ced1be834df" dependencies = [ "macro_rules_attribute", - "prettyplease", + "prettyplease 0.1.25", "proc-macro2", "quote", "syn 1.0.109", diff --git a/ephemera/Cargo.toml b/ephemera/Cargo.toml index c831726134..50743df2fe 100644 --- a/ephemera/Cargo.toml +++ b/ephemera/Cargo.toml @@ -37,7 +37,7 @@ nym-ephemera-common = { path = "../common/cosmwasm-smart-contracts/ephemera" } pretty_env_logger = "0.4" refinery = { version = "0.8.7", features = ["rusqlite"], optional = true } reqwest = { version = "0.11.6", features = ["json"] } -rocksdb = { version = "0.20.1", optional = true } +rocksdb = { version = "0.21.0", optional = true } rusqlite = { version = "0.27.0", features = ["bundled"], optional = true } serde = { version = "1.0", features = ["derive"] } serde_derive = "1.0.149" diff --git a/ephemera/src/core/builder.rs b/ephemera/src/core/builder.rs index db9b3e8254..58d4b7cd08 100644 --- a/ephemera/src/core/builder.rs +++ b/ephemera/src/core/builder.rs @@ -211,7 +211,7 @@ impl EphemeraStarterWithApplication { #[cfg(feature = "rocksdb_storage")] fn connect_rocksdb(&self) -> anyhow::Result { info!("Opening database..."); - RocksDbStorage::open(self.init.config.storage.clone()) + RocksDbStorage::open(&self.init.config.storage) .map_err(|e| anyhow::anyhow!("Failed to open database: {}", e)) } diff --git a/ephemera/src/storage/rocksdb/mod.rs b/ephemera/src/storage/rocksdb/mod.rs index dc86a4bd7f..24e5916bc3 100644 --- a/ephemera/src/storage/rocksdb/mod.rs +++ b/ephemera/src/storage/rocksdb/mod.rs @@ -104,7 +104,7 @@ fn block_hash_key(block_hash: &str) -> String { format!("{PREFIX_BLOCK_HASH}:{block_hash}") } -fn block_height_key(height: &u64) -> String { +fn block_height_key(height: u64) -> String { format!("{PREFIX_BLOCK_HEIGHT}:{height}") } diff --git a/ephemera/src/storage/rocksdb/query.rs b/ephemera/src/storage/rocksdb/query.rs index a2586557e5..a41bde78e9 100644 --- a/ephemera/src/storage/rocksdb/query.rs +++ b/ephemera/src/storage/rocksdb/query.rs @@ -53,7 +53,7 @@ impl Database { pub(crate) fn get_block_by_height(&self, height: u64) -> anyhow::Result> { trace!("Getting block by height: {}", height); - if let Some(block_hash) = self.database.get(block_height_key(&height))? { + if let Some(block_hash) = self.database.get(block_height_key(height))? { let block_hash = String::from_utf8(block_hash)?; self.get_block_by_hash(&block_hash) } else { diff --git a/ephemera/src/storage/rocksdb/store.rs b/ephemera/src/storage/rocksdb/store.rs index 66e1f6d2de..9b65137863 100644 --- a/ephemera/src/storage/rocksdb/store.rs +++ b/ephemera/src/storage/rocksdb/store.rs @@ -12,6 +12,7 @@ use rocksdb::{TransactionDB, WriteBatchWithTransaction}; use crate::utilities::crypto::Certificate; +#[allow(clippy::module_name_repetitions)] pub struct DbStore { connection: Arc, } @@ -34,7 +35,7 @@ impl DbStore { let block_id_key = block_hash_key(&hash_str); let certificates_key = certificates_key(&hash_str); - let height_key = block_height_key(&block.header.height); + let height_key = block_height_key(block.header.height); let members_key = members_key(&hash_str); let merkle_tree_key = merkle_tree_key(&hash_str); From 2cd2b1ccd4a080e3328d6879a606f53541ca4734 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C4=99drzej=20Stuczy=C5=84ski?= Date: Wed, 23 Aug 2023 09:09:44 +0100 Subject: [PATCH 310/361] always using http client in v034 compat mode --- .../examples/offline_signing.rs | 4 ++-- .../validator-client/src/client.rs | 5 +++-- .../client-libs/validator-client/src/lib.rs | 2 ++ .../src/nyxd/cosmwasm_client/mod.rs | 7 +++++-- .../validator-client/src/nyxd/mod.rs | 9 ++++++--- .../validator-client/src/rpc/mod.rs | 19 +++++++++++++++++++ 6 files changed, 37 insertions(+), 9 deletions(-) diff --git a/common/client-libs/validator-client/examples/offline_signing.rs b/common/client-libs/validator-client/examples/offline_signing.rs index baa2d10035..0e5bce5681 100644 --- a/common/client-libs/validator-client/examples/offline_signing.rs +++ b/common/client-libs/validator-client/examples/offline_signing.rs @@ -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(); diff --git a/common/client-libs/validator-client/src/client.rs b/common/client-libs/validator-client/src/client.rs index e70cbdaf2f..22a986558a 100644 --- a/common/client-libs/validator-client/src/client.rs +++ b/common/client-libs/validator-client/src/client.rs @@ -26,6 +26,7 @@ pub use nym_mixnet_contract_common::{ // re-export the type to not break existing imports pub use crate::coconut::CoconutApiClient; +use crate::rpc::http_client; #[cfg(feature = "http-client")] use crate::{DirectSigningHttpRpcValidatorClient, HttpRpcClient, QueryHttpRpcValidatorClient}; @@ -95,7 +96,7 @@ impl Client { config: Config, mnemonic: bip39::Mnemonic, ) -> Result { - 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 +127,7 @@ impl Client { #[cfg(feature = "http-client")] impl Client { pub fn new_query(config: Config) -> Result { - 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)) } diff --git a/common/client-libs/validator-client/src/lib.rs b/common/client-libs/validator-client/src/lib.rs index c6391673a0..b3dca0a473 100644 --- a/common/client-libs/validator-client/src/lib.rs +++ b/common/client-libs/validator-client/src/lib.rs @@ -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 diff --git a/common/client-libs/validator-client/src/nyxd/cosmwasm_client/mod.rs b/common/client-libs/validator-client/src/nyxd/cosmwasm_client/mod.rs index 7ebff2ef16..44c917e25d 100644 --- a/common/client-libs/validator-client/src/nyxd/cosmwasm_client/mod.rs +++ b/common/client-libs/validator-client/src/nyxd/cosmwasm_client/mod.rs @@ -10,11 +10,14 @@ use crate::signing::{ AccountData, }; use async_trait::async_trait; +use cosmrs::tx::{Raw, SignDoc}; +use std::fmt::Debug; use tendermint_rpc::{Error as TendermintRpcError, 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 +76,7 @@ impl MaybeSigningClient { where U: TryInto, { - self.client = HttpClient::new(new_endpoint)?; + self.client = http_client(new_endpoint)?; Ok(()) } } diff --git a/common/client-libs/validator-client/src/nyxd/mod.rs b/common/client-libs/validator-client/src/nyxd/mod.rs index 16027a1970..550608ad9d 100644 --- a/common/client-libs/validator-client/src/nyxd/mod.rs +++ b/common/client-libs/validator-client/src/nyxd/mod.rs @@ -50,9 +50,12 @@ use crate::signing::direct_wallet::DirectSecp256k1HdWallet; #[cfg(feature = "http-client")] use crate::{DirectSigningHttpRpcNyxdClient, QueryHttpRpcNyxdClient}; use crate::{DirectSigningReqwestRpcNyxdClient, QueryReqwestRpcNyxdClient, ReqwestRpcClient}; +use url::Url; + +#[cfg(feature = "http-client")] +use crate::http_client; #[cfg(feature = "http-client")] use cosmrs::rpc::{HttpClient, HttpClientUrl}; -use url::Url; pub mod coin; pub mod contract_traits; @@ -97,7 +100,7 @@ impl NyxdClient { where U: TryInto, { - let client = HttpClient::new(endpoint)?; + let client = http_client(endpoint)?; Ok(NyxdClient { client: MaybeSigningClient::new(client, (&config).into()), @@ -140,7 +143,7 @@ impl NyxdClient { where U: TryInto, { - 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); diff --git a/common/client-libs/validator-client/src/rpc/mod.rs b/common/client-libs/validator-client/src/rpc/mod.rs index f483f1067c..fd3f7991ba 100644 --- a/common/client-libs/validator-client/src/rpc/mod.rs +++ b/common/client-libs/validator-client/src/rpc/mod.rs @@ -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(url: U) -> Result +where + U: TryInto, +{ + 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))] From 2651784e8b44c35de84a9428dc8dfc808674fda6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C4=99drzej=20Stuczy=C5=84ski?= Date: Wed, 23 Aug 2023 10:42:09 +0100 Subject: [PATCH 311/361] fully delegating 'TendermintRpcClient' trait --- .../src/nyxd/cosmwasm_client/mod.rs | 218 ++++++++++++++++- .../validator-client/src/nyxd/mod.rs | 230 +++++++++++++++++- nym-connect/desktop/Cargo.lock | 2 +- 3 files changed, 438 insertions(+), 12 deletions(-) diff --git a/common/client-libs/validator-client/src/nyxd/cosmwasm_client/mod.rs b/common/client-libs/validator-client/src/nyxd/cosmwasm_client/mod.rs index 44c917e25d..4b3fb7deb1 100644 --- a/common/client-libs/validator-client/src/nyxd/cosmwasm_client/mod.rs +++ b/common/client-libs/validator-client/src/nyxd/cosmwasm_client/mod.rs @@ -3,16 +3,20 @@ 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 cosmrs::rpc::endpoint::*; +use cosmrs::tendermint::{abci, evidence::Evidence, Genesis}; use cosmrs::tx::{Raw, SignDoc}; +use serde::{de::DeserializeOwned, Serialize}; use std::fmt::Debug; -use tendermint_rpc::{Error as TendermintRpcError, SimpleRequest}; +use tendermint_rpc::query::Query; +use tendermint_rpc::{Error as TendermintRpcError, Order, Paging, SimpleRequest}; #[cfg(feature = "http-client")] use crate::http_client; @@ -88,6 +92,216 @@ where C: TendermintRpcClient + Send + Sync, S: Send + Sync, { + async fn abci_info(&self) -> Result { + self.client.abci_info().await + } + + async fn abci_query( + &self, + path: Option, + data: V, + height: Option, + prove: bool, + ) -> Result + where + V: Into> + Send, + { + self.client.abci_query(path, data, height, prove).await + } + + async fn block(&self, height: H) -> Result + where + H: Into + Send, + { + self.client.block(height).await + } + + async fn block_by_hash( + &self, + hash: Hash, + ) -> Result { + self.client.block_by_hash(hash).await + } + + async fn latest_block(&self) -> Result { + self.client.latest_block().await + } + + async fn header(&self, height: H) -> Result + where + H: Into + Send, + { + self.client.header(height).await + } + + async fn header_by_hash( + &self, + hash: Hash, + ) -> Result { + self.client.header_by_hash(hash).await + } + + async fn block_results( + &self, + height: H, + ) -> Result + where + H: Into + Send, + { + self.client.block_results(height).await + } + + async fn latest_block_results(&self) -> Result { + self.client.latest_block_results().await + } + + async fn block_search( + &self, + query: Query, + page: u32, + per_page: u8, + order: Order, + ) -> Result { + self.client.block_search(query, page, per_page, order).await + } + + async fn blockchain( + &self, + min: H, + max: H, + ) -> Result + where + H: Into + Send, + { + self.client.blockchain(min, max).await + } + + async fn broadcast_tx_async( + &self, + tx: T, + ) -> Result + where + T: Into> + Send, + { + self.client.broadcast_tx_async(tx).await + } + + async fn broadcast_tx_sync( + &self, + tx: T, + ) -> Result + where + T: Into> + Send, + { + self.client.broadcast_tx_sync(tx).await + } + + async fn broadcast_tx_commit( + &self, + tx: T, + ) -> Result + where + T: Into> + Send, + { + self.client.broadcast_tx_commit(tx).await + } + + async fn commit(&self, height: H) -> Result + where + H: Into + Send, + { + self.client.commit(height).await + } + + async fn consensus_params( + &self, + height: H, + ) -> Result + where + H: Into + Send, + { + self.client.consensus_params(height).await + } + + async fn consensus_state(&self) -> Result { + self.client.consensus_state().await + } + + async fn validators( + &self, + height: H, + paging: Paging, + ) -> Result + where + H: Into + Send, + { + self.client.validators(height, paging).await + } + + async fn latest_consensus_params( + &self, + ) -> Result { + self.client.latest_consensus_params().await + } + + async fn latest_commit(&self) -> Result { + self.client.latest_commit().await + } + + async fn health(&self) -> Result<(), TendermintRpcError> { + self.client.health().await + } + + async fn genesis(&self) -> Result, TendermintRpcError> + where + AppState: Debug + Serialize + DeserializeOwned + Send, + { + self.client.genesis().await + } + + async fn net_info(&self) -> Result { + self.client.net_info().await + } + + async fn status(&self) -> Result { + self.client.status().await + } + + async fn broadcast_evidence( + &self, + e: Evidence, + ) -> Result { + self.client.broadcast_evidence(e).await + } + + async fn tx(&self, hash: Hash, prove: bool) -> Result { + self.client.tx(hash, prove).await + } + + async fn tx_search( + &self, + query: Query, + prove: bool, + page: u32, + per_page: u8, + order: Order, + ) -> Result { + 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(&self, timeout: T) -> Result<(), Error> + where + T: Into + Send, + { + self.client.wait_until_healthy(timeout).await + } + async fn perform(&self, request: R) -> Result where R: SimpleRequest, diff --git a/common/client-libs/validator-client/src/nyxd/mod.rs b/common/client-libs/validator-client/src/nyxd/mod.rs index 550608ad9d..489b0193db 100644 --- a/common/client-libs/validator-client/src/nyxd/mod.rs +++ b/common/client-libs/validator-client/src/nyxd/mod.rs @@ -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::rpc::endpoint::*; +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::{Error as TendermintRpcError, Order}; +use url::Url; pub use crate::nyxd::cosmwasm_client::client_traits::{CosmWasmClient, SigningCosmWasmClient}; pub use crate::nyxd::fee::Fee; @@ -45,17 +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::{DirectSigningHttpRpcNyxdClient, QueryHttpRpcNyxdClient}; -use crate::{DirectSigningReqwestRpcNyxdClient, QueryReqwestRpcNyxdClient, ReqwestRpcClient}; -use url::Url; - #[cfg(feature = "http-client")] use crate::http_client; #[cfg(feature = "http-client")] +use crate::{DirectSigningHttpRpcNyxdClient, QueryHttpRpcNyxdClient}; +#[cfg(feature = "http-client")] use cosmrs::rpc::{HttpClient, HttpClientUrl}; +use tendermint_rpc::query::Query; pub mod coin; pub mod contract_traits; @@ -571,6 +573,216 @@ where C: TendermintRpcClient + Send + Sync, S: Send + Sync, { + async fn abci_info(&self) -> Result { + self.client.abci_info().await + } + + async fn abci_query( + &self, + path: Option, + data: V, + height: Option, + prove: bool, + ) -> Result + where + V: Into> + Send, + { + self.client.abci_query(path, data, height, prove).await + } + + async fn block(&self, height: H) -> Result + where + H: Into + Send, + { + self.client.block(height).await + } + + async fn block_by_hash( + &self, + hash: Hash, + ) -> Result { + self.client.block_by_hash(hash).await + } + + async fn latest_block(&self) -> Result { + self.client.latest_block().await + } + + async fn header(&self, height: H) -> Result + where + H: Into + Send, + { + self.client.header(height).await + } + + async fn header_by_hash( + &self, + hash: Hash, + ) -> Result { + self.client.header_by_hash(hash).await + } + + async fn block_results( + &self, + height: H, + ) -> Result + where + H: Into + Send, + { + self.client.block_results(height).await + } + + async fn latest_block_results(&self) -> Result { + self.client.latest_block_results().await + } + + async fn block_search( + &self, + query: Query, + page: u32, + per_page: u8, + order: Order, + ) -> Result { + self.client.block_search(query, page, per_page, order).await + } + + async fn blockchain( + &self, + min: H, + max: H, + ) -> Result + where + H: Into + Send, + { + self.client.blockchain(min, max).await + } + + async fn broadcast_tx_async( + &self, + tx: T, + ) -> Result + where + T: Into> + Send, + { + TendermintRpcClient::broadcast_tx_async(&self.client, tx).await + } + + async fn broadcast_tx_sync( + &self, + tx: T, + ) -> Result + where + T: Into> + Send, + { + TendermintRpcClient::broadcast_tx_sync(&self.client, tx).await + } + + async fn broadcast_tx_commit( + &self, + tx: T, + ) -> Result + where + T: Into> + Send, + { + TendermintRpcClient::broadcast_tx_commit(&self.client, tx).await + } + + async fn commit(&self, height: H) -> Result + where + H: Into + Send, + { + self.client.commit(height).await + } + + async fn consensus_params( + &self, + height: H, + ) -> Result + where + H: Into + Send, + { + self.client.consensus_params(height).await + } + + async fn consensus_state(&self) -> Result { + self.client.consensus_state().await + } + + async fn validators( + &self, + height: H, + paging: Paging, + ) -> Result + where + H: Into + Send, + { + self.client.validators(height, paging).await + } + + async fn latest_consensus_params( + &self, + ) -> Result { + self.client.latest_consensus_params().await + } + + async fn latest_commit(&self) -> Result { + self.client.latest_commit().await + } + + async fn health(&self) -> Result<(), TendermintRpcError> { + self.client.health().await + } + + async fn genesis(&self) -> Result, TendermintRpcError> + where + AppState: Debug + Serialize + DeserializeOwned + Send, + { + self.client.genesis().await + } + + async fn net_info(&self) -> Result { + self.client.net_info().await + } + + async fn status(&self) -> Result { + self.client.status().await + } + + async fn broadcast_evidence( + &self, + e: Evidence, + ) -> Result { + self.client.broadcast_evidence(e).await + } + + async fn tx(&self, hash: Hash, prove: bool) -> Result { + self.client.tx(hash, prove).await + } + + async fn tx_search( + &self, + query: Query, + prove: bool, + page: u32, + per_page: u8, + order: Order, + ) -> Result { + 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(&self, timeout: T) -> Result<(), Error> + where + T: Into + Send, + { + self.client.wait_until_healthy(timeout).await + } + async fn perform(&self, request: R) -> Result where R: SimpleRequest, diff --git a/nym-connect/desktop/Cargo.lock b/nym-connect/desktop/Cargo.lock index 47977894ba..612a9d8149 100644 --- a/nym-connect/desktop/Cargo.lock +++ b/nym-connect/desktop/Cargo.lock @@ -3991,7 +3991,7 @@ dependencies = [ [[package]] name = "nym-connect" -version = "1.1.17" +version = "1.1.18" dependencies = [ "anyhow", "bip39", From 5241047f450f139fb1d02f8b6dd14bdd8f3f5a32 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C4=99drzej=20Stuczy=C5=84ski?= Date: Wed, 23 Aug 2023 11:05:31 +0100 Subject: [PATCH 312/361] implemented compatibility mode for reqwest client --- .../validator-client/src/client.rs | 1 + .../src/nyxd/cosmwasm_client/mod.rs | 2 +- .../validator-client/src/nyxd/mod.rs | 2 +- .../validator-client/src/rpc/reqwest.rs | 151 ++++++++++++++++-- 4 files changed, 141 insertions(+), 15 deletions(-) diff --git a/common/client-libs/validator-client/src/client.rs b/common/client-libs/validator-client/src/client.rs index 22a986558a..6352dabff0 100644 --- a/common/client-libs/validator-client/src/client.rs +++ b/common/client-libs/validator-client/src/client.rs @@ -26,6 +26,7 @@ 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}; diff --git a/common/client-libs/validator-client/src/nyxd/cosmwasm_client/mod.rs b/common/client-libs/validator-client/src/nyxd/cosmwasm_client/mod.rs index 4b3fb7deb1..ab78a8c418 100644 --- a/common/client-libs/validator-client/src/nyxd/cosmwasm_client/mod.rs +++ b/common/client-libs/validator-client/src/nyxd/cosmwasm_client/mod.rs @@ -10,11 +10,11 @@ use crate::signing::{ AccountData, }; use async_trait::async_trait; -use cosmrs::rpc::endpoint::*; 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}; diff --git a/common/client-libs/validator-client/src/nyxd/mod.rs b/common/client-libs/validator-client/src/nyxd/mod.rs index 489b0193db..10cca6de7e 100644 --- a/common/client-libs/validator-client/src/nyxd/mod.rs +++ b/common/client-libs/validator-client/src/nyxd/mod.rs @@ -17,7 +17,6 @@ use crate::signing::AccountData; use crate::{DirectSigningReqwestRpcNyxdClient, QueryReqwestRpcNyxdClient, ReqwestRpcClient}; use async_trait::async_trait; use cosmrs::cosmwasm; -use cosmrs::rpc::endpoint::*; use cosmrs::tendermint::{abci, evidence::Evidence, Genesis}; use cosmrs::tx::{Msg, Raw, SignDoc}; use cosmwasm_std::Addr; @@ -26,6 +25,7 @@ use serde::{de::DeserializeOwned, Serialize}; use std::fmt::Debug; use std::time::SystemTime; use tendermint_rpc::endpoint::block::Response as BlockResponse; +use tendermint_rpc::endpoint::*; use tendermint_rpc::{Error as TendermintRpcError, Order}; use url::Url; diff --git a/common/client-libs/validator-client/src/rpc/reqwest.rs b/common/client-libs/validator-client/src/rpc/reqwest.rs index 9c8411d666..ffad7dd815 100644 --- a/common/client-libs/validator-client/src/rpc/reqwest.rs +++ b/common/client-libs/validator-client/src/rpc/reqwest.rs @@ -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(&self, request: R) -> RequestBuilder { + pub fn set_compat_mode(&mut self, compat: CompatMode) { + self.compat = compat; + } + + fn build_request(&self, request: R) -> RequestBuilder + where + R: SimpleRequest, + 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(&self, request: R) -> Result + where + R: SimpleRequest, + 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(&self, request: R) -> Result + where + R: SimpleRequest, + { + self.perform_request(request).await + } + + async fn perform_v0_37(&self, request: R) -> Result + where + R: SimpleRequest, + { + 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(&self, height: H) -> Result + where + H: Into + Send, + { + perform_with_compat!(self, block_results::Request::new(height.into())) + } + + async fn latest_block_results(&self) -> Result { + perform_with_compat!(self, block_results::Request::default()) + } + + async fn header(&self, height: H) -> Result + where + H: Into + 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 { + 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 { + 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 { + 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 { + perform_with_compat!( + self, + tx_search::Request::new(query, prove, page, per_page, order) + ) + } + + async fn broadcast_tx_commit(&self, tx: T) -> Result + where + T: Into> + Send, + { + perform_with_compat!(self, broadcast::tx_commit::Request::new(tx)) } } From f8b70097d3f6a8955a478a7911b846602d87ef2e Mon Sep 17 00:00:00 2001 From: pierre Date: Wed, 23 Aug 2023 16:12:14 +0200 Subject: [PATCH 313/361] use geoip location for gateways --- explorer-api/.env.dev | 44 ++++ explorer-api/.gitignore | 1 + explorer-api/README.md | 18 +- explorer/.env.dev | 9 + explorer/.gitignore | 3 +- explorer/README.md | 15 ++ explorer/package.json | 29 +-- explorer/src/api/constants.ts | 1 + explorer/src/api/index.ts | 7 + explorer/src/components/Gateways.ts | 4 +- explorer/src/typeDefs/explorer-api.ts | 18 ++ yarn.lock | 339 ++------------------------ 12 files changed, 140 insertions(+), 348 deletions(-) create mode 100644 explorer-api/.env.dev create mode 100644 explorer/.env.dev diff --git a/explorer-api/.env.dev b/explorer-api/.env.dev new file mode 100644 index 0000000000..0020aba233 --- /dev/null +++ b/explorer-api/.env.dev @@ -0,0 +1,44 @@ +CONFIGURED=true + +RUST_LOG=info +RUST_BACKTRACE=1 + +BECH32_PREFIX=n +MIX_DENOM=unym +MIX_DENOM_DISPLAY=nym +STAKE_DENOM=unyx +STAKE_DENOM_DISPLAY=nyx +DENOMS_EXPONENT=6 + +REWARDING_VALIDATOR_ADDRESS=n1pefc2utwpy5w78p2kqdsfmpjxfwmn9d39k5mqa +MIXNET_CONTRACT_ADDRESS=n1xr3rq8yvd7qplsw5yx90ftsr2zdhg4e9z60h5duusgxpv72hud3sjkxkav +VESTING_CONTRACT_ADDRESS=n1unyuj8qnmygvzuex3dwmg9yzt9alhvyeat0uu0jedg2wj33efl5qackslz +BANDWIDTH_CLAIM_CONTRACT_ADDRESS=n19lc9u84cz0yz3fww5283nucc9yvr8gsjmgeul0 +COCONUT_BANDWIDTH_CONTRACT_ADDRESS=n16a32stm6kknhq5cc8rx77elr66pygf2hfszw7wvpq746x3uffylqkjar4l +GROUP_CONTRACT_ADDRESS=n1pd7kfgvr5tpcv0xnlv46c4jsq9jg2r799xxrcwqdm4l2jhq2pjwqrmz5ju +MULTISIG_CONTRACT_ADDRESS=n14ph4e660eyqz0j36zlkaey4zgzexm5twkmjlqaequxr2cjm9eprqsmad6k +COCONUT_DKG_CONTRACT_ADDRESS=n1ahg0erc2fs6xx3j5m8sfx3ryuzdjh6kf6qm9plsf865fltekyrfsesac6a +NAME_SERVICE_CONTRACT_ADDRESS=n12ne7qtmdwd0j03t9t5es8md66wq4e5xg9neladrsag8fx3y89rcs36asfp +SERVICE_PROVIDER_DIRECTORY_CONTRACT_ADDRESS=n1ps5yutd7sufwg058qd7ac7ldnlazsvmhzqwucsfxmm445d70u8asqxpur4 + +STATISTICS_SERVICE_DOMAIN_ADDRESS="http://0.0.0.0" +NYXD="https://sandbox-validator1.nymtech.net" +NYM_API="https://sandbox-nym-api1.nymtech.net/api" + +GEOIP_DB_PATH=geo_ip/GeoLite2-City.mmdb +# MaxMind account ID +# TODO replace with your own account ID +GEOIPUPDATE_ACCOUNT_ID=xxx +# MaxMind license key +# TODO replace with your own license key +GEOIPUPDATE_LICENSE_KEY=xxx +# List of space-separated database edition IDs. Edition IDs may +# consist of letters, digits, and dashes. For example, GeoIP2-City +# would download the GeoIP2 City database (GeoIP2-City). +GEOIPUPDATE_EDITION_IDS=GeoLite2-City +# The number of hours between geoipupdate runs. If this is not set +# or is set to 0, geoipupdate will run once and exit. +GEOIPUPDATE_FREQUENCY=72 +# The path to the directory where geoipupdate will download the +# database. +GEOIP_DB_DIRECTORY=./geo_ip diff --git a/explorer-api/.gitignore b/explorer-api/.gitignore index 66ab76436b..fd6f795b06 100644 --- a/explorer-api/.gitignore +++ b/explorer-api/.gitignore @@ -1,3 +1,4 @@ target explorer-api-state.json /geo_ip +!.env.dev diff --git a/explorer-api/README.md b/explorer-api/README.md index 86e64e4c12..8ab514d616 100644 --- a/explorer-api/README.md +++ b/explorer-api/README.md @@ -8,9 +8,17 @@ Features: - calculates how many nodes are in each country - proxies mixnode API requests to add HTTPS +## Development + +Several environment variables are required. They can be +provisioned via a `.env` file. For convenience a `.env.dev` is +provided, just copy its content into `.env`. + +Follow the steps to setup the geoip database. + ## GeoIP db install/update -First we need to install the geoip database. +A geoip database needs to be installed. We use https://github.com/maxmind/geoipupdate to automatically download and update GeoLite2 binary database. For convenience we @@ -37,13 +45,7 @@ When starting the explorer-api, supply the environment variable `GEOIP_DB_PATH`, pointing to the GeoLite2 binary database file. It should be previously installed thanks to `geoipupdate` service. -For example: - -```shell -GEOIP_DB_PATH=./geo_ip/GeoLite2-City.mmdb cargo run -``` - -Note: explorer-api binary reads the provided `.env` file. +Note: As mentioned above the explorer-api binary reads the provided `.env` file. Run as a service and reverse proxy with `nginx` to add `https` with Lets Encrypt. diff --git a/explorer/.env.dev b/explorer/.env.dev new file mode 100644 index 0000000000..fe4119f671 --- /dev/null +++ b/explorer/.env.dev @@ -0,0 +1,9 @@ +# When running the explorer API locally +#EXPLORER_API_URL=http://localhost:8000/v1 + +EXPLORER_API_URL=https://sandbox-explorer.nymtech.net/api/v1 +NYM_API_URL=https://sandbox-nym-api1.nymtech.net +VALIDATOR_URL=https://sandbox-validator1.nymtech.net +BIG_DIPPER_URL=https://sandbox-blocks.nymtech.net +CURRENCY_DENOM=unym +CURRENCY_STAKING_DENOM=unyx diff --git a/explorer/.gitignore b/explorer/.gitignore index b9a105e264..b8b1827577 100644 --- a/explorer/.gitignore +++ b/explorer/.gitignore @@ -1,3 +1,4 @@ dist .DS_Store -detailAPI.md \ No newline at end of file +detailAPI.md +!.env.dev diff --git a/explorer/README.md b/explorer/README.md index cc8c7f0363..a7bbc2346a 100644 --- a/explorer/README.md +++ b/explorer/README.md @@ -32,6 +32,21 @@ You can then open a browser to http://localhost:3000 and start development. Documentation for developers [can be found here](./docs). +Several environment variables are required. They can be +provisioned via a `.env` file. For convenience a `.env.dev` is +provided, just copy its content into `.env`. + +#### Required env vars + +``` +EXPLORER_API_URL +NYM_API_URL +VALIDATOR_URL +BIG_DIPPER_URL +CURRENCY_DENOM +CURRENCY_STAKING_DENOM +``` + ## Deployment Build the UI with (starting in the repository root): diff --git a/explorer/package.json b/explorer/package.json index 094e705132..885f378254 100644 --- a/explorer/package.json +++ b/explorer/package.json @@ -3,6 +3,20 @@ "version": "1.0.7", "private": true, "license": "Apache-2.0", + "scripts": { + "start": "webpack serve --progress --port 3000", + "build": "webpack build --progress --config webpack.prod.js", + "build:serve": "npx serve dist", + "test": "jest", + "test:watch": "jest --watch", + "tsc": "tsc --noEmit true", + "tsc:watch": "tsc --watch --noEmit true", + "lint": "eslint src", + "lint:fix": "eslint src --fix", + "prestorybook": "yarn --cwd .. build", + "storybook": "start-storybook -p 6006", + "storybook:build": "build-storybook" + }, "dependencies": { "@cosmjs/math": "^0.26.2", "@emotion/react": "^11.4.1", @@ -18,6 +32,7 @@ "big.js": "^6.2.1", "d3-scale": "^4.0.0", "date-fns": "^2.24.0", + "lodash": "^4.17.21", "i18n-iso-countries": "^6.8.0", "react": "^18.2.0", "react-dom": "^18.2.0", @@ -95,20 +110,6 @@ "webpack-favicons": "^1.3.8", "webpack-merge": "^5.8.0" }, - "scripts": { - "start": "webpack serve --progress --port 3000", - "build": "webpack build --progress --config webpack.prod.js", - "build:serve": "npx serve dist", - "test": "jest", - "test:watch": "jest --watch", - "tsc": "tsc --noEmit true", - "tsc:watch": "tsc --watch --noEmit true", - "lint": "eslint src", - "lint:fix": "eslint src --fix", - "prestorybook": "yarn --cwd .. build", - "storybook": "start-storybook -p 6006", - "storybook:build": "build-storybook" - }, "browserslist": { "production": [ ">0.2%", diff --git a/explorer/src/api/constants.ts b/explorer/src/api/constants.ts index e3f39a3cae..f00c9ec280 100644 --- a/explorer/src/api/constants.ts +++ b/explorer/src/api/constants.ts @@ -9,6 +9,7 @@ export const OVERVIEW_API = `${API_BASE_URL}/overview`; export const MIXNODE_PING = `${API_BASE_URL}/ping`; export const MIXNODES_API = `${API_BASE_URL}/mix-nodes`; export const MIXNODE_API = `${API_BASE_URL}/mix-node`; +export const GATEWAYS_EXPLORER_API = `${API_BASE_URL}/gateways`; export const GATEWAYS_API = `${NYM_API_BASE_URL}/api/v1/status/gateways/detailed`; export const VALIDATORS_API = `${VALIDATOR_BASE_URL}/validators`; export const BLOCK_API = `${NYM_API_BASE_URL}/block`; diff --git a/explorer/src/api/index.ts b/explorer/src/api/index.ts index ceb5878e97..a2b134d4ae 100644 --- a/explorer/src/api/index.ts +++ b/explorer/src/api/index.ts @@ -1,3 +1,4 @@ +import keyBy from 'lodash/keyBy'; import { API_BASE_URL, BLOCK_API, @@ -11,6 +12,7 @@ import { UPTIME_STORY_API, VALIDATORS_API, SERVICE_PROVIDERS, + GATEWAYS_EXPLORER_API, } from './constants'; import { @@ -32,6 +34,7 @@ import { GatewayBondAnnotated, GatewayBond, DirectoryServiceProvider, + LocatedGateway, } from '../typeDefs/explorer-api'; function getFromCache(key: string) { @@ -98,9 +101,13 @@ export class Api { static fetchGateways = async (): Promise => { const res = await fetch(GATEWAYS_API); const gatewaysAnnotated: GatewayBondAnnotated[] = await res.json(); + const res2 = await fetch(GATEWAYS_EXPLORER_API); + const locatedGateways: LocatedGateway[] = await res2.json(); + const locatedGatewaysByOwner = keyBy(locatedGateways, 'owner'); return gatewaysAnnotated.map(({ gateway_bond, node_performance }) => ({ ...gateway_bond, node_performance, + location: locatedGatewaysByOwner[gateway_bond.owner]?.location, })); }; diff --git a/explorer/src/components/Gateways.ts b/explorer/src/components/Gateways.ts index b78b1320da..39419257d4 100644 --- a/explorer/src/components/Gateways.ts +++ b/explorer/src/components/Gateways.ts @@ -26,7 +26,7 @@ export function gatewayToGridRow(arrayOfGateways: GatewayResponse): GatewayRowTy id: gw.owner, owner: gw.owner, identity_key: gw.gateway.identity_key || '', - location: gw?.gateway?.location || '', + location: gw.location?.country_name || '', bond: gw.pledge_amount.amount || 0, host: gw.gateway.host || '', version: gw.gateway.version || '', @@ -39,7 +39,7 @@ export function gatewayEnrichedToGridRow(gateway: GatewayBond, report: GatewayRe id: gateway.owner, owner: gateway.owner, identity_key: gateway.gateway.identity_key || '', - location: gateway?.gateway?.location || '', + location: gateway.location?.country_name || '', bond: gateway.pledge_amount.amount || 0, host: gateway.gateway.host || '', version: gateway.gateway.version || '', diff --git a/explorer/src/typeDefs/explorer-api.ts b/explorer/src/typeDefs/explorer-api.ts index 736c6e8ffa..9319c33952 100644 --- a/explorer/src/typeDefs/explorer-api.ts +++ b/explorer/src/typeDefs/explorer-api.ts @@ -131,6 +131,7 @@ export interface GatewayBond { owner: string; gateway: Gateway; node_performance: NodePerformance; + location?: Location; } export interface GatewayBondAnnotated { @@ -138,6 +139,23 @@ export interface GatewayBondAnnotated { node_performance: NodePerformance; } +export interface Location { + two_letter_iso_country_code: string; + three_letter_iso_country_code: string; + country_name: string; + latitude?: number; + longitude?: number; +} + +export interface LocatedGateway { + pledge_amount: Amount; + owner: string; + block_height: number; + gateway: Gateway; + proxy?: string; + location?: Location; +} + export type GatewayResponse = GatewayBond[]; export interface GatewayReportResponse { diff --git a/yarn.lock b/yarn.lock index 10f93b7eaf..ca5ac7a967 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1724,23 +1724,6 @@ resolved "https://registry.yarnpkg.com/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz#1d572bfbbe14b7704e0ba0f39b74815b84870d70" integrity sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw== -"@emotion/babel-plugin@^11.10.6": - version "11.10.6" - resolved "https://registry.yarnpkg.com/@emotion/babel-plugin/-/babel-plugin-11.10.6.tgz#a68ee4b019d661d6f37dec4b8903255766925ead" - integrity sha512-p2dAqtVrkhSa7xz1u/m9eHYdLi+en8NowrmXeF/dKtJpU8lCWli8RUAati7NcSl0afsBott48pdnANuD0wh9QQ== - dependencies: - "@babel/helper-module-imports" "^7.16.7" - "@babel/runtime" "^7.18.3" - "@emotion/hash" "^0.9.0" - "@emotion/memoize" "^0.8.0" - "@emotion/serialize" "^1.1.1" - babel-plugin-macros "^3.1.0" - convert-source-map "^1.5.0" - escape-string-regexp "^4.0.0" - find-root "^1.1.0" - source-map "^0.5.7" - stylis "4.1.3" - "@emotion/babel-plugin@^11.11.0": version "11.11.0" resolved "https://registry.yarnpkg.com/@emotion/babel-plugin/-/babel-plugin-11.11.0.tgz#c2d872b6a7767a9d176d007f5b31f7d504bb5d6c" @@ -1769,19 +1752,19 @@ "@emotion/weak-memoize" "^0.3.1" stylis "4.2.0" -"@emotion/hash@^0.9.0", "@emotion/hash@^0.9.1": +"@emotion/hash@^0.9.1": version "0.9.1" resolved "https://registry.yarnpkg.com/@emotion/hash/-/hash-0.9.1.tgz#4ffb0055f7ef676ebc3a5a91fb621393294e2f43" integrity sha512-gJB6HLm5rYwSLI6PQa+X1t5CFGrv1J1TWG+sOyMCeKz2ojaj6Fnl/rZEspogG+cvqbt4AE/2eIyD2QfLKTBNlQ== -"@emotion/is-prop-valid@^1.2.0", "@emotion/is-prop-valid@^1.2.1": +"@emotion/is-prop-valid@^1.2.1": version "1.2.1" resolved "https://registry.yarnpkg.com/@emotion/is-prop-valid/-/is-prop-valid-1.2.1.tgz#23116cf1ed18bfeac910ec6436561ecb1a3885cc" integrity sha512-61Mf7Ufx4aDxx1xlDeOm8aFFigGHE4z+0sKCa+IHCeZKiyP9RLD0Mmx7m8b9/Cf37f7NAvQOOJAbQQGVr5uERw== dependencies: "@emotion/memoize" "^0.8.1" -"@emotion/memoize@^0.8.0", "@emotion/memoize@^0.8.1": +"@emotion/memoize@^0.8.1": version "0.8.1" resolved "https://registry.yarnpkg.com/@emotion/memoize/-/memoize-0.8.1.tgz#c1ddb040429c6d21d38cc945fe75c818cfb68e17" integrity sha512-W2P2c/VRW1/1tLox0mVUalvnWXxavmv/Oum2aPsRcoDJuob75FC3Y8FbpfLwUegRcxINtGUMPq0tFCvYNTBXNA== @@ -1800,7 +1783,7 @@ "@emotion/weak-memoize" "^0.3.1" hoist-non-react-statics "^3.3.1" -"@emotion/serialize@^1.1.1", "@emotion/serialize@^1.1.2": +"@emotion/serialize@^1.1.2": version "1.1.2" resolved "https://registry.yarnpkg.com/@emotion/serialize/-/serialize-1.1.2.tgz#017a6e4c9b8a803bd576ff3d52a0ea6fa5a62b51" integrity sha512-zR6a/fkFP4EAcCMQtLOhIgpprZOwNmCldtpaISpvz348+DP4Mz8ZoKaGGCQpbzepNIUWbq4w6hNZkwDyKoS+HA== @@ -1828,29 +1811,17 @@ "@emotion/use-insertion-effect-with-fallbacks" "^1.0.1" "@emotion/utils" "^1.2.1" -"@emotion/styled@^11.7.0": - version "11.10.6" - resolved "https://registry.yarnpkg.com/@emotion/styled/-/styled-11.10.6.tgz#d886afdc51ef4d66c787ebde848f3cc8b117ebba" - integrity sha512-OXtBzOmDSJo5Q0AFemHCfl+bUueT8BIcPSxu0EGTpGk6DmI5dnhSzQANm1e1ze0YZL7TDyAyy6s/b/zmGOS3Og== - dependencies: - "@babel/runtime" "^7.18.3" - "@emotion/babel-plugin" "^11.10.6" - "@emotion/is-prop-valid" "^1.2.0" - "@emotion/serialize" "^1.1.1" - "@emotion/use-insertion-effect-with-fallbacks" "^1.0.0" - "@emotion/utils" "^1.2.0" - "@emotion/unitless@^0.8.1": version "0.8.1" resolved "https://registry.yarnpkg.com/@emotion/unitless/-/unitless-0.8.1.tgz#182b5a4704ef8ad91bde93f7a860a88fd92c79a3" integrity sha512-KOEGMu6dmJZtpadb476IsZBclKvILjopjUii3V+7MnXIQCYh8W3NgNcgwo21n9LXZX6EDIKvqfjYxXebDwxKmQ== -"@emotion/use-insertion-effect-with-fallbacks@^1.0.0", "@emotion/use-insertion-effect-with-fallbacks@^1.0.1": +"@emotion/use-insertion-effect-with-fallbacks@^1.0.1": version "1.0.1" resolved "https://registry.yarnpkg.com/@emotion/use-insertion-effect-with-fallbacks/-/use-insertion-effect-with-fallbacks-1.0.1.tgz#08de79f54eb3406f9daaf77c76e35313da963963" integrity sha512-jT/qyKZ9rzLErtrjGgdkMBn2OP8wl0G3sQlBb3YPryvKHsjvINUhVaPFfP+fpBcOkmrVOVEEHQFJ7nbj2TH2gw== -"@emotion/utils@^1.2.0", "@emotion/utils@^1.2.1": +"@emotion/utils@^1.2.1": version "1.2.1" resolved "https://registry.yarnpkg.com/@emotion/utils/-/utils-1.2.1.tgz#bbab58465738d31ae4cb3dbb6fc00a5991f755e4" integrity sha512-Y2tGf3I+XVnajdItskUCn6LX+VUDmP6lTL4fcqsXAv43dnlbZiuW4MWQW38rW/BVWSE7Q/7+XQocmpnRYILUmg== @@ -1942,11 +1913,6 @@ resolved "https://registry.yarnpkg.com/@hookform/resolvers/-/resolvers-2.9.11.tgz#9ce96e7746625a89239f68ca57c4f654264c17ef" integrity sha512-bA3aZ79UgcHj7tFV7RlgThzwSSHZgvfbt2wprldRkYBcMopdMvHyO17Wwp/twcJasNFischFfS7oz8Katz8DdQ== -"@hookform/resolvers@^3.1.0": - version "3.1.0" - resolved "https://registry.yarnpkg.com/@hookform/resolvers/-/resolvers-3.1.0.tgz#ff83ef4aa6078173201da131ceea4c3583b67034" - integrity sha512-z0A8K+Nxq+f83Whm/ajlwE6VtQlp/yPHZnXw7XWVPIGm1Vx0QV8KThU3BpbBRfAZ7/dYqCKKBNnQh85BkmBKkA== - "@humanwhocodes/config-array@^0.11.10": version "0.11.10" resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.11.10.tgz#5a3ffe32cc9306365fb3fd572596cd602d5e12d2" @@ -2691,20 +2657,6 @@ call-me-maybe "^1.0.1" glob-to-regexp "^0.3.0" -"@mui/base@5.0.0-beta.0": - version "5.0.0-beta.0" - resolved "https://registry.yarnpkg.com/@mui/base/-/base-5.0.0-beta.0.tgz#4145f8a700e04d68d703da70d3c26c62d278f4e5" - integrity sha512-ap+juKvt8R8n3cBqd/pGtZydQ4v2I/hgJKnvJRGjpSh3RvsvnDHO4rXov8MHQlH6VqpOekwgilFLGxMZjNTucA== - dependencies: - "@babel/runtime" "^7.21.0" - "@emotion/is-prop-valid" "^1.2.0" - "@mui/types" "^7.2.4" - "@mui/utils" "^5.12.3" - "@popperjs/core" "^2.11.7" - clsx "^1.2.1" - prop-types "^15.8.1" - react-is "^18.2.0" - "@mui/base@5.0.0-beta.4": version "5.0.0-beta.4" resolved "https://registry.yarnpkg.com/@mui/base/-/base-5.0.0-beta.4.tgz#e3f4f4a056b88ab357194a245e223177ce35e0b0" @@ -2733,11 +2685,6 @@ prop-types "^15.8.1" react-is "^18.2.0" -"@mui/core-downloads-tracker@^5.13.0": - version "5.13.7" - resolved "https://registry.yarnpkg.com/@mui/core-downloads-tracker/-/core-downloads-tracker-5.13.7.tgz#f4d9af5fe113b80b98b2cb158263d7b8f77e61c7" - integrity sha512-/suIo4WoeL/OyO3KUsFVpdOmKiSAr6NpWXmQ4WLSxwKrTiha1FJxM6vwAki5W/5kR9WnVLw5E8JC4oHHsutT8w== - "@mui/core-downloads-tracker@^5.13.4": version "5.13.4" resolved "https://registry.yarnpkg.com/@mui/core-downloads-tracker/-/core-downloads-tracker-5.13.4.tgz#7e4b491d8081b6d45ae51556d82cb16b31315a19" @@ -2750,13 +2697,6 @@ dependencies: "@babel/runtime" "^7.21.0" -"@mui/icons-material@^5.11.11": - version "5.11.11" - resolved "https://registry.yarnpkg.com/@mui/icons-material/-/icons-material-5.11.11.tgz#d4e01bd405b0dac779f5e060586277f91f3acb6e" - integrity sha512-Eell3ADmQVE8HOpt/LZ3zIma8JSvPh3XgnhwZLT0k5HRqZcd6F/QDHc7xsWtgz09t+UEFvOYJXjtrwKmLdwwpw== - dependencies: - "@babel/runtime" "^7.21.0" - "@mui/lab@^5.0.0-alpha.72": version "5.0.0-alpha.134" resolved "https://registry.yarnpkg.com/@mui/lab/-/lab-5.0.0-alpha.134.tgz#e48c108fce91fbb89446dcf86ca35e7e761bc078" @@ -2789,25 +2729,7 @@ react-is "^18.2.0" react-transition-group "^4.4.5" -"@mui/material@^5.11.15": - version "5.13.0" - resolved "https://registry.yarnpkg.com/@mui/material/-/material-5.13.0.tgz#23c63a9d117dca22b81bb82db6e9c4d761c8fbf1" - integrity sha512-ckS+9tCpAzpdJdaTF+btF0b6mF9wbXg/EVKtnoAWYi0UKXoXBAVvEUMNpLGA5xdpCdf+A6fPbVUEHs9TsfU+Yw== - dependencies: - "@babel/runtime" "^7.21.0" - "@mui/base" "5.0.0-beta.0" - "@mui/core-downloads-tracker" "^5.13.0" - "@mui/system" "^5.12.3" - "@mui/types" "^7.2.4" - "@mui/utils" "^5.12.3" - "@types/react-transition-group" "^4.4.6" - clsx "^1.2.1" - csstype "^3.1.2" - prop-types "^15.8.1" - react-is "^18.2.0" - react-transition-group "^4.4.5" - -"@mui/private-theming@^5.13.1", "@mui/private-theming@^5.13.7": +"@mui/private-theming@^5.13.1": version "5.13.7" resolved "https://registry.yarnpkg.com/@mui/private-theming/-/private-theming-5.13.7.tgz#2f8ef5da066f3c6c6423bd4260d003a28d10b099" integrity sha512-qbSr+udcij5F9dKhGX7fEdx2drXchq7htLNr2Qg2Ma+WJ6q0ERlEqGSBiPiVDJkptcjeVL4DGmcf1wl5+vD4EA== @@ -2863,20 +2785,6 @@ csstype "^3.1.2" prop-types "^15.8.1" -"@mui/system@^5.11.15", "@mui/system@^5.12.3": - version "5.13.7" - resolved "https://registry.yarnpkg.com/@mui/system/-/system-5.13.7.tgz#b02e6284bbaab4201b142546ebbb2012ec0fa63d" - integrity sha512-7R2KdI6vr8KtnauEfg9e9xQmPk6Gg/1vGNiALYyhSI+cYztxN6WmlSqGX4bjWn/Sygp1TUE1jhFEgs7MWruhkQ== - dependencies: - "@babel/runtime" "^7.22.5" - "@mui/private-theming" "^5.13.7" - "@mui/styled-engine" "^5.13.2" - "@mui/types" "^7.2.4" - "@mui/utils" "^5.13.7" - clsx "^1.2.1" - csstype "^3.1.2" - prop-types "^15.8.1" - "@mui/types@^7.2.4": version "7.2.4" resolved "https://registry.yarnpkg.com/@mui/types/-/types-7.2.4.tgz#b6fade19323b754c5c6de679a38f068fd50b9328" @@ -2893,7 +2801,7 @@ prop-types "^15.8.1" react-is "^18.2.0" -"@mui/utils@^5.12.3", "@mui/utils@^5.13.7": +"@mui/utils@^5.13.7": version "5.13.7" resolved "https://registry.yarnpkg.com/@mui/utils/-/utils-5.13.7.tgz#7e6a8336e05eb2642667a5c02eb605351e27ec20" integrity sha512-/3BLptG/q0u36eYED7Nhf4fKXmcKb6LjjT7ZMwhZIZSdSxVqDqSTmATW3a56n3KEPQUXCU9TpxAfCBQhs6brVA== @@ -3211,9 +3119,6 @@ dependencies: nx "15.9.4" -"@nymproject/extension-storage@file:./nym-browser-extension/storage/pkg": - version "0.1.0" - "@nymproject/nym-validator-client@^0.18.0": version "0.18.0" resolved "https://registry.yarnpkg.com/@nymproject/nym-validator-client/-/nym-validator-client-0.18.0.tgz#4dd72bafdf6c72b603242f32c0bb9a1f9e475b98" @@ -3386,7 +3291,7 @@ schema-utils "^3.0.0" source-map "^0.7.3" -"@popperjs/core@^2.11.7", "@popperjs/core@^2.11.8": +"@popperjs/core@^2.11.8": version "2.11.8" resolved "https://registry.yarnpkg.com/@popperjs/core/-/core-2.11.8.tgz#6b79032e760a0899cd4204710beede972a3a185f" integrity sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A== @@ -3449,11 +3354,6 @@ resolved "https://registry.yarnpkg.com/@remix-run/router/-/router-1.7.0.tgz#550a8d5760b78efc5d60f62b5829b0f74c1fde81" integrity sha512-Eu1V3kz3mV0wUpVTiFHuaT8UD1gj/0VnoFHQYX35xlslQUpe8CuYoKFn9d4WZFHm3yDywz6ALZuGdnUPKrNeAw== -"@remix-run/router@1.7.1": - version "1.7.1" - resolved "https://registry.yarnpkg.com/@remix-run/router/-/router-1.7.1.tgz#fea7ac35ae4014637c130011f59428f618730498" - integrity sha512-bgVQM4ZJ2u2CM8k1ey70o1ePFXsEzYVZoWghh6WjM8p59jQ7HxzbHW4SbnWFG7V9ig9chLawQxDTZ3xzOF8MkQ== - "@rollup/plugin-commonjs@^24.0.1": version "24.1.0" resolved "https://registry.yarnpkg.com/@rollup/plugin-commonjs/-/plugin-commonjs-24.1.0.tgz#79e54bd83bb64396761431eee6c44152ef322100" @@ -4548,7 +4448,7 @@ react-docgen-typescript "^2.1.1" tslib "^2.0.0" -"@storybook/react@^6.5.15", "@storybook/react@^6.5.16": +"@storybook/react@^6.5.15": version "6.5.16" resolved "https://registry.yarnpkg.com/@storybook/react/-/react-6.5.16.tgz#f7b82ba87f5bb73b4e4e83cce298a98710a88398" integrity sha512-cBtNlOzf/MySpNLBK22lJ8wFU22HnfTB2xJyBk7W7Zi71Lm7Uxkhv1Pz8HdiQndJ0SlsAAQOWjQYsSZsGkZIaA== @@ -4915,20 +4815,6 @@ lz-string "^1.5.0" pretty-format "^27.0.2" -"@testing-library/dom@^9.0.0": - version "9.2.0" - resolved "https://registry.yarnpkg.com/@testing-library/dom/-/dom-9.2.0.tgz#0e1f45e956f2a16f471559c06edd8827c4832f04" - integrity sha512-xTEnpUKiV/bMyEsE5bT4oYA0x0Z/colMtxzUY8bKyPXBNLn/e0V4ZjBZkEhms0xE4pv9QsPfSRu9AWS4y5wGvA== - dependencies: - "@babel/code-frame" "^7.10.4" - "@babel/runtime" "^7.12.5" - "@types/aria-query" "^5.0.1" - aria-query "^5.0.0" - chalk "^4.1.0" - dom-accessibility-api "^0.5.9" - lz-string "^1.5.0" - pretty-format "^27.0.2" - "@testing-library/jest-dom@^5.14.1": version "5.16.5" resolved "https://registry.yarnpkg.com/@testing-library/jest-dom/-/jest-dom-5.16.5.tgz#3912846af19a29b2dbf32a6ae9c31ef52580074e" @@ -4953,15 +4839,6 @@ "@testing-library/dom" "^8.0.0" "@types/react-dom" "<18.0.0" -"@testing-library/react@^14.0.0": - version "14.0.0" - resolved "https://registry.yarnpkg.com/@testing-library/react/-/react-14.0.0.tgz#59030392a6792450b9ab8e67aea5f3cc18d6347c" - integrity sha512-S04gSNJbYE30TlIMLTzv6QCTzt9AqIF5y6s6SzVFILNcNvbV/jU96GeiTPillGQo+Ny64M/5PV7klNYYgv5Dfg== - dependencies: - "@babel/runtime" "^7.12.5" - "@testing-library/dom" "^9.0.0" - "@types/react-dom" "^18.0.0" - "@testing-library/user-event@^13.2.1": version "13.5.0" resolved "https://registry.yarnpkg.com/@testing-library/user-event/-/user-event-13.5.0.tgz#69d77007f1e124d55314a2b73fd204b333b13295" @@ -5104,11 +4981,6 @@ dependencies: "@types/node" "*" -"@types/crypto-js@4.1.1": - version "4.1.1" - resolved "https://registry.yarnpkg.com/@types/crypto-js/-/crypto-js-4.1.1.tgz#602859584cecc91894eb23a4892f38cfa927890d" - integrity sha512-BG7fQKZ689HIoc5h+6D2Dgq1fABRa0RbBWKBd9SP/MVRVXROflpm5fhwyATX5duFmbStzyzyycPB8qUYKDH3NA== - "@types/d3-array@^3.0.3": version "3.0.5" resolved "https://registry.yarnpkg.com/@types/d3-array/-/d3-array-3.0.5.tgz#857c1afffd3f51319bbc5b301956aca68acaa7b8" @@ -5475,11 +5347,6 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-16.18.37.tgz#a1f8728e4dc30163deb41e9b7aba65d0c2d4eda1" integrity sha512-ql+4dw4PlPFBP495k8JzUX/oMNRI2Ei4PrMHgj8oT4VhGlYUzF4EYr0qk2fW+XBVGIrq8Zzk13m4cvyXZuv4pA== -"@types/node@^18.16.1": - version "18.16.7" - resolved "https://registry.yarnpkg.com/@types/node/-/node-18.16.7.tgz#86d0ba2541f808cb78d4dc5d3e40242a349d6db8" - integrity sha512-MFg7ua/bRtnA1hYE3pVyWxGd/r7aMqjNOdHvlSsXV3n8iaeGKkOaPzpJh6/ovf4bEXWcojkeMJpTsq3mzXW4IQ== - "@types/normalize-package-data@^2.4.0": version "2.4.1" resolved "https://registry.yarnpkg.com/@types/normalize-package-data/-/normalize-package-data-2.4.1.tgz#d3357479a0fdfdd5907fe67e17e0a85c906e1301" @@ -5539,13 +5406,6 @@ dependencies: "@types/react" "^17" -"@types/react-dom@^18.0.0": - version "18.2.4" - resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-18.2.4.tgz#13f25bfbf4e404d26f62ac6e406591451acba9e0" - integrity sha512-G2mHoTMTL4yoydITgOGwWdWMVd8sNgyEP85xVmMKAPUBwQWm9wBPQUmvbeF4V3WBY1P7mmL4BkjQ0SqUpf1snw== - dependencies: - "@types/react" "*" - "@types/react-dom@^18.0.10": version "18.2.6" resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-18.2.6.tgz#ad621fa71a8db29af7c31b41b2ea3d8a6f4144d1" @@ -6133,11 +5993,6 @@ resolved "https://registry.yarnpkg.com/@webpack-cli/configtest/-/configtest-1.2.0.tgz#7b20ce1c12533912c3b217ea68262365fa29a6f5" integrity sha512-4FB8Tj6xyVkyqjj1OaTqCjXYULB9FMkqQ8yGrZjRDrYh0nOE+7Lhs45WioWQQMV+ceFlE368Ukhe6xdvJM9Egg== -"@webpack-cli/configtest@^2.0.1": - version "2.0.1" - resolved "https://registry.yarnpkg.com/@webpack-cli/configtest/-/configtest-2.0.1.tgz#a69720f6c9bad6aef54a8fa6ba9c3533e7ef4c7f" - integrity sha512-njsdJXJSiS2iNbQVS0eT8A/KPnmyH4pv1APj2K0d1wrZcBLw+yppxOy4CGqa0OxDJkzfL/XELDhD8rocnIwB5A== - "@webpack-cli/info@^1.5.0": version "1.5.0" resolved "https://registry.yarnpkg.com/@webpack-cli/info/-/info-1.5.0.tgz#6c78c13c5874852d6e2dd17f08a41f3fe4c261b1" @@ -6145,21 +6000,11 @@ dependencies: envinfo "^7.7.3" -"@webpack-cli/info@^2.0.1": - version "2.0.1" - resolved "https://registry.yarnpkg.com/@webpack-cli/info/-/info-2.0.1.tgz#eed745799c910d20081e06e5177c2b2569f166c0" - integrity sha512-fE1UEWTwsAxRhrJNikE7v4EotYflkEhBL7EbajfkPlf6E37/2QshOy/D48Mw8G5XMFlQtS6YV42vtbG9zBpIQA== - "@webpack-cli/serve@^1.7.0": version "1.7.0" resolved "https://registry.yarnpkg.com/@webpack-cli/serve/-/serve-1.7.0.tgz#e1993689ac42d2b16e9194376cfb6753f6254db1" integrity sha512-oxnCNGj88fL+xzV+dacXs44HcDwf1ovs3AuEzvP7mqXw7fQntqIhQ1BRmynh4qEKQSSSRSWVyXRjmTbZIX9V2Q== -"@webpack-cli/serve@^2.0.2": - version "2.0.2" - resolved "https://registry.yarnpkg.com/@webpack-cli/serve/-/serve-2.0.2.tgz#10aa290e44a182c02e173a89452781b1acbc86d9" - integrity sha512-S9h3GmOmzUseyeFW3tYNnWS7gNUuwxZ3mmMq0JyW78Vx1SGKPSkt5bT4pB0rUnVfHjP0EL9gW2bOzmtiTfQt0A== - "@xtuc/ieee754@^1.2.0": version "1.2.0" resolved "https://registry.yarnpkg.com/@xtuc/ieee754/-/ieee754-1.2.0.tgz#eef014a3145ae477a1cbc00cd1e552336dceb790" @@ -8079,11 +7924,6 @@ commander@2, commander@^2.19.0, commander@^2.20.0: resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33" integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ== -commander@^10.0.1: - version "10.0.1" - resolved "https://registry.yarnpkg.com/commander/-/commander-10.0.1.tgz#881ee46b4f77d1c1dccc5823433aa39b022cbe06" - integrity sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug== - commander@^4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/commander/-/commander-4.1.1.tgz#9fd602bd936294e9e9ef46a3f4d6964044b18068" @@ -8351,18 +8191,6 @@ copy-descriptor@^0.1.0: resolved "https://registry.yarnpkg.com/copy-descriptor/-/copy-descriptor-0.1.1.tgz#676f6eb3c39997c2ee1ac3a924fd6124748f578d" integrity sha512-XgZ0pFcakEUlbwQEVNg3+QAis1FyTL3Qel9FYy8pSkQqoG3PNoT0bOCQtOXcOkur21r2Eq2kI+IE+gsmAEVlYw== -copy-webpack-plugin@^11.0.0: - version "11.0.0" - resolved "https://registry.yarnpkg.com/copy-webpack-plugin/-/copy-webpack-plugin-11.0.0.tgz#96d4dbdb5f73d02dd72d0528d1958721ab72e04a" - integrity sha512-fX2MWpamkW0hZxMEg0+mYnA40LTosOSa5TqZ9GYIBzyJa9C3QUaMPSE2xAi/buNr8u89SfD9wHSQVBzrRa/SOQ== - dependencies: - fast-glob "^3.2.11" - glob-parent "^6.0.1" - globby "^13.1.1" - normalize-path "^3.0.0" - schema-utils "^4.0.0" - serialize-javascript "^6.0.0" - core-js-compat@^3.30.1, core-js-compat@^3.30.2, core-js-compat@^3.8.1: version "3.31.0" resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.31.0.tgz#4030847c0766cc0e803dcdfb30055d7ef2064bf1" @@ -8532,11 +8360,6 @@ crypto-browserify@^3.11.0: randombytes "^2.0.0" randomfill "^1.0.3" -crypto-js@^4.1.1: - version "4.1.1" - resolved "https://registry.yarnpkg.com/crypto-js/-/crypto-js-4.1.1.tgz#9e485bcf03521041bd85844786b83fb7619736cf" - integrity sha512-o2JlM7ydqd3Qk9CA0L4NL6mTzU2sdx96a+oOfPu8Mkl/PK51vSyoi8/rQ8NknZtk44vq15lmhAj9CIAGwgeWKw== - crypto-random-string@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/crypto-random-string/-/crypto-random-string-2.0.0.tgz#ef2a7a966ec11083388369baa02ebead229b30d5" @@ -9422,13 +9245,6 @@ dotenv-webpack@^7.0.3: dependencies: dotenv-defaults "^2.0.2" -dotenv-webpack@^8.0.1: - version "8.0.1" - resolved "https://registry.yarnpkg.com/dotenv-webpack/-/dotenv-webpack-8.0.1.tgz#6656550460a8076fab20e5ac2eac867e72478645" - integrity sha512-CdrgfhZOnx4uB18SgaoP9XHRN2v48BbjuXQsZY5ixs5A8579NxQkmMxRtI7aTwSiSQcM2ao12Fdu+L3ZS3bG4w== - dependencies: - dotenv-defaults "^2.0.2" - dotenv@^16.0.3: version "16.3.1" resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-16.3.1.tgz#369034de7d7e5b120972693352a3bf112172cc3e" @@ -10491,17 +10307,6 @@ fast-glob@^2.2.6: merge2 "^1.2.3" micromatch "^3.1.10" -fast-glob@^3.2.11, fast-glob@^3.3.0: - version "3.3.0" - resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.3.0.tgz#7c40cb491e1e2ed5664749e87bfb516dbe8727c0" - integrity sha512-ChDuvbOypPuNjO8yIDf36x7BlZX1smcUMTTcyoIjycexOxd6DFsKsg21qVBzEmr3G7fUKIRy2/psii+CIUt7FA== - dependencies: - "@nodelib/fs.stat" "^2.0.2" - "@nodelib/fs.walk" "^1.2.3" - glob-parent "^5.1.2" - merge2 "^1.3.0" - micromatch "^4.0.4" - fast-glob@^3.2.9: version "3.2.12" resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.2.12.tgz#7f39ec99c2e6ab030337142da9e0c18f37afae80" @@ -11200,7 +11005,7 @@ glob-parent@^3.1.0: is-glob "^3.1.0" path-dirname "^1.0.0" -glob-parent@^6.0.1, glob-parent@^6.0.2: +glob-parent@^6.0.2: version "6.0.2" resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-6.0.2.tgz#6d237d99083950c79290f24c7642a3de9a28f9e3" integrity sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A== @@ -11331,17 +11136,6 @@ globby@11.1.0, globby@^11.0.1, globby@^11.0.2, globby@^11.1.0: merge2 "^1.4.1" slash "^3.0.0" -globby@^13.1.1: - version "13.2.2" - resolved "https://registry.yarnpkg.com/globby/-/globby-13.2.2.tgz#63b90b1bf68619c2135475cbd4e71e66aa090592" - integrity sha512-Y1zNGV+pzQdh7H39l9zgB4PJqjRNqydvdYCDG4HFXM4XuvSaQQlEc91IU1yALL8gUTDomgBAfz3XJdmUS+oo0w== - dependencies: - dir-glob "^3.0.1" - fast-glob "^3.3.0" - ignore "^5.2.4" - merge2 "^1.4.1" - slash "^4.0.0" - globby@^6.1.0: version "6.1.0" resolved "https://registry.yarnpkg.com/globby/-/globby-6.1.0.tgz#f5a6d70e8395e21c858fb0489d64df02424d506c" @@ -11945,7 +11739,7 @@ ignore@^4.0.3, ignore@^4.0.6: resolved "https://registry.yarnpkg.com/ignore/-/ignore-4.0.6.tgz#750e3db5862087b4737ebac8207ffd1ef27b25fc" integrity sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg== -ignore@^5.0.4, ignore@^5.2.0, ignore@^5.2.4: +ignore@^5.0.4, ignore@^5.2.0: version "5.2.4" resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.2.4.tgz#a291c0c6178ff1b960befe47fcdec301674a6324" integrity sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ== @@ -12105,11 +11899,6 @@ interpret@^2.2.0: resolved "https://registry.yarnpkg.com/interpret/-/interpret-2.2.0.tgz#1a78a0b5965c40a5416d007ad6f50ad27c417df9" integrity sha512-Ju0Bz/cEia55xDwUWEa8+olFpCiQoypjnQySseKtmjNrnps3P+xfpUmGr90T7yjlVJmOtybRvPXhKMbHr+fWnw== -interpret@^3.1.1: - version "3.1.1" - resolved "https://registry.yarnpkg.com/interpret/-/interpret-3.1.1.tgz#5be0ceed67ca79c6c4bc5cf0d7ee843dcea110c4" - integrity sha512-6xwYfHbajpoF0xLW+iwLkhwgvLoZDfjYfoFNu8ftMoXINzwuymNLd9u/KmwtdT2GbR+/Cz66otEGEVVUHX9QLQ== - ip@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/ip/-/ip-2.0.0.tgz#4cf4ab182fee2314c75ede1276f8c80b479936da" @@ -12170,7 +11959,7 @@ is-alphanumerical@^2.0.0: is-alphabetical "^2.0.0" is-decimal "^2.0.0" -is-arguments@^1.0.4, is-arguments@^1.1.1: +is-arguments@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/is-arguments/-/is-arguments-1.1.1.tgz#15b3f88fda01f2a97fec84ca761a560f123efa9b" integrity sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA== @@ -12361,13 +12150,6 @@ is-generator-fn@^2.0.0: resolved "https://registry.yarnpkg.com/is-generator-fn/-/is-generator-fn-2.1.0.tgz#7d140adc389aaf3011a8f2a2a4cfa6faadffb118" integrity sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ== -is-generator-function@^1.0.7: - version "1.0.10" - resolved "https://registry.yarnpkg.com/is-generator-function/-/is-generator-function-1.0.10.tgz#f1558baf1ac17e0deea7c0415c438351ff2b3c72" - integrity sha512-jsEjy9l3yiXEQ+PsXdmBwEPcOxaXWLspKdplFUVI9vq1iZgIekeC0L167qeu86czQaxed3q/Uzuw0swL0irL8A== - dependencies: - has-tostringtag "^1.0.0" - is-glob@^3.0.0, is-glob@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-3.1.0.tgz#7ba5ae24217804ac70707b96922567486cc3e84a" @@ -12594,7 +12376,7 @@ is-text-path@^1.0.1: dependencies: text-extensions "^1.0.0" -is-typed-array@^1.1.10, is-typed-array@^1.1.3, is-typed-array@^1.1.9: +is-typed-array@^1.1.10, is-typed-array@^1.1.9: version "1.1.10" resolved "https://registry.yarnpkg.com/is-typed-array/-/is-typed-array-1.1.10.tgz#36a5b5cb4189b575d1a3e4b08536bfb485801e3f" integrity sha512-PJqgEHiWZvMpaFZ3uTc8kHPM4+4ADTlDniuQL7cU/UDA0Ql7F70yGfHph3cLNe+c9toaigv+DFzTJKhc2CtO6A== @@ -17577,11 +17359,6 @@ qrcode.react@^1.0.1: prop-types "^15.6.0" qr.js "0.0.0" -qrcode.react@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/qrcode.react/-/qrcode.react-3.1.0.tgz#5c91ddc0340f768316fbdb8fff2765134c2aecd8" - integrity sha512-oyF+Urr3oAMUG/OiOuONL3HXM+53wvuH3mtIWQrYmsXoAq0DkvZp2RYUWFSMFtbdOpuS++9v+WAkzNVkMlNW6Q== - qs@6.11.0: version "6.11.0" resolved "https://registry.yarnpkg.com/qs/-/qs-6.11.0.tgz#fd0d963446f7a65e1367e01abd85429453f0c37a" @@ -17729,13 +17506,6 @@ react-error-boundary@^3.1.3, react-error-boundary@^3.1.4: dependencies: "@babel/runtime" "^7.12.5" -react-error-boundary@^4.0.3: - version "4.0.4" - resolved "https://registry.yarnpkg.com/react-error-boundary/-/react-error-boundary-4.0.4.tgz#d2e84505b0a67cec7a6bf33b0146faadfe31597d" - integrity sha512-AbqMFx8bCsob8rCHZvJYQ42MQijK0/034RUvan9qrqyJCpazr8d9vKHrysbxcr6odoHLZvQEcYomFPoIqH9fow== - dependencies: - "@babel/runtime" "^7.12.5" - react-google-charts@^3.0.15: version "3.0.15" resolved "https://registry.yarnpkg.com/react-google-charts/-/react-google-charts-3.0.15.tgz#30759a470f48336e744fd383d054122b039a1ff2" @@ -17743,7 +17513,7 @@ react-google-charts@^3.0.15: dependencies: react-load-script "^0.0.6" -react-hook-form@^7.14.2, react-hook-form@^7.43.9: +react-hook-form@^7.14.2: version "7.45.1" resolved "https://registry.yarnpkg.com/react-hook-form/-/react-hook-form-7.45.1.tgz#e352c7f4dbc7540f0756abbb4dcfd1122fecc9bb" integrity sha512-6dWoFJwycbuFfw/iKMcl+RdAOAOHDiF11KWYhNDRN/OkUt+Di5qsZHwA0OwsVnu9y135gkHpTw9DJA+WzCeR9w== @@ -17818,11 +17588,6 @@ react-refresh-typescript@^2.0.2, react-refresh-typescript@^2.0.3: resolved "https://registry.yarnpkg.com/react-refresh-typescript/-/react-refresh-typescript-2.0.9.tgz#f8a86efcb34f8d717100230564b9b57477d74b10" integrity sha512-chAnOO4vpxm/3WkgOVmti+eN8yUtkJzeGkOigV6UA9eDFz12W34e/SsYe2H5+RwYJ3+sfSZkVbiXcG1chEBxlg== -react-refresh-typescript@^2.0.8: - version "2.0.8" - resolved "https://registry.yarnpkg.com/react-refresh-typescript/-/react-refresh-typescript-2.0.8.tgz#84d086f9da742d7de87ad15824301763bf8135ba" - integrity sha512-7zsaM8jIpUZ2hxMdTo9GNZRauTI11AD5YNNaNXVGb8mtYVwdSbc6IQV8YaHevSWYbx+ZpZispezdCfaJuTrh3w== - react-refresh@^0.10.0: version "0.10.0" resolved "https://registry.yarnpkg.com/react-refresh/-/react-refresh-0.10.0.tgz#2f536c9660c0b9b1d500684d9e52a65e7404f7e3" @@ -17833,11 +17598,6 @@ react-refresh@^0.11.0: resolved "https://registry.yarnpkg.com/react-refresh/-/react-refresh-0.11.0.tgz#77198b944733f0f1f1a90e791de4541f9f074046" integrity sha512-F27qZr8uUqwhWZboondsPx8tnC3Ct3SxZA3V5WyEvujRyyNv0VYPhoBg1gZ8/MV5tubQp76Trw8lTv9hzRBa+A== -react-refresh@^0.14.0: - version "0.14.0" - resolved "https://registry.yarnpkg.com/react-refresh/-/react-refresh-0.14.0.tgz#4e02825378a5f227079554d4284889354e5f553e" - integrity sha512-wViHqhAd8OHeLS/IRMJjTSDHF3U9eWi62F/MledQGPdJGDhodXJ9PBLNGr6WWL7qlH12Mt3TyTpbS+hGXMjCzQ== - react-resize-detector@^8.0.4: version "8.1.0" resolved "https://registry.yarnpkg.com/react-resize-detector/-/react-resize-detector-8.1.0.tgz#1c7817db8bc886e2dbd3fbe3b26ea8e56be0524a" @@ -17853,14 +17613,6 @@ react-router-dom@6, react-router-dom@^6.7.0: "@remix-run/router" "1.7.0" react-router "6.14.0" -react-router-dom@^6.9.0: - version "6.14.1" - resolved "https://registry.yarnpkg.com/react-router-dom/-/react-router-dom-6.14.1.tgz#0ad7ba7abdf75baa61169d49f096f0494907a36f" - integrity sha512-ssF6M5UkQjHK70fgukCJyjlda0Dgono2QGwqGvuk7D+EDGHdacEN3Yke2LTMjkrpHuFwBfDFsEjGVXBDmL+bWw== - dependencies: - "@remix-run/router" "1.7.1" - react-router "6.14.1" - react-router@6.14.0: version "6.14.0" resolved "https://registry.yarnpkg.com/react-router/-/react-router-6.14.0.tgz#1c3e8e922d934d43a253fd862c72c82167c0a7f1" @@ -17868,13 +17620,6 @@ react-router@6.14.0: dependencies: "@remix-run/router" "1.7.0" -react-router@6.14.1: - version "6.14.1" - resolved "https://registry.yarnpkg.com/react-router/-/react-router-6.14.1.tgz#5e82bcdabf21add859dc04b1859f91066b3a5810" - integrity sha512-U4PfgvG55LdvbQjg5Y9QRWyVxIdO1LlpYT7x+tMAxd9/vmiPuJhIwdxZuIQLN/9e3O4KFDHYfR9gzGeYMasW8g== - dependencies: - "@remix-run/router" "1.7.1" - react-simple-maps@^2.3.0: version "2.3.0" resolved "https://registry.yarnpkg.com/react-simple-maps/-/react-simple-maps-2.3.0.tgz#7fdb55120335a9b1ef5bf62e0dcb669511705f3e" @@ -18134,13 +17879,6 @@ rechoir@^0.7.0: dependencies: resolve "^1.9.0" -rechoir@^0.8.0: - version "0.8.0" - resolved "https://registry.yarnpkg.com/rechoir/-/rechoir-0.8.0.tgz#49f866e0d32146142da3ad8f0eff352b3215ff22" - integrity sha512-/vxpCXddiX8NGfGO/mTafwjq4aFa/71pvamip0++IQk3zG8cbCj0fifNPrjjF1XMXUne91jL9OoxmdykoEtifQ== - dependencies: - resolve "^1.20.0" - redent@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/redent/-/redent-1.0.0.tgz#cf916ab1fd5f1f16dfb20822dd6ec7f730c2afde" @@ -19086,11 +18824,6 @@ slash@^2.0.0: resolved "https://registry.yarnpkg.com/slash/-/slash-2.0.0.tgz#de552851a1759df3a8f206535442f5ec4ddeab44" integrity sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A== -slash@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/slash/-/slash-4.0.0.tgz#2422372176c4c6c5addb5e2ada885af984b396a7" - integrity sha512-3dOsAHXXUkQTpOYcoAxLIorMTp4gIQr5IW3iVb7A7lFIp0VHhnynm9izx6TssdrIcVIESAlVjtnO2K8bg+Coew== - slice-ansi@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-4.0.0.tgz#500e8dd0fd55b05815086255b3195adf2a45fe6b" @@ -19675,11 +19408,6 @@ stylehacks@^5.1.1: browserslist "^4.21.4" postcss-selector-parser "^6.0.4" -stylis@4.1.3: - version "4.1.3" - resolved "https://registry.yarnpkg.com/stylis/-/stylis-4.1.3.tgz#fd2fbe79f5fed17c55269e16ed8da14c84d069f7" - integrity sha512-GP6WDNWf+o403jrEp9c5jibKavrtLW+/qYGhFxFrG8maXhwTBI7gLLhiBb0o7uFccWN+EOS9aMO6cGHWAO07OA== - stylis@4.2.0: version "4.2.0" resolved "https://registry.yarnpkg.com/stylis/-/stylis-4.2.0.tgz#79daee0208964c8fe695a42fcffcac633a211a51" @@ -20855,17 +20583,6 @@ util@^0.11.0: dependencies: inherits "2.0.3" -util@^0.12.5: - version "0.12.5" - resolved "https://registry.yarnpkg.com/util/-/util-0.12.5.tgz#5f17a6059b73db61a875668781a1c2b136bd6fbc" - integrity sha512-kZf/K6hEIrWHI6XqOFUiiMa+79wE/D8Q+NCNAWclkyg3b4d2k7s0QGepNjiABc+aR3N1PAyHL7p6UcLY6LmrnA== - dependencies: - inherits "^2.0.3" - is-arguments "^1.0.4" - is-generator-function "^1.0.7" - is-typed-array "^1.1.3" - which-typed-array "^1.1.2" - utila@~0.4: version "0.4.0" resolved "https://registry.yarnpkg.com/utila/-/utila-0.4.0.tgz#8a16a05d445657a3aea5eecc5b12a4fa5379772c" @@ -21165,25 +20882,6 @@ webpack-cli@^4.8.0: rechoir "^0.7.0" webpack-merge "^5.7.3" -webpack-cli@^5.0.1: - version "5.0.2" - resolved "https://registry.yarnpkg.com/webpack-cli/-/webpack-cli-5.0.2.tgz#2954c10ecb61c5d4dad6f68ee2d77f051741946c" - integrity sha512-4y3W5Dawri5+8dXm3+diW6Mn1Ya+Dei6eEVAdIduAmYNLzv1koKVAqsfgrrc9P2mhrYHQphx5htnGkcNwtubyQ== - dependencies: - "@discoveryjs/json-ext" "^0.5.0" - "@webpack-cli/configtest" "^2.0.1" - "@webpack-cli/info" "^2.0.1" - "@webpack-cli/serve" "^2.0.2" - colorette "^2.0.14" - commander "^10.0.1" - cross-spawn "^7.0.3" - envinfo "^7.7.3" - fastest-levenshtein "^1.0.12" - import-local "^3.0.2" - interpret "^3.1.1" - rechoir "^0.8.0" - webpack-merge "^5.7.3" - webpack-dev-middleware@^3.7.3: version "3.7.3" resolved "https://registry.yarnpkg.com/webpack-dev-middleware/-/webpack-dev-middleware-3.7.3.tgz#0639372b143262e2b84ab95d3b91a7597061c2c5" @@ -21439,7 +21137,7 @@ which-collection@^1.0.1: is-weakmap "^2.0.1" is-weakset "^2.0.1" -which-typed-array@^1.1.2, which-typed-array@^1.1.9: +which-typed-array@^1.1.9: version "1.1.9" resolved "https://registry.yarnpkg.com/which-typed-array/-/which-typed-array-1.1.9.tgz#307cf898025848cf995e795e8423c7f337efbde6" integrity sha512-w9c4xkx6mPidwp7180ckYWfMmvxpjlZuIudNtDf4N/tTAUB8VJbX25qZoAsrtGuYNnGw3pa0AXgbGKRB8/EceA== @@ -21801,11 +21499,6 @@ yup@^1.2.0: toposort "^2.0.2" type-fest "^2.19.0" -zod@^3.21.4: - version "3.21.4" - resolved "https://registry.yarnpkg.com/zod/-/zod-3.21.4.tgz#10882231d992519f0a10b5dd58a38c9dabbb64db" - integrity sha512-m46AKbrzKVzOzs/DZgVnG5H55N1sv1M8qZU3A8RIKbs3mrACDNeIOeilDymVb2HdmP8uwshOCF4uJ8uM9rCqJw== - zwitch@^1.0.0: version "1.0.5" resolved "https://registry.yarnpkg.com/zwitch/-/zwitch-1.0.5.tgz#d11d7381ffed16b742f6af7b3f223d5cd9fe9920" From 026d52a2182cf265be98d135b95dafcff881886a Mon Sep 17 00:00:00 2001 From: pierre Date: Wed, 23 Aug 2023 16:46:07 +0200 Subject: [PATCH 314/361] print location in uppercase --- explorer/src/components/Gateways.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/explorer/src/components/Gateways.ts b/explorer/src/components/Gateways.ts index 39419257d4..4072a33db7 100644 --- a/explorer/src/components/Gateways.ts +++ b/explorer/src/components/Gateways.ts @@ -26,7 +26,7 @@ export function gatewayToGridRow(arrayOfGateways: GatewayResponse): GatewayRowTy id: gw.owner, owner: gw.owner, identity_key: gw.gateway.identity_key || '', - location: gw.location?.country_name || '', + location: gw.location?.country_name.toUpperCase() || '', bond: gw.pledge_amount.amount || 0, host: gw.gateway.host || '', version: gw.gateway.version || '', @@ -39,7 +39,7 @@ export function gatewayEnrichedToGridRow(gateway: GatewayBond, report: GatewayRe id: gateway.owner, owner: gateway.owner, identity_key: gateway.gateway.identity_key || '', - location: gateway.location?.country_name || '', + location: gateway.location?.country_name.toUpperCase() || '', bond: gateway.pledge_amount.amount || 0, host: gateway.gateway.host || '', version: gateway.gateway.version || '', From 3c9faff4ecd0ff99b83fc1a0a9f55601de29a383 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C4=99drzej=20Stuczy=C5=84ski?= Date: Wed, 23 Aug 2023 16:11:27 +0100 Subject: [PATCH 315/361] [hotfix]: don't assign invalid fields when crossing the JS boundary (#3805) * [hotfix]: don't assign invalid fields when crossing the JS boundary * eslint --- nym-wallet/src/components/Bonding/utils.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/nym-wallet/src/components/Bonding/utils.ts b/nym-wallet/src/components/Bonding/utils.ts index d3285215f6..6e7b05a651 100644 --- a/nym-wallet/src/components/Bonding/utils.ts +++ b/nym-wallet/src/components/Bonding/utils.ts @@ -4,12 +4,13 @@ import { toPercentFloatString } from '../../utils'; export function mixnodeToTauri(data: MixnodeData): MixNode { return { - ...data, mix_port: data.mixPort, http_api_port: data.httpApiPort, verloc_port: data.verlocPort, sphinx_key: data.sphinxKey, identity_key: data.identityKey, + version: data.version, + host: data.host, }; } @@ -25,7 +26,6 @@ export function costParamsToTauri(data: MixnodeAmount): MixNodeCostParams { export function gatewayToTauri(data: GatewayData): Gateway { return { - ...data, host: data.host, version: data.version, mix_port: data.mixPort, From 2f624d4f10712fcfcf8f92526f043d4d5e0331c8 Mon Sep 17 00:00:00 2001 From: pierre Date: Wed, 23 Aug 2023 17:20:36 +0200 Subject: [PATCH 316/361] doc(nc-android): add notes for AAB build --- nym-connect/native/android/README.md | 10 ++++++++-- nym-connect/native/android/app/build.gradle.kts | 12 ++++++++++++ 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/nym-connect/native/android/README.md b/nym-connect/native/android/README.md index b2a92f2454..c0092aedd5 100644 --- a/nym-connect/native/android/README.md +++ b/nym-connect/native/android/README.md @@ -37,7 +37,7 @@ RELEASE=true ./build-android.sh aarch64 x86_64 The shared library for each ABIs will be automatically moved into `app/src/main/jniLibs/*` directories. -### APK build (from terminal) +### APK/AAB build (from terminal) This project is setup with multiple [product flavors](app/build.gradle) to build for specific architectures.\ @@ -48,12 +48,18 @@ Supported archs: - x86_64 - x86 -For example to build for _arm64_ in _release_ mode use +For example to build an APK for _arm64_ in _release_ mode use ```shell ./gradlew :app:assembleArm64Release ``` +Instead of building an APK, to build an app bundle (`.aab`) run + +```shell +./gradlew :app:bundleArm64Release +``` + **NOTE**: you likely want _arch64_ (`arm64` & `x86_64`) for APK distribution To build a _universal_ APK which includes all ABI use diff --git a/nym-connect/native/android/app/build.gradle.kts b/nym-connect/native/android/app/build.gradle.kts index decadfc6dc..b1c912729f 100644 --- a/nym-connect/native/android/app/build.gradle.kts +++ b/nym-connect/native/android/app/build.gradle.kts @@ -22,6 +22,18 @@ android { } } + bundle { + language { + enableSplit = true + } + density { + enableSplit = true + } + abi { + enableSplit = true + } + } + buildTypes { release { isMinifyEnabled = true From 776a9d508a6db865eea31bc66b5e431b350274c7 Mon Sep 17 00:00:00 2001 From: pierre Date: Wed, 23 Aug 2023 17:44:53 +0200 Subject: [PATCH 317/361] fix wrong helper text --- .../src/components/Bonding/forms/gatewayValidationSchema.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nym-wallet/src/components/Bonding/forms/gatewayValidationSchema.ts b/nym-wallet/src/components/Bonding/forms/gatewayValidationSchema.ts index b36ff2d095..6935b6e623 100644 --- a/nym-wallet/src/components/Bonding/forms/gatewayValidationSchema.ts +++ b/nym-wallet/src/components/Bonding/forms/gatewayValidationSchema.ts @@ -28,7 +28,7 @@ export const gatewayValidationSchema = Yup.object().shape({ location: Yup.string() .required('A location is required') - .test('valid-location', 'A valid version is required', (locationValueTest) => + .test('valid-location', 'A valid location is required', (locationValueTest) => locationValueTest ? validateLocation(locationValueTest) : false, ), From f1b9cf4d68a273970e08e605f46cb4b995358077 Mon Sep 17 00:00:00 2001 From: mfahampshire Date: Wed, 23 Aug 2023 20:56:43 +0200 Subject: [PATCH 318/361] update minibolt pp --- .../images/profile_picture/minibolt_pp.png | Bin 13877 -> 23061 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/documentation/dev-portal/src/images/profile_picture/minibolt_pp.png b/documentation/dev-portal/src/images/profile_picture/minibolt_pp.png index ed059e6055840dcd148f5760efc06b2eb5e331d9..455243cc221e6350d5575ccc8cd8c9461cc672f4 100644 GIT binary patch literal 23061 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 literal 13877 zcmV-5HpL{T8P z03<-{jo#49+uys(IcNU3_jNx24UhyHASv+_y889&#(npm@A>VUu-5Wpv-tYm={k2si~io&o;&+W=pG>U;=z7_;<7e;eS}xT+bTk6Z_~d%f174CpUpGW>0TFGH=L z%7CrBMfHMu2K4X0S*ZHW0Ps%WH-KN|$7_8id#*PWX!!=S;zxm#KZf9vJ=4kT8LK~O z06&tQ?7zp4|N48`vp({Zz@iUi^64&qGS{I@&v^F7k%E5%IQSC`F4?mm%=Y5PhJt_6 zsrC(}=Ic$tpCa}8ybbQg)ckn1M{h}h9|eBzEo{@?h*in<=+U
  • {J-bRK*gzt^>% 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 DYWw7D From f0a888d59c82f2a059c36ef65e238d03f469c7d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jon=20H=C3=A4ggblad?= Date: Thu, 24 Aug 2023 15:34:49 +0200 Subject: [PATCH 319/361] Add EXPLORER_API configurable url --- .../topology_control/geo_aware_provider.rs | 17 +++++++++++++++-- common/network-defaults/src/lib.rs | 10 ++++++++++ common/network-defaults/src/mainnet.rs | 4 ++++ common/network-defaults/src/var_names.rs | 1 + envs/mainnet.env | 1 + envs/sandbox.env | 3 ++- 6 files changed, 33 insertions(+), 3 deletions(-) diff --git a/common/client-core/src/client/topology_control/geo_aware_provider.rs b/common/client-core/src/client/topology_control/geo_aware_provider.rs index 268f6612e0..62085796c6 100644 --- a/common/client-core/src/client/topology_control/geo_aware_provider.rs +++ b/common/client-core/src/client/topology_control/geo_aware_provider.rs @@ -2,6 +2,7 @@ use std::{collections::HashMap, fmt}; use log::{debug, error, info}; use nym_explorer_api_requests::PrettyDetailedMixNodeBond; +use nym_network_defaults::var_names::EXPLORER_API; use nym_topology::{ nym_topology_from_detailed, provider_trait::{async_trait, TopologyProvider}, @@ -13,11 +14,23 @@ use serde::{Deserialize, Serialize}; use url::Url; const MIN_NODES_PER_LAYER: usize = 1; -const EXPLORER_API_MIXNODES_URL: &str = "https://explorer.nymtech.net/api/v1/mix-nodes"; // TODO: create a explorer-api-client async fn fetch_mixnodes_from_explorer_api() -> Option> { - reqwest::get(EXPLORER_API_MIXNODES_URL) + let explorer_api_url = std::env::var(EXPLORER_API).ok()?; + let explorer_api_url = Url::parse(&explorer_api_url) + .ok()? + .join("v1/mix-nodes") + .ok()?; + + debug!("Fetching: {}", explorer_api_url); + let client = reqwest::Client::builder() + .timeout(std::time::Duration::from_secs(5)) + .build() + .ok()?; + client + .get(explorer_api_url) + .send() .await .ok()? .json::>() diff --git a/common/network-defaults/src/lib.rs b/common/network-defaults/src/lib.rs index 8de0ed465c..1704608043 100644 --- a/common/network-defaults/src/lib.rs +++ b/common/network-defaults/src/lib.rs @@ -43,6 +43,7 @@ pub struct NymNetworkDetails { pub chain_details: ChainDetails, pub endpoints: Vec, pub contracts: NymContracts, + pub explorer_api: Option, } // 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>(mut self, endpoint: Option) -> Self { + self.explorer_api = endpoint.map(Into::into); + self + } } #[derive(Debug, Copy, Serialize, Deserialize, Clone, PartialEq, Eq)] diff --git a/common/network-defaults/src/mainnet.rs b/common/network-defaults/src/mainnet.rs index fbc25c4b00..bfb3f42531 100644 --- a/common/network-defaults/src/mainnet.rs +++ b/common/network-defaults/src/mainnet.rs @@ -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 { 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); } diff --git a/common/network-defaults/src/var_names.rs b/common/network-defaults/src/var_names.rs index 26b65b7f95..d082045a03 100644 --- a/common/network-defaults/src/var_names.rs +++ b/common/network-defaults/src/var_names.rs @@ -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"; diff --git a/envs/mainnet.env b/envs/mainnet.env index 4bf406bd64..9dc5b5272d 100644 --- a/envs/mainnet.env +++ b/envs/mainnet.env @@ -22,5 +22,6 @@ REWARDING_VALIDATOR_ADDRESS=n10yyd98e2tuwu0f7ypz9dy3hhjw7v772q6287gy STATISTICS_SERVICE_DOMAIN_ADDRESS="https://mainnet-stats.nymte.ch:8090" NYXD="https://rpc.nymtech.net" NYM_API="https://validator.nymtech.net/api/" +EXPLORER_API="https://explorer.nymtech.net/api/" DKG_TIME_CONFIGURATION="259200,300,300,60,60,1209600" diff --git a/envs/sandbox.env b/envs/sandbox.env index 95b0b4d49a..b4a66a70d1 100644 --- a/envs/sandbox.env +++ b/envs/sandbox.env @@ -23,4 +23,5 @@ EPHEMERA_CONTRACT_ADDRESS="nymt1gwk6muhmzeuxje7df7rjvqwl2vex0kj4t2hwuzmyx5k62kfu STATISTICS_SERVICE_DOMAIN_ADDRESS="http://0.0.0.0" NYXD="https://sandbox-validator1.nymtech.net" -NYM_API="https://sandbox-nym-api1.nymtech.net/api/" \ No newline at end of file +NYM_API="https://sandbox-nym-api1.nymtech.net/api/" +EXPLORER_API="https://sandbox-explorer.nymtech.net/api/" From 8a38c61065ba28014fa763b82b0b7dd7bb9c2177 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jon=20H=C3=A4ggblad?= Date: Thu, 24 Aug 2023 15:43:48 +0200 Subject: [PATCH 320/361] Fix wallet build --- nym-wallet/nym-wallet-types/src/network/qa.rs | 3 +++ nym-wallet/nym-wallet-types/src/network/sandbox.rs | 3 +++ 2 files changed, 6 insertions(+) diff --git a/nym-wallet/nym-wallet-types/src/network/qa.rs b/nym-wallet/nym-wallet-types/src/network/qa.rs index be282384f5..605cc63fdd 100644 --- a/nym-wallet/nym-wallet-types/src/network/qa.rs +++ b/nym-wallet/nym-wallet-types/src/network/qa.rs @@ -39,6 +39,8 @@ pub(crate) fn validators() -> Vec { )] } +pub(crate) const EXPLORER_API: &str = "https://qa-explorer.qa.nymte.ch/api/"; + pub(crate) fn network_details() -> nym_network_defaults::NymNetworkDetails { nym_network_defaults::NymNetworkDetails { network_name: NETWORK_NAME.into(), @@ -63,5 +65,6 @@ pub(crate) fn network_details() -> nym_network_defaults::NymNetworkDetails { ), name_service_contract_address: parse_optional_str(NAME_SERVICE_CONTRACT_ADDRESS), }, + explorer_api: parse_optional_str(EXPLORER_API), } } diff --git a/nym-wallet/nym-wallet-types/src/network/sandbox.rs b/nym-wallet/nym-wallet-types/src/network/sandbox.rs index 0499fa5420..ace2ff7663 100644 --- a/nym-wallet/nym-wallet-types/src/network/sandbox.rs +++ b/nym-wallet/nym-wallet-types/src/network/sandbox.rs @@ -35,6 +35,8 @@ pub(crate) fn validators() -> Vec { )] } +pub(crate) const EXPLORER_API: &str = "https://sandbox-explorer.nymtech.net/api/"; + pub(crate) fn network_details() -> nym_network_defaults::NymNetworkDetails { nym_network_defaults::NymNetworkDetails { network_name: NETWORK_NAME.into(), @@ -57,5 +59,6 @@ pub(crate) fn network_details() -> nym_network_defaults::NymNetworkDetails { service_provider_directory_contract_address: None, name_service_contract_address: None, }, + explorer_api: parse_optional_str(EXPLORER_API), } } From 6a327b0bd68ad7c94d3334ab78a6018874e5fb06 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jon=20H=C3=A4ggblad?= Date: Thu, 24 Aug 2023 16:01:47 +0200 Subject: [PATCH 321/361] No timeout for wasm --- Cargo.lock | 1 + clients/webassembly/Cargo.lock | 1 + common/client-core/Cargo.toml | 1 + .../topology_control/geo_aware_provider.rs | 17 +++++++++++++---- 4 files changed, 16 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 9024fa7ad6..98a64c657a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5891,6 +5891,7 @@ version = "1.1.15" dependencies = [ "async-trait", "base64 0.21.2", + "cfg-if", "dashmap 5.5.0", "dirs 4.0.0", "futures", diff --git a/clients/webassembly/Cargo.lock b/clients/webassembly/Cargo.lock index 8449b45ff6..529b241500 100644 --- a/clients/webassembly/Cargo.lock +++ b/clients/webassembly/Cargo.lock @@ -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", diff --git a/common/client-core/Cargo.toml b/common/client-core/Cargo.toml index bcb21899fe..56733b5ab8 100644 --- a/common/client-core/Cargo.toml +++ b/common/client-core/Cargo.toml @@ -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 } diff --git a/common/client-core/src/client/topology_control/geo_aware_provider.rs b/common/client-core/src/client/topology_control/geo_aware_provider.rs index 62085796c6..ece6e967a2 100644 --- a/common/client-core/src/client/topology_control/geo_aware_provider.rs +++ b/common/client-core/src/client/topology_control/geo_aware_provider.rs @@ -24,10 +24,19 @@ async fn fetch_mixnodes_from_explorer_api() -> Option Date: Thu, 24 Aug 2023 17:46:16 +0200 Subject: [PATCH 322/361] Upgrade ts-rs to 7.0.0 --- Cargo.lock | 10 +++++----- Cargo.toml | 1 + common/client-libs/validator-client/Cargo.toml | 2 +- .../mixnet-contract/Cargo.toml | 2 +- .../vesting-contract/Cargo.toml | 2 +- common/types/Cargo.toml | 2 +- explorer-api/explorer-api-requests/Cargo.toml | 2 +- nym-api/Cargo.toml | 2 +- nym-api/nym-api-requests/Cargo.toml | 2 +- nym-connect/desktop/src-tauri/Cargo.toml | 2 +- nym-wallet/Cargo.lock | 10 +++++----- nym-wallet/nym-wallet-types/Cargo.toml | 2 +- nym-wallet/src-tauri/Cargo.toml | 2 +- tools/ts-rs-cli/Cargo.toml | 2 +- 14 files changed, 22 insertions(+), 21 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 9024fa7ad6..078ad10a0f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -10562,9 +10562,9 @@ checksum = "3528ecfd12c466c6f163363caf2d02a71161dd5e1cc6ae7b34207ea2d42d81ed" [[package]] name = "ts-rs" -version = "6.2.1" +version = "7.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4added4070a4fdf9df03457206cd2e4b12417c8560a2954d91ffcbe60177a56a" +checksum = "e1ff1f8c90369bc172200013ac17ae86e7b5def580687df4e6127883454ff2b0" dependencies = [ "thiserror", "ts-rs-macros", @@ -10587,14 +10587,14 @@ dependencies = [ [[package]] name = "ts-rs-macros" -version = "6.2.0" +version = "7.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f807fdb3151fee75df7485b901a89624358cd07a67a8fb1a5831bf5a07681ff" +checksum = "a6f41cc0aeb7a4a55730188e147d3795a7349b501f8334697fd37629b896cdc2" dependencies = [ "Inflector", "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.28", "termcolor", ] diff --git a/Cargo.toml b/Cargo.toml index 696f1bd9f1..ac0178a41e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -150,6 +150,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" diff --git a/common/client-libs/validator-client/Cargo.toml b/common/client-libs/validator-client/Cargo.toml index f3d2716143..0ee5c8b3af 100644 --- a/common/client-libs/validator-client/Cargo.toml +++ b/common/client-libs/validator-client/Cargo.toml @@ -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" diff --git a/common/cosmwasm-smart-contracts/mixnet-contract/Cargo.toml b/common/cosmwasm-smart-contracts/mixnet-contract/Cargo.toml index 47397d7b88..e82be77e29 100644 --- a/common/cosmwasm-smart-contracts/mixnet-contract/Cargo.toml +++ b/common/cosmwasm-smart-contracts/mixnet-contract/Cargo.toml @@ -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" diff --git a/common/cosmwasm-smart-contracts/vesting-contract/Cargo.toml b/common/cosmwasm-smart-contracts/vesting-contract/Cargo.toml index f5fa35df5f..3fd7e8a377 100644 --- a/common/cosmwasm-smart-contracts/vesting-contract/Cargo.toml +++ b/common/cosmwasm-smart-contracts/vesting-contract/Cargo.toml @@ -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"] diff --git a/common/types/Cargo.toml b/common/types/Cargo.toml index 5515ba5248..4df4087661 100644 --- a/common/types/Cargo.toml +++ b/common/types/Cargo.toml @@ -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 } diff --git a/explorer-api/explorer-api-requests/Cargo.toml b/explorer-api/explorer-api-requests/Cargo.toml index 7d8dc2f803..d7139b9f78 100644 --- a/explorer-api/explorer-api-requests/Cargo.toml +++ b/explorer-api/explorer-api-requests/Cargo.toml @@ -9,4 +9,4 @@ nym-mixnet-contract-common = { path = "../../common/cosmwasm-smart-contracts/mix nym-api-requests = { path = "../../nym-api/nym-api-requests" } schemars = { version = "0.8", features = ["preserve_order"] } serde = { version = "1.0", features = ["derive"] } -ts-rs = { version = "6.1.2", optional = true } +ts-rs = { workspace = true, optional = true } diff --git a/nym-api/Cargo.toml b/nym-api/Cargo.toml index c9a0b8e8e4..506c0dda9a 100644 --- a/nym-api/Cargo.toml +++ b/nym-api/Cargo.toml @@ -47,7 +47,7 @@ tokio = { version = "1.24.1", features = [ tokio-stream = "0.1.11" url = { workspace = true } -ts-rs = {version = "6.1", optional = true} +ts-rs = { workspace = true, optional = true} anyhow = "1.0" getset = "0.1.1" diff --git a/nym-api/nym-api-requests/Cargo.toml b/nym-api/nym-api-requests/Cargo.toml index 37aded5efa..e890aa8e44 100644 --- a/nym-api/nym-api-requests/Cargo.toml +++ b/nym-api/nym-api-requests/Cargo.toml @@ -12,7 +12,7 @@ cosmwasm-std = { workspace = true, default-features = false } getset = "0.1.1" schemars = { version = "0.8", features = ["preserve_order"] } serde = { version = "1.0", features = ["derive"] } -ts-rs = { version = "6.1.2", optional = true } +ts-rs = { workspace = true, optional = true } nym-coconut-interface = { path = "../../common/coconut-interface" } nym-mixnet-contract-common = { path= "../../common/cosmwasm-smart-contracts/mixnet-contract" } diff --git a/nym-connect/desktop/src-tauri/Cargo.toml b/nym-connect/desktop/src-tauri/Cargo.toml index 1844688af9..630adca45b 100644 --- a/nym-connect/desktop/src-tauri/Cargo.toml +++ b/nym-connect/desktop/src-tauri/Cargo.toml @@ -61,7 +61,7 @@ nym-task = { path = "../../../common/task" } nym-validator-client = { path = "../../../common/client-libs/validator-client" } [dev-dependencies] -ts-rs = "6.1.2" +ts-rs = "7.0.0" tempfile = "3.3.0" [features] diff --git a/nym-wallet/Cargo.lock b/nym-wallet/Cargo.lock index fbe8b1d3f4..3cb32da98f 100644 --- a/nym-wallet/Cargo.lock +++ b/nym-wallet/Cargo.lock @@ -5864,9 +5864,9 @@ checksum = "3528ecfd12c466c6f163363caf2d02a71161dd5e1cc6ae7b34207ea2d42d81ed" [[package]] name = "ts-rs" -version = "6.2.1" +version = "7.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4added4070a4fdf9df03457206cd2e4b12417c8560a2954d91ffcbe60177a56a" +checksum = "e1ff1f8c90369bc172200013ac17ae86e7b5def580687df4e6127883454ff2b0" dependencies = [ "thiserror", "ts-rs-macros", @@ -5874,14 +5874,14 @@ dependencies = [ [[package]] name = "ts-rs-macros" -version = "6.2.0" +version = "7.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f807fdb3151fee75df7485b901a89624358cd07a67a8fb1a5831bf5a07681ff" +checksum = "a6f41cc0aeb7a4a55730188e147d3795a7349b501f8334697fd37629b896cdc2" dependencies = [ "Inflector", "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.28", "termcolor", ] diff --git a/nym-wallet/nym-wallet-types/Cargo.toml b/nym-wallet/nym-wallet-types/Cargo.toml index 670739ad5f..ca422a9832 100644 --- a/nym-wallet/nym-wallet-types/Cargo.toml +++ b/nym-wallet/nym-wallet-types/Cargo.toml @@ -9,7 +9,7 @@ hex-literal = "0.3.3" serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" strum = { version = "0.23", features = ["derive"] } -ts-rs = "6.1.2" +ts-rs = "7.0.0" cosmwasm-std = "1.3.0" cosmrs = "=0.14.0" diff --git a/nym-wallet/src-tauri/Cargo.toml b/nym-wallet/src-tauri/Cargo.toml index 2297a1ac90..d6b9c1392a 100644 --- a/nym-wallet/src-tauri/Cargo.toml +++ b/nym-wallet/src-tauri/Cargo.toml @@ -66,7 +66,7 @@ nym-store-cipher = { path = "../../common/store-cipher", features = ["json"] } nym-crypto = { path = "../../common/crypto", features = ["rand"] } rand_chacha = "0.2" tempfile = "3.3.0" -ts-rs = "6.1.2" +ts-rs = "7.0.0" [features] default = ["custom-protocol"] diff --git a/tools/ts-rs-cli/Cargo.toml b/tools/ts-rs-cli/Cargo.toml index 41f9ead98d..5e315a761c 100644 --- a/tools/ts-rs-cli/Cargo.toml +++ b/tools/ts-rs-cli/Cargo.toml @@ -7,7 +7,7 @@ edition = "2021" [dependencies] anyhow = "1" -ts-rs = "6.1.2" +ts-rs = { workspace = true } walkdir = "2" nym-validator-client = { path = "../../common/client-libs/validator-client", features = [ From 5d7fd66cfce1457213a8d93fb4ed8060e528a270 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jon=20H=C3=A4ggblad?= Date: Thu, 24 Aug 2023 17:13:49 +0200 Subject: [PATCH 323/361] rustfmt --- clients/webassembly/src/client/helpers.rs | 4 ++-- clients/webassembly/src/tester/ephemeral_receiver.rs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/clients/webassembly/src/client/helpers.rs b/clients/webassembly/src/client/helpers.rs index c3f456a891..7d5373853b 100644 --- a/clients/webassembly/src/client/helpers.rs +++ b/clients/webassembly/src/client/helpers.rs @@ -114,7 +114,7 @@ impl WasmTopologyExt for Arc { 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 { 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 { diff --git a/clients/webassembly/src/tester/ephemeral_receiver.rs b/clients/webassembly/src/tester/ephemeral_receiver.rs index 826bfa07b2..2d3117addd 100644 --- a/clients/webassembly/src/tester/ephemeral_receiver.rs +++ b/clients/webassembly/src/tester/ephemeral_receiver.rs @@ -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) => { From 377c9be790827e721a6c06d93bf9bca39022da9e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jon=20H=C3=A4ggblad?= Date: Thu, 24 Aug 2023 17:43:57 +0200 Subject: [PATCH 324/361] clippy --fix --- .../native/websocket-requests/src/requests.rs | 30 +++++++++---------- .../websocket-requests/src/responses.rs | 12 ++++---- .../webassembly/src/encoded_payload_helper.rs | 2 +- .../src/signing/direct_wallet.rs | 8 ++--- common/coconut-interface/src/lib.rs | 2 +- common/nymcoconut/src/proofs/mod.rs | 4 +-- .../acknowledgements/src/identifier.rs | 2 +- .../acknowledgements/src/surb_ack.rs | 2 +- .../anonymous-replies/src/reply_surb.rs | 2 +- .../anonymous-replies/src/requests.rs | 4 +-- common/nymsphinx/chunking/src/fragment.rs | 2 +- common/nymsphinx/cover/src/lib.rs | 2 +- common/nymsphinx/forwarding/src/packet.rs | 4 +-- common/nymsphinx/src/preparer/payload.rs | 9 ++---- common/socks5/requests/src/lib.rs | 14 ++++----- common/socks5/requests/src/request.rs | 24 ++++++--------- common/socks5/requests/src/response.rs | 10 +++---- common/topology/src/lib.rs | 2 +- .../src/registration/handshake/gateway.rs | 2 +- .../src/registration/handshake/shared_key.rs | 5 +--- .../common/src/interface/control.rs | 2 +- .../common/src/interface/request.rs | 4 +-- .../common/src/interface/response.rs | 4 +-- 23 files changed, 67 insertions(+), 85 deletions(-) diff --git a/clients/native/websocket-requests/src/requests.rs b/clients/native/websocket-requests/src/requests.rs index 3c4c1a618b..645f2edf9c 100644 --- a/clients/native/websocket-requests/src/requests.rs +++ b/clients/native/websocket-requests/src/requests.rs @@ -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 { 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 { 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() } diff --git a/clients/native/websocket-requests/src/responses.rs b/clients/native/websocket-requests/src/responses.rs index d326388570..713054ecf3 100644 --- a/clients/native/websocket-requests/src/responses.rs +++ b/clients/native/websocket-requests/src/responses.rs @@ -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 { 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() } diff --git a/clients/webassembly/src/encoded_payload_helper.rs b/clients/webassembly/src/encoded_payload_helper.rs index 84f463ec58..17ae92fbf8 100644 --- a/clients/webassembly/src/encoded_payload_helper.rs +++ b/clients/webassembly/src/encoded_payload_helper.rs @@ -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(), diff --git a/common/client-libs/validator-client/src/signing/direct_wallet.rs b/common/client-libs/validator-client/src/signing/direct_wallet.rs index b26ac39ada..3de09502ae 100644 --- a/common/client-libs/validator-client/src/signing/direct_wallet.rs +++ b/common/client-libs/validator-client/src/signing/direct_wallet.rs @@ -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", diff --git a/common/coconut-interface/src/lib.rs b/common/coconut-interface/src/lib.rs index 4f69c2c9fa..1b96fe062f 100644 --- a/common/coconut-interface/src/lib.rs +++ b/common/coconut-interface/src/lib.rs @@ -57,7 +57,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(), ] diff --git a/common/nymcoconut/src/proofs/mod.rs b/common/nymcoconut/src/proofs/mod.rs index 84c7211aa2..42f923466d 100644 --- a/common/nymcoconut/src/proofs/mod.rs +++ b/common/nymcoconut/src/proofs/mod.rs @@ -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::>(); - 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 diff --git a/common/nymsphinx/acknowledgements/src/identifier.rs b/common/nymsphinx/acknowledgements/src/identifier.rs index aab5a96146..abe1d58368 100644 --- a/common/nymsphinx/acknowledgements/src/identifier.rs +++ b/common/nymsphinx/acknowledgements/src/identifier.rs @@ -17,7 +17,7 @@ pub fn prepare_identifier( let id_ciphertext = encrypt::(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( diff --git a/common/nymsphinx/acknowledgements/src/surb_ack.rs b/common/nymsphinx/acknowledgements/src/surb_ack.rs index e623d4e738..732f84b9ba 100644 --- a/common/nymsphinx/acknowledgements/src/surb_ack.rs +++ b/common/nymsphinx/acknowledgements/src/surb_ack.rs @@ -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)) } diff --git a/common/nymsphinx/anonymous-replies/src/reply_surb.rs b/common/nymsphinx/anonymous-replies/src/reply_surb.rs index 2bf5799912..d38b16fc13 100644 --- a/common/nymsphinx/anonymous-replies/src/reply_surb.rs +++ b/common/nymsphinx/anonymous-replies/src/reply_surb.rs @@ -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() } diff --git a/common/nymsphinx/anonymous-replies/src/requests.rs b/common/nymsphinx/anonymous-replies/src/requests.rs index f4effbf7d6..9dd4c84dc0 100644 --- a/common/nymsphinx/anonymous-replies/src/requests.rs +++ b/common/nymsphinx/anonymous-replies/src/requests.rs @@ -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(), } } diff --git a/common/nymsphinx/chunking/src/fragment.rs b/common/nymsphinx/chunking/src/fragment.rs index 74996f29ab..6dccfa053a 100644 --- a/common/nymsphinx/chunking/src/fragment.rs +++ b/common/nymsphinx/chunking/src/fragment.rs @@ -204,7 +204,7 @@ impl Fragment { self.header .to_bytes() .into_iter() - .chain(self.payload.into_iter()) + .chain(self.payload) .collect() } diff --git a/common/nymsphinx/cover/src/lib.rs b/common/nymsphinx/cover/src/lib.rs index 05f4cffe33..66142a4cee 100644 --- a/common/nymsphinx/cover/src/lib.rs +++ b/common/nymsphinx/cover/src/lib.rs @@ -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 = diff --git a/common/nymsphinx/forwarding/src/packet.rs b/common/nymsphinx/forwarding/src/packet.rs index 2276e9a5cd..13026a3aaf 100644 --- a/common/nymsphinx/forwarding/src/packet.rs +++ b/common/nymsphinx/forwarding/src/packet.rs @@ -106,8 +106,8 @@ impl MixPacket { pub fn into_bytes(self) -> Result, 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()) } } diff --git a/common/nymsphinx/src/preparer/payload.rs b/common/nymsphinx/src/preparer/payload.rs index 5999026252..bfd1715d57 100644 --- a/common/nymsphinx/src/preparer/payload.rs +++ b/common/nymsphinx/src/preparer/payload.rs @@ -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 { let key_digest = packet_encryption_key.compute_digest(); - self.build::( - packet_encryption_key.inner(), - key_digest.into_iter(), - ) + self.build::(packet_encryption_key.inner(), key_digest) } pub fn build_regular( diff --git a/common/socks5/requests/src/lib.rs b/common/socks5/requests/src/lib.rs index 58c23dcd9d..c131f132a0 100644 --- a/common/socks5/requests/src/lib.rs +++ b/common/socks5/requests/src/lib.rs @@ -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 { 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 { - 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 { @@ -190,9 +188,7 @@ impl SocketData { } pub fn into_response_bytes_iter(self) -> impl Iterator { - self.header - .into_response_bytes_iter() - .chain(self.data.into_iter()) + self.header.into_response_bytes_iter().chain(self.data) } } diff --git a/common/socks5/requests/src/request.rs b/common/socks5/requests/src/request.rs index 0e29fb49d9..530d27e11f 100644 --- a/common/socks5/requests/src/request.rs +++ b/common/socks5/requests/src/request.rs @@ -113,7 +113,7 @@ impl Serializable for Socks5Request { fn into_bytes(self) -> Vec { 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 { diff --git a/common/socks5/requests/src/response.rs b/common/socks5/requests/src/response.rs index c0ff60fa39..0415035ac5 100644 --- a/common/socks5/requests/src/response.rs +++ b/common/socks5/requests/src/response.rs @@ -84,7 +84,7 @@ impl Serializable for Socks5Response { fn into_bytes(self) -> Vec { 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 = 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!( diff --git a/common/topology/src/lib.rs b/common/topology/src/lib.rs index 45eb2d9d7d..428efd1056 100644 --- a/common/topology/src/lib.rs +++ b/common/topology/src/lib.rs @@ -131,7 +131,7 @@ impl NymTopology { } pub fn mixes_in_layer(&self, layer: MixLayer) -> Vec { - assert!(vec![1, 2, 3].contains(&layer)); + assert!([1, 2, 3].contains(&layer)); self.mixes.get(&layer).unwrap().to_owned() } diff --git a/gateway/gateway-requests/src/registration/handshake/gateway.rs b/gateway/gateway-requests/src/registration/handshake/gateway.rs index 2afebbc115..69381dc5d4 100644 --- a/gateway/gateway-requests/src/registration/handshake/gateway.rs +++ b/gateway/gateway-requests/src/registration/handshake/gateway.rs @@ -94,7 +94,7 @@ impl<'a> GatewayHandshake<'a> { .to_bytes() .iter() .cloned() - .chain(material.into_iter()) + .chain(material) .collect() } diff --git a/gateway/gateway-requests/src/registration/handshake/shared_key.rs b/gateway/gateway-requests/src/registration/handshake/shared_key.rs index abe1754e31..d3101017a4 100644 --- a/gateway/gateway-requests/src/registration/handshake/shared_key.rs +++ b/gateway/gateway-requests/src/registration/handshake/shared_key.rs @@ -85,10 +85,7 @@ impl SharedKeys { let mac = compute_keyed_hmac::(self.mac_key(), &encrypted_data); - mac.into_bytes() - .into_iter() - .chain(encrypted_data.into_iter()) - .collect() + mac.into_bytes().into_iter().chain(encrypted_data).collect() } pub fn decrypt_tagged( diff --git a/service-providers/common/src/interface/control.rs b/service-providers/common/src/interface/control.rs index 8e808d6fdd..d6a0a28a68 100644 --- a/service-providers/common/src/interface/control.rs +++ b/service-providers/common/src/interface/control.rs @@ -130,7 +130,7 @@ impl Serializable for ControlResponse { fn into_bytes(self) -> Vec { std::iter::once(self.tag() as u8) - .chain(self.serialize_inner().into_iter()) + .chain(self.serialize_inner()) .collect() } diff --git a/service-providers/common/src/interface/request.rs b/service-providers/common/src/interface/request.rs index fefa1ffae6..12a884d79e 100644 --- a/service-providers/common/src/interface/request.rs +++ b/service-providers/common/src/interface/request.rs @@ -81,7 +81,7 @@ where pub fn into_bytes(self) -> Vec { if let Some(version) = self.interface_version.as_u8() { std::iter::once(version) - .chain(self.content.into_bytes(self.interface_version).into_iter()) + .chain(self.content.into_bytes(self.interface_version)) .collect() } else { self.content.into_bytes(self.interface_version) @@ -137,7 +137,7 @@ where } } else { std::iter::once(self.tag() as u8) - .chain(self.serialize_inner().into_iter()) + .chain(self.serialize_inner()) .collect() } } diff --git a/service-providers/common/src/interface/response.rs b/service-providers/common/src/interface/response.rs index 5aff60c5ad..ad502e8b30 100644 --- a/service-providers/common/src/interface/response.rs +++ b/service-providers/common/src/interface/response.rs @@ -71,7 +71,7 @@ where pub fn into_bytes(self) -> Vec { if let Some(version) = self.interface_version.as_u8() { std::iter::once(version) - .chain(self.content.into_bytes(self.interface_version).into_iter()) + .chain(self.content.into_bytes(self.interface_version)) .collect() } else { self.content.into_bytes(self.interface_version) @@ -127,7 +127,7 @@ where } } else { std::iter::once(self.tag() as u8) - .chain(self.serialize_inner().into_iter()) + .chain(self.serialize_inner()) .collect() } } From b050ae72de668f56b1d922cb0e2fe719a44cf6bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jon=20H=C3=A4ggblad?= Date: Fri, 25 Aug 2023 07:33:32 +0200 Subject: [PATCH 325/361] Replace Arc with Rc on clippy's suggestion --- clients/webassembly/src/storage/mod.rs | 7 ++++--- clients/webassembly/src/tester/mod.rs | 7 ++++--- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/clients/webassembly/src/storage/mod.rs b/clients/webassembly/src/storage/mod.rs index f4ab912657..febbd9ecd5 100644 --- a/clients/webassembly/src/storage/mod.rs +++ b/clients/webassembly/src/storage/mod.rs @@ -1,6 +1,8 @@ // Copyright 2023 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 +use std::rc::Rc; + use crate::client::config::Config; use crate::storage::error::ClientStorageError; use js_sys::Promise; @@ -8,7 +10,6 @@ use nym_client_core::client::base_client::storage::gateway_details::PersistedGat use nym_crypto::asymmetric::{encryption, identity}; use nym_gateway_client::SharedKeys; use nym_sphinx::acknowledgements::AckKey; -use std::sync::Arc; use wasm_bindgen::prelude::*; use wasm_bindgen_futures::future_to_promise; use wasm_utils::storage::{IdbVersionChangeEvent, WasmStorage}; @@ -43,7 +44,7 @@ mod v1 { pub struct ClientStorage { #[allow(dead_code)] pub(crate) name: String, - pub(crate) inner: Arc, + pub(crate) inner: Rc, } #[wasm_bindgen] @@ -88,7 +89,7 @@ impl ClientStorage { .await?; Ok(ClientStorage { - inner: Arc::new(inner), + inner: Rc::new(inner), name, }) } diff --git a/clients/webassembly/src/tester/mod.rs b/clients/webassembly/src/tester/mod.rs index ba70062f3b..6433c029c7 100644 --- a/clients/webassembly/src/tester/mod.rs +++ b/clients/webassembly/src/tester/mod.rs @@ -29,6 +29,7 @@ use nym_validator_client::client::IdentityKey; use nym_validator_client::QueryReqwestRpcNyxdClient; use rand::rngs::OsRng; use std::collections::HashSet; +use std::rc::Rc; use std::sync::atomic::{AtomicBool, AtomicU32, Ordering}; use std::sync::{Arc, Mutex as SyncMutex}; use std::time::Duration; @@ -42,7 +43,7 @@ pub(crate) mod helpers; pub type NodeTestMessage = TestMessage; type LockedGatewayClient = - Arc>>; + Rc>>; pub(crate) const DEFAULT_TEST_TIMEOUT: Duration = Duration::from_secs(10); pub(crate) const DEFAULT_TEST_PACKETS: u32 = 20; @@ -210,7 +211,7 @@ impl NymNodeTesterBuilder { test_in_progress: Arc::new(AtomicBool::new(false)), current_test_nonce: Default::default(), tester: Arc::new(SyncMutex::new(tester)), - gateway_client: Arc::new(AsyncMutex::new(gateway_client)), + gateway_client: Rc::new(AsyncMutex::new(gateway_client)), processed_receiver: ReceivedReceiverWrapper::new(processed_receiver), _task_manager: task_manager, }) @@ -343,7 +344,7 @@ impl NymNodeTester { )); let processed_receiver_clone = self.processed_receiver.clone(); - let gateway_client_clone = Arc::clone(&self.gateway_client); + let gateway_client_clone = Rc::clone(&self.gateway_client); let tester_marker = TestMarker::new(Arc::clone(&self.test_in_progress)); // start doing async things (send packets and watch for anything coming back) From 48023eab41a38df16f56d193f260a4c2a5e6e123 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jon=20H=C3=A4ggblad?= Date: Fri, 25 Aug 2023 07:33:49 +0200 Subject: [PATCH 326/361] Remove shadowing import --- common/wasm-utils/src/storage/mod.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/common/wasm-utils/src/storage/mod.rs b/common/wasm-utils/src/storage/mod.rs index 1a5b601ce6..eb17949849 100644 --- a/common/wasm-utils/src/storage/mod.rs +++ b/common/wasm-utils/src/storage/mod.rs @@ -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, From 8503def37fb1d3570d12fbe0ed143a4bf1c163c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jon=20H=C3=A4ggblad?= Date: Fri, 25 Aug 2023 07:37:29 +0200 Subject: [PATCH 327/361] Remove nym-connect mobile from Makefile --- Makefile | 3 --- 1 file changed, 3 deletions(-) diff --git a/Makefile b/Makefile index 2576489be9..6fc92e55f8 100644 --- a/Makefile +++ b/Makefile @@ -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 From 8104761914c92207112ace1706a5208367aba869 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jon=20H=C3=A4ggblad?= Date: Fri, 25 Aug 2023 07:37:45 +0200 Subject: [PATCH 328/361] Update nym-connect Cargo.lock --- nym-connect/desktop/Cargo.lock | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/nym-connect/desktop/Cargo.lock b/nym-connect/desktop/Cargo.lock index 612a9d8149..df5097ad1c 100644 --- a/nym-connect/desktop/Cargo.lock +++ b/nym-connect/desktop/Cargo.lock @@ -7557,9 +7557,9 @@ checksum = "3528ecfd12c466c6f163363caf2d02a71161dd5e1cc6ae7b34207ea2d42d81ed" [[package]] name = "ts-rs" -version = "6.2.1" +version = "7.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4added4070a4fdf9df03457206cd2e4b12417c8560a2954d91ffcbe60177a56a" +checksum = "e1ff1f8c90369bc172200013ac17ae86e7b5def580687df4e6127883454ff2b0" dependencies = [ "thiserror", "ts-rs-macros", @@ -7567,14 +7567,14 @@ dependencies = [ [[package]] name = "ts-rs-macros" -version = "6.2.0" +version = "7.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f807fdb3151fee75df7485b901a89624358cd07a67a8fb1a5831bf5a07681ff" +checksum = "a6f41cc0aeb7a4a55730188e147d3795a7349b501f8334697fd37629b896cdc2" dependencies = [ "Inflector", "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.28", "termcolor", ] From decae2b54dba147e4102406b3c602ffe0437d793 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jon=20H=C3=A4ggblad?= Date: Fri, 25 Aug 2023 09:01:27 +0200 Subject: [PATCH 329/361] Clippy pub use shadowing warning --- common/coconut-interface/src/lib.rs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/common/coconut-interface/src/lib.rs b/common/coconut-interface/src/lib.rs index 1b96fe062f..f0f38f5cd4 100644 --- a/common/coconut-interface/src/lib.rs +++ b/common/coconut-interface/src/lib.rs @@ -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, hash_to_scalar, prepare_blind_sign, + prove_bandwidth_credential, Attribute, Base58, BlindSignRequest, BlindedSignature, Bytable, + CoconutError, KeyPair, Parameters, PrivateAttribute, PublicAttribute, Signature, + SignatureShare, Theta, VerificationKey, blind_sign +}; #[derive(Debug, Serialize, Deserialize, Getters, CopyGetters, Clone, PartialEq, Eq)] pub struct Credential { @@ -138,6 +145,8 @@ impl Base58 for Credential {} #[cfg(test)] mod tests { + use nym_coconut::{prove_bandwidth_credential, Signature}; + use super::*; #[test] From 188650ea05dad6b79f79882bdccf2e2eef28bbaf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jon=20H=C3=A4ggblad?= Date: Fri, 25 Aug 2023 09:09:05 +0200 Subject: [PATCH 330/361] Stay on 1.71.0 for nym-wallet until the clippy crash is resolved --- .github/workflows/wallet.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/wallet.yml b/.github/workflows/wallet.yml index d77fb9d6fb..34f202d25f 100644 --- a/.github/workflows/wallet.yml +++ b/.github/workflows/wallet.yml @@ -31,7 +31,7 @@ jobs: uses: actions-rs/toolchain@v1 with: profile: minimal - toolchain: stable + toolchain: 1.71.0 override: true components: rustfmt, clippy From ee67f3d0f040b9546d5d4721d2ccf136530d15a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jon=20H=C3=A4ggblad?= Date: Fri, 25 Aug 2023 09:10:10 +0200 Subject: [PATCH 331/361] rustfmt --- common/coconut-interface/src/lib.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/common/coconut-interface/src/lib.rs b/common/coconut-interface/src/lib.rs index f0f38f5cd4..2322bc3978 100644 --- a/common/coconut-interface/src/lib.rs +++ b/common/coconut-interface/src/lib.rs @@ -11,10 +11,10 @@ use error::CoconutInterfaceError; // 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, hash_to_scalar, prepare_blind_sign, - prove_bandwidth_credential, Attribute, Base58, BlindSignRequest, BlindedSignature, Bytable, - CoconutError, KeyPair, Parameters, PrivateAttribute, PublicAttribute, Signature, - SignatureShare, Theta, VerificationKey, blind_sign + 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)] From ec75a06c9d73d74f962119766c191b4a60b6716f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jon=20H=C3=A4ggblad?= Date: Thu, 24 Aug 2023 14:47:03 +0200 Subject: [PATCH 332/361] nym-connect: filter gateways on compatible version --- .../src-tauri/src/operations/directory/mod.rs | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/nym-connect/desktop/src-tauri/src/operations/directory/mod.rs b/nym-connect/desktop/src-tauri/src/operations/directory/mod.rs index dc001dac2e..3842720161 100644 --- a/nym-connect/desktop/src-tauri/src/operations/directory/mod.rs +++ b/nym-connect/desktop/src-tauri/src/operations/directory/mod.rs @@ -8,6 +8,7 @@ use crate::{ }; use itertools::Itertools; use nym_api_requests::models::GatewayBondAnnotated; +use nym_bin_common::version_checker::is_minor_version_compatible; use nym_config::defaults::var_names::{NETWORK_NAME, NYM_API}; use nym_contracts_common::types::Percent; use nym_validator_client::nym_api::Client as ApiClient; @@ -120,7 +121,17 @@ fn filter_out_inactive_services( async fn fetch_gateways() -> Result> { let api_client = ApiClient::new(Url::from_str(&std::env::var(NYM_API)?)?); - Ok(api_client.get_gateways_detailed().await?) + let gateways = api_client.get_gateways_detailed().await?; + let our_version = env!("CARGO_PKG_VERSION"); + log::debug!( + "Our version that we use to filter compatible gateways: {}", + our_version + ); + let gateways = gateways + .into_iter() + .filter(|g| is_minor_version_compatible(&g.gateway_bond.gateway.version, our_version)) + .collect(); + Ok(gateways) } #[tauri::command] From 2fbb901deb3d97ec36933d99a707c08029db6d5a Mon Sep 17 00:00:00 2001 From: Lorexia Date: Fri, 25 Aug 2023 11:42:36 +0200 Subject: [PATCH 333/361] Update mixnode setup to return IPv4 --- documentation/operators/src/nodes/mix-node-setup.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/documentation/operators/src/nodes/mix-node-setup.md b/documentation/operators/src/nodes/mix-node-setup.md index a50c5cac13..b168329487 100644 --- a/documentation/operators/src/nodes/mix-node-setup.md +++ b/documentation/operators/src/nodes/mix-node-setup.md @@ -63,16 +63,16 @@ To check available configuration options for initializing your node use: ``` ~~~ -Initalise your mix node with the following command, replacing the value of `--id` with the moniker you wish to give your mix node. Your `--host` must be publicly routable on the internet in order to mix packets, and can be either an Ipv4 or IPv6 address. The `$(curl ifconfig.me)` command returns your IP automatically using an external service. If you enter your IP address manually, enter it **without** any port information. +Initalise your mix node with the following command, replacing the value of `--id` with the moniker you wish to give your mix node. Your `--host` must be publicly routable on the internet in order to mix packets, and can be either an Ipv4 or IPv6 address. The `$(curl -4 https://ifconfig.me)` command returns your IP automatically using an external service. If you enter your IP address manually, enter it **without** any port information. ``` -./nym-mixnode init --id --host $(curl ifconfig.me) +./nym-mixnode init --id --host $(curl -4 https://ifconfig.me) ``` ~~~admonish example collapsible=true title="Console output" ``` -.nym-mixnode init --id --host $(curl ifconfig.me) --wallet-address +.nym-mixnode init --id --host $(curl -4 https://ifconfig.me) --wallet-address Initialising mixnode ... From 069226125b05699e267c2b021ec651457b42078e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jon=20H=C3=A4ggblad?= Date: Thu, 24 Aug 2023 16:36:06 +0200 Subject: [PATCH 334/361] Separate cfg wasm function --- .../topology_control/geo_aware_provider.rs | 28 +++++++++---------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/common/client-core/src/client/topology_control/geo_aware_provider.rs b/common/client-core/src/client/topology_control/geo_aware_provider.rs index ece6e967a2..36010e5e3f 100644 --- a/common/client-core/src/client/topology_control/geo_aware_provider.rs +++ b/common/client-core/src/client/topology_control/geo_aware_provider.rs @@ -15,6 +15,19 @@ use url::Url; const MIN_NODES_PER_LAYER: usize = 1; +#[cfg(target_arch = "wasm32")] +fn reqwest_client() -> Option { + reqwest::Client::builder().build().ok() +} + +#[cfg(not(target_arch = "wasm32"))] +fn reqwest_client() -> Option { + reqwest::Client::builder() + .timeout(std::time::Duration::from_secs(5)) + .build() + .ok() +} + // TODO: create a explorer-api-client async fn fetch_mixnodes_from_explorer_api() -> Option> { let explorer_api_url = std::env::var(EXPLORER_API).ok()?; @@ -24,20 +37,7 @@ async fn fetch_mixnodes_from_explorer_api() -> Option Date: Fri, 25 Aug 2023 12:44:17 +0200 Subject: [PATCH 335/361] Also filter gateways on location --- .../topology_control/geo_aware_provider.rs | 67 ++++++++++++++++++- 1 file changed, 65 insertions(+), 2 deletions(-) diff --git a/common/client-core/src/client/topology_control/geo_aware_provider.rs b/common/client-core/src/client/topology_control/geo_aware_provider.rs index 36010e5e3f..88a8bdada4 100644 --- a/common/client-core/src/client/topology_control/geo_aware_provider.rs +++ b/common/client-core/src/client/topology_control/geo_aware_provider.rs @@ -1,14 +1,14 @@ use std::{collections::HashMap, fmt}; use log::{debug, error, info}; -use nym_explorer_api_requests::PrettyDetailedMixNodeBond; +use nym_explorer_api_requests::{PrettyDetailedMixNodeBond, PrettyDetailedGatewayBond}; use nym_network_defaults::var_names::EXPLORER_API; use nym_topology::{ nym_topology_from_detailed, provider_trait::{async_trait, TopologyProvider}, NymTopology, }; -use nym_validator_client::client::MixId; +use nym_validator_client::client::{MixId, IdentityKey}; use rand::{prelude::SliceRandom, thread_rng}; use serde::{Deserialize, Serialize}; use url::Url; @@ -47,6 +47,25 @@ async fn fetch_mixnodes_from_explorer_api() -> Option Option> { + let explorer_api_url = std::env::var(EXPLORER_API).ok()?; + let explorer_api_url = Url::parse(&explorer_api_url) + .ok()? + .join("v1/gateways") + .ok()?; + + debug!("Fetching: {}", explorer_api_url); + reqwest_client()? + .get(explorer_api_url) + .send() + .await + .ok()? + .json::>() + .await + .ok() +} + #[derive(Copy, Clone, Hash, PartialEq, Eq, Serialize, Deserialize, Debug)] pub enum CountryGroup { Europe, @@ -213,6 +232,22 @@ fn group_mixnodes_by_country_code( }) } +fn group_gateways_by_country_code( + gateways: Vec, +) -> HashMap> { + gateways + .into_iter() + .fold(HashMap::>::new(), |mut acc, g| { + if let Some(ref location) = g.location { + let country_code = location.two_letter_iso_country_code.clone(); + let group_code = CountryGroup::new(country_code.as_str()); + let gateways = acc.entry(group_code).or_insert_with(Vec::new); + gateways.push(g.gateway.identity_key) + } + acc + }) +} + fn log_mixnode_distribution(mixnodes: &HashMap>) { let mixnode_distribution = mixnodes .iter() @@ -222,6 +257,15 @@ fn log_mixnode_distribution(mixnodes: &HashMap>) { debug!("Mixnode distribution - {}", mixnode_distribution); } +fn log_gateway_distribution(gateways: &HashMap>) { + let gateway_distribution = gateways + .iter() + .map(|(k, v)| format!("{}: {}", k, v.len())) + .collect::>() + .join(", "); + debug!("Gateway distribution - {}", gateway_distribution); +} + fn check_layer_integrity(topology: NymTopology) -> Result<(), ()> { let mixes = topology.mixes(); if mixes.keys().len() < 3 { @@ -294,6 +338,12 @@ impl GeoAwareTopologyProvider { return None; }; + debug!("Fetching gateways from explorer-api..."); + let Some(gateways_from_explorer_api) = fetch_gateways_from_explorer_api().await else { + error!("failed to get mixnodes from explorer-api"); + return None; + }; + // 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. @@ -302,16 +352,29 @@ impl GeoAwareTopologyProvider { let mixnode_distribution = group_mixnodes_by_country_code(mixnodes_from_explorer_api); log_mixnode_distribution(&mixnode_distribution); + let gateway_distribution = group_gateways_by_country_code(gateways_from_explorer_api); + log_gateway_distribution(&gateway_distribution); + let Some(filtered_mixnode_ids) = mixnode_distribution.get(&self.filter_on) else { error!("no mixnodes found for: {}", self.filter_on); return None; }; + let Some(filtered_gateway_ids) = gateway_distribution.get(&self.filter_on) else { + error!("no gateways found for: {}", self.filter_on); + return None; + }; + let mixnodes = mixnodes .into_iter() .filter(|m| filtered_mixnode_ids.contains(&m.mix_id())) .collect::>(); + let gateways = gateways + .into_iter() + .filter(|g| filtered_gateway_ids.contains(g.identity())) + .collect::>(); + let topology = nym_topology_from_detailed(mixnodes, gateways) .filter_system_version(&self.client_version); From 66f04d17b73d7a1d34a84394a616d077e7a31083 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jon=20H=C3=A4ggblad?= Date: Fri, 25 Aug 2023 13:41:21 +0200 Subject: [PATCH 336/361] Select country group by the exit gateway --- clients/socks5/src/commands/mod.rs | 16 ++++-- .../client-core/src/client/base_client/mod.rs | 12 +++-- .../topology_control/geo_aware_provider.rs | 54 ++++++++++++++----- common/client-core/src/config/mod.rs | 13 ++++- nym-connect/desktop/Cargo.lock | 1 + nym-connect/desktop/src-tauri/src/tasks.rs | 13 +++-- 6 files changed, 82 insertions(+), 27 deletions(-) diff --git a/clients/socks5/src/commands/mod.rs b/clients/socks5/src/commands/mod.rs index 393429246d..d2b37b5a84 100644 --- a/clients/socks5/src/commands/mod.rs +++ b/clients/socks5/src/commands/mod.rs @@ -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() }; diff --git a/common/client-core/src/client/base_client/mod.rs b/common/client-core/src/client/base_client/mod.rs index 2f2e7d0072..02615f2b95 100644 --- a/common/client-core/src/client/base_client/mod.rs +++ b/common/client-core/src/client/base_client/mod.rs @@ -354,11 +354,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, + )) + } }) } diff --git a/common/client-core/src/client/topology_control/geo_aware_provider.rs b/common/client-core/src/client/topology_control/geo_aware_provider.rs index 88a8bdada4..dabece05e5 100644 --- a/common/client-core/src/client/topology_control/geo_aware_provider.rs +++ b/common/client-core/src/client/topology_control/geo_aware_provider.rs @@ -1,18 +1,21 @@ use std::{collections::HashMap, fmt}; use log::{debug, error, info}; -use nym_explorer_api_requests::{PrettyDetailedMixNodeBond, PrettyDetailedGatewayBond}; +use nym_explorer_api_requests::{PrettyDetailedGatewayBond, PrettyDetailedMixNodeBond}; use nym_network_defaults::var_names::EXPLORER_API; use nym_topology::{ nym_topology_from_detailed, provider_trait::{async_trait, TopologyProvider}, NymTopology, }; -use nym_validator_client::client::{MixId, IdentityKey}; +use nym_validator_client::client::{IdentityKey, MixId}; use rand::{prelude::SliceRandom, thread_rng}; use serde::{Deserialize, Serialize}; +use tap::TapOptional; use url::Url; +use crate::config::GroupBy; + const MIN_NODES_PER_LAYER: usize = 1; #[cfg(target_arch = "wasm32")] @@ -235,9 +238,9 @@ fn group_mixnodes_by_country_code( fn group_gateways_by_country_code( gateways: Vec, ) -> HashMap> { - gateways - .into_iter() - .fold(HashMap::>::new(), |mut acc, g| { + gateways.into_iter().fold( + HashMap::>::new(), + |mut acc, g| { if let Some(ref location) = g.location { let country_code = location.two_letter_iso_country_code.clone(); let group_code = CountryGroup::new(country_code.as_str()); @@ -245,7 +248,8 @@ fn group_gateways_by_country_code( gateways.push(g.gateway.identity_key) } acc - }) + }, + ) } fn log_mixnode_distribution(mixnodes: &HashMap>) { @@ -288,7 +292,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, } @@ -296,7 +300,7 @@ impl GeoAwareTopologyProvider { pub fn new( mut nym_api_urls: Vec, client_version: String, - filter_on: CountryGroup, + filter_on: GroupBy, ) -> GeoAwareTopologyProvider { log::info!( "Creating geo-aware topology provider with filter on {:?}", @@ -344,6 +348,32 @@ impl GeoAwareTopologyProvider { 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. @@ -355,13 +385,13 @@ impl GeoAwareTopologyProvider { let gateway_distribution = group_gateways_by_country_code(gateways_from_explorer_api); log_gateway_distribution(&gateway_distribution); - let Some(filtered_mixnode_ids) = mixnode_distribution.get(&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; }; - let Some(filtered_gateway_ids) = gateway_distribution.get(&self.filter_on) else { - error!("no gateways found for: {}", self.filter_on); + let Some(filtered_gateway_ids) = gateway_distribution.get(&filter_on) else { + error!("no gateways found for: {}", filter_on); return None; }; diff --git a/common/client-core/src/config/mod.rs b/common/client-core/src/config/mod.rs index 465c227e19..3442210d37 100644 --- a/common/client-core/src/config/mod.rs +++ b/common/client-core/src/config/mod.rs @@ -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; @@ -484,7 +487,13 @@ pub struct Topology { pub enum TopologyStructure { #[default] NymApi, - GeoAware(CountryGroup), + GeoAware(GroupBy), +} + +#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)] +pub enum GroupBy { + CountryGroup(CountryGroup), + NymAddress(Recipient), } impl Default for Topology { diff --git a/nym-connect/desktop/Cargo.lock b/nym-connect/desktop/Cargo.lock index df5097ad1c..e915498a01 100644 --- a/nym-connect/desktop/Cargo.lock +++ b/nym-connect/desktop/Cargo.lock @@ -3884,6 +3884,7 @@ version = "1.1.15" dependencies = [ "async-trait", "base64 0.21.2", + "cfg-if", "dashmap", "dirs 4.0.0", "futures", diff --git a/nym-connect/desktop/src-tauri/src/tasks.rs b/nym-connect/desktop/src-tauri/src/tasks.rs index 06b0809c2f..6b0d5cadc8 100644 --- a/nym-connect/desktop/src-tauri/src/tasks.rs +++ b/nym-connect/desktop/src-tauri/src/tasks.rs @@ -6,7 +6,7 @@ use nym_client_core::{ }, topology_control::geo_aware_provider::CountryGroup, }, - config::{GatewayEndpointConfig, TopologyStructure}, + config::{GatewayEndpointConfig, GroupdBy, TopologyStructure}, error::ClientCoreStatusMessage, }; use nym_socks5_client_core::{NymClient as Socks5NymClient, Socks5ControlMessageSender}; @@ -54,12 +54,17 @@ fn override_config_from_env(config: &mut Config, privacy_level: &PrivacyLevel) { config.core.base.set_no_per_hop_delays(); // TODO: selectable in the UI - let default_country_group = CountryGroup::Europe; - log::warn!("Using geo-aware mixnode selection: {default_country_group}"); + let address = config + .core + .socks5 + .provider_mix_address + .parse() + .expect("failed to parse provider mix address"); + log::warn!("Using geo-aware mixnode selection baseon the location of: {address}"); config .core .base - .set_topology_structure(TopologyStructure::GeoAware(default_country_group)); + .set_topology_structure(TopologyStructure::GeoAware(GroupBy::NymAddress(address))); } } From 6c67cff235a087c0417d1dc6d146e69b3850d600 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jon=20H=C3=A4ggblad?= Date: Fri, 25 Aug 2023 16:02:13 +0200 Subject: [PATCH 337/361] Allow clippy for now --- common/client-core/src/config/mod.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/common/client-core/src/config/mod.rs b/common/client-core/src/config/mod.rs index 3442210d37..f3cee8200f 100644 --- a/common/client-core/src/config/mod.rs +++ b/common/client-core/src/config/mod.rs @@ -483,6 +483,7 @@ pub struct Topology { pub topology_structure: TopologyStructure, } +#[allow(clippy::large_enum_variant)] #[derive(Default, Copy, Clone, Debug, PartialEq, Serialize, Deserialize)] pub enum TopologyStructure { #[default] @@ -490,6 +491,7 @@ pub enum TopologyStructure { GeoAware(GroupBy), } +#[allow(clippy::large_enum_variant)] #[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)] pub enum GroupBy { CountryGroup(CountryGroup), From d530e492ec3f66e7e2cc2a9570d64f01f6ec5f88 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jon=20H=C3=A4ggblad?= Date: Fri, 25 Aug 2023 16:13:41 +0200 Subject: [PATCH 338/361] Fix compilation --- nym-connect/desktop/src-tauri/src/tasks.rs | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/nym-connect/desktop/src-tauri/src/tasks.rs b/nym-connect/desktop/src-tauri/src/tasks.rs index 6b0d5cadc8..4af366ca48 100644 --- a/nym-connect/desktop/src-tauri/src/tasks.rs +++ b/nym-connect/desktop/src-tauri/src/tasks.rs @@ -1,12 +1,9 @@ use futures::{channel::mpsc, StreamExt}; use nym_client_core::{ - client::{ - base_client::storage::{ - gateway_details::GatewayDetailsStore, MixnetClientStorage, OnDiskPersistent, - }, - topology_control::geo_aware_provider::CountryGroup, + client::base_client::storage::{ + gateway_details::GatewayDetailsStore, MixnetClientStorage, OnDiskPersistent, }, - config::{GatewayEndpointConfig, GroupdBy, TopologyStructure}, + config::{GatewayEndpointConfig, GroupBy, TopologyStructure}, error::ClientCoreStatusMessage, }; use nym_socks5_client_core::{NymClient as Socks5NymClient, Socks5ControlMessageSender}; From 2f6617daace6a6517142948add8f087d49192729 Mon Sep 17 00:00:00 2001 From: Fran Arbanas Date: Fri, 25 Aug 2023 17:13:52 +0200 Subject: [PATCH 339/361] Feature/issue credentials (#3691) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Move the functionality to issue credentials from the credential binary and connect it with nym-cli * finished CLI part, trying to fit SDK part * finished Rust SDK * fix: cleanup * linting * linting * linting * remove one layer of coconut in nym-cli * linting * Fixes based on PR comments * formatting * fixes based on PR comments * formatting * fixing clippy errors * fixed post-rebasing issues and converted the lib into shared dep for other binaries * removed credentials client in favour of moving the functionality to nym-cli * removed redundant 'issue_credential' example (it did the same thing as 'bandwdith') * removed credentials client from build server * made the coconut cli also accept nym-api configs * fixed support for socks5 and NR --------- Co-authored-by: Jędrzej Stuczyński --- .../build-and-upload-binaries-ci.yml | 1 - Cargo.lock | 45 +++--- Cargo.toml | 2 +- clients/credential/README.md | 32 ---- clients/credential/src/client.rs | 55 ------- clients/credential/src/commands.rs | 82 ---------- clients/credential/src/error.rs | 36 ----- clients/credential/src/main.rs | 131 --------------- clients/credential/src/recovery_storage.rs | 56 ------- clients/webassembly/Cargo.lock | 1 + .../examples/query_name_service.rs | 2 +- .../query_service_provider_directory.rs | 2 +- .../validator-client/src/client.rs | 8 + common/commands/Cargo.toml | 7 + .../commands/src/coconut/issue_credentials.rs | 51 ++++++ common/commands/src/coconut/mod.rs | 18 ++- .../src/coconut/recover_credentials.rs | 48 ++++++ common/commands/src/utils.rs | 153 +++++++++++++++++- .../credential-utils}/Cargo.toml | 19 +-- common/credential-utils/src/errors.rs | 31 ++++ common/credential-utils/src/lib.rs | 5 + .../credential-utils/src/recovery_storage.rs | 64 ++++++++ common/credential-utils/src/utils.rs | 139 ++++++++++++++++ common/credentials/Cargo.toml | 3 +- common/credentials/src/coconut/utils.rs | 29 +++- common/network-defaults/src/lib.rs | 4 +- explorer-api/src/main.rs | 2 +- nym-connect/desktop/Cargo.lock | 1 + sdk/lib/socks5-listener/src/lib.rs | 2 +- sdk/rust/nym-sdk/Cargo.toml | 4 +- sdk/rust/nym-sdk/examples/bandwidth.rs | 29 ++-- sdk/rust/nym-sdk/src/mixnet/paths.rs | 3 +- tools/nym-cli/src/coconut/mod.rs | 26 +++ tools/nym-cli/src/main.rs | 4 + 34 files changed, 633 insertions(+), 462 deletions(-) delete mode 100644 clients/credential/README.md delete mode 100644 clients/credential/src/client.rs delete mode 100644 clients/credential/src/commands.rs delete mode 100644 clients/credential/src/error.rs delete mode 100644 clients/credential/src/main.rs delete mode 100644 clients/credential/src/recovery_storage.rs create mode 100644 common/commands/src/coconut/issue_credentials.rs create mode 100644 common/commands/src/coconut/recover_credentials.rs rename {clients/credential => common/credential-utils}/Cargo.toml (54%) create mode 100644 common/credential-utils/src/errors.rs create mode 100644 common/credential-utils/src/lib.rs create mode 100644 common/credential-utils/src/recovery_storage.rs create mode 100644 common/credential-utils/src/utils.rs create mode 100644 tools/nym-cli/src/coconut/mod.rs diff --git a/.github/workflows/build-and-upload-binaries-ci.yml b/.github/workflows/build-and-upload-binaries-ci.yml index f317f90110..57b897d9c2 100644 --- a/.github/workflows/build-and-upload-binaries-ci.yml +++ b/.github/workflows/build-and-upload-binaries-ci.yml @@ -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 diff --git a/Cargo.lock b/Cargo.lock index e31c5a9702..1232af2f51 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5811,6 +5811,7 @@ dependencies = [ name = "nym-cli-commands" version = "1.0.0" dependencies = [ + "anyhow", "base64 0.13.1", "bip39", "bs58 0.4.0", @@ -5824,10 +5825,16 @@ dependencies = [ "humantime-serde", "k256 0.13.1", "log", + "nym-bandwidth-controller", "nym-bin-common", + "nym-client-core", "nym-coconut-bandwidth-contract-common", "nym-coconut-dkg-common", + "nym-config", "nym-contracts-common", + "nym-credential-storage", + "nym-credential-utils", + "nym-credentials", "nym-crypto", "nym-mixnet-contract-common", "nym-multisig-contract-common", @@ -6024,26 +6031,6 @@ dependencies = [ "thiserror", ] -[[package]] -name = "nym-credential-client" -version = "0.1.0" -dependencies = [ - "clap 4.3.21", - "log", - "nym-bandwidth-controller", - "nym-bin-common", - "nym-client-core", - "nym-config", - "nym-credential-storage", - "nym-credentials", - "nym-network-defaults", - "nym-pemstore", - "nym-validator-client", - "serde", - "thiserror", - "tokio", -] - [[package]] name = "nym-credential-storage" version = "0.1.0" @@ -6055,12 +6042,28 @@ dependencies = [ "tokio", ] +[[package]] +name = "nym-credential-utils" +version = "0.1.0" +dependencies = [ + "log", + "nym-bandwidth-controller", + "nym-client-core", + "nym-config", + "nym-credential-storage", + "nym-credentials", + "nym-validator-client", + "thiserror", + "tokio", +] + [[package]] name = "nym-credentials" version = "0.1.0" dependencies = [ "bls12_381 0.5.0", "cosmrs", + "log", "nym-api-requests", "nym-coconut-interface", "nym-crypto", @@ -6568,6 +6571,7 @@ dependencies = [ name = "nym-sdk" version = "0.1.0" dependencies = [ + "anyhow", "async-trait", "bip39", "dotenvy", @@ -6579,6 +6583,7 @@ dependencies = [ "nym-bin-common", "nym-client-core", "nym-credential-storage", + "nym-credential-utils", "nym-credentials", "nym-crypto", "nym-gateway-requests", diff --git a/Cargo.toml b/Cargo.toml index ac0178a41e..2c505c4bfe 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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", diff --git a/clients/credential/README.md b/clients/credential/README.md deleted file mode 100644 index 9d82b4b839..0000000000 --- a/clients/credential/README.md +++ /dev/null @@ -1,32 +0,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. - diff --git a/clients/credential/src/client.rs b/clients/credential/src/client.rs deleted file mode 100644 index 9eb0a7da75..0000000000 --- a/clients/credential/src/client.rs +++ /dev/null @@ -1,55 +0,0 @@ -// Copyright 2022 - Nym Technologies SA -// 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, - ) -> Result { - 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()) - } -} diff --git a/clients/credential/src/commands.rs b/clients/credential/src/commands.rs deleted file mode 100644 index da603eb84b..0000000000 --- a/clients/credential/src/commands.rs +++ /dev/null @@ -1,82 +0,0 @@ -// Copyright 2022 - Nym Technologies SA -// 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( - 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(()) -} diff --git a/clients/credential/src/error.rs b/clients/credential/src/error.rs deleted file mode 100644 index 1c6ac1521b..0000000000 --- a/clients/credential/src/error.rs +++ /dev/null @@ -1,36 +0,0 @@ -// Copyright 2022 - Nym Technologies SA -// 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 = std::result::Result; - -#[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), -} diff --git a/clients/credential/src/main.rs b/clients/credential/src/main.rs deleted file mode 100644 index 982ef02823..0000000000 --- a/clients/credential/src/main.rs +++ /dev/null @@ -1,131 +0,0 @@ -// Copyright 2022-2023 - Nym Technologies SA -// 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, - - #[clap(subcommand)] - pub(crate) command: Command, -} - -async fn block_until_coconut_is_available( - 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 /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(()) -} diff --git a/clients/credential/src/recovery_storage.rs b/clients/credential/src/recovery_storage.rs deleted file mode 100644 index db81d2ff6a..0000000000 --- a/clients/credential/src/recovery_storage.rs +++ /dev/null @@ -1,56 +0,0 @@ -// Copyright 2023 - Nym Technologies SA -// 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 { - create_dir_all(&recovery_dir)?; - Ok(Self { recovery_dir }) - } - - pub fn unconsumed_vouchers(&self) -> std::io::Result> { - 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 { - 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) - } -} diff --git a/clients/webassembly/Cargo.lock b/clients/webassembly/Cargo.lock index 529b241500..271ea93ea8 100644 --- a/clients/webassembly/Cargo.lock +++ b/clients/webassembly/Cargo.lock @@ -2677,6 +2677,7 @@ version = "0.1.0" dependencies = [ "bls12_381 0.5.0", "cosmrs", + "log", "nym-api-requests", "nym-coconut-interface", "nym-crypto", diff --git a/common/client-libs/validator-client/examples/query_name_service.rs b/common/client-libs/validator-client/examples/query_name_service.rs index 68e4da4cf2..f705cb3564 100644 --- a/common/client-libs/validator-client/examples/query_name_service.rs +++ b/common/client-libs/validator-client/examples/query_name_service.rs @@ -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(); diff --git a/common/client-libs/validator-client/examples/query_service_provider_directory.rs b/common/client-libs/validator-client/examples/query_service_provider_directory.rs index d0f6f7ce46..e489564e94 100644 --- a/common/client-libs/validator-client/examples/query_service_provider_directory.rs +++ b/common/client-libs/validator-client/examples/query_service_provider_directory.rs @@ -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(); diff --git a/common/client-libs/validator-client/src/client.rs b/common/client-libs/validator-client/src/client.rs index 6352dabff0..c5ae83a6bf 100644 --- a/common/client-libs/validator-client/src/client.rs +++ b/common/client-libs/validator-client/src/client.rs @@ -172,6 +172,10 @@ impl Client { // validator-api wrappers impl Client { + 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) } @@ -242,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); } diff --git a/common/commands/Cargo.toml b/common/commands/Cargo.toml index 45c9139fe3..df6860641e 100644 --- a/common/commands/Cargo.toml +++ b/common/commands/Cargo.toml @@ -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" } diff --git a/common/commands/src/coconut/issue_credentials.rs b/common/commands/src/coconut/issue_credentials.rs new file mode 100644 index 0000000000..cd8a9b5f36 --- /dev/null +++ b/common/commands/src/coconut/issue_credentials.rs @@ -0,0 +1,51 @@ +// Copyright 2022-2023 - Nym Technologies SA +// 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(()) +} diff --git a/common/commands/src/coconut/mod.rs b/common/commands/src/coconut/mod.rs index 6cfc921228..531a316125 100644 --- a/common/commands/src/coconut/mod.rs +++ b/common/commands/src/coconut/mod.rs @@ -1,4 +1,20 @@ // Copyright 2022 - Nym Technologies SA // 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), +} diff --git a/common/commands/src/coconut/recover_credentials.rs b/common/commands/src/coconut/recover_credentials.rs new file mode 100644 index 0000000000..025ea68c2c --- /dev/null +++ b/common/commands/src/coconut/recover_credentials.rs @@ -0,0 +1,48 @@ +// Copyright 2023 - Nym Technologies SA +// 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(()) +} diff --git a/common/commands/src/utils.rs b/common/commands/src/utils.rs index 63f8b6a085..25797c71e9 100644 --- a/common/commands/src/utils.rs +++ b/common/commands/src/utils.rs @@ -1,13 +1,17 @@ // Copyright 2022 - Nym Technologies SA // 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 DataWrapper { 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), + + // nym-api + NymApi(NymApiConfigLight), + + // anything else that might get get introduced + Unknown(UnknownConfigWrapper), +} + +impl CommonConfigsWrapper { + pub(crate) fn try_load>(path: P) -> anyhow::Result { + 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 { + 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 { + 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") + } + } +} diff --git a/clients/credential/Cargo.toml b/common/credential-utils/Cargo.toml similarity index 54% rename from clients/credential/Cargo.toml rename to common/credential-utils/Cargo.toml index 8ceb14bc98..1949107036 100644 --- a/clients/credential/Cargo.toml +++ b/common/credential-utils/Cargo.toml @@ -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" } diff --git a/common/credential-utils/src/errors.rs b/common/credential-utils/src/errors.rs new file mode 100644 index 0000000000..6407929a99 --- /dev/null +++ b/common/credential-utils/src/errors.rs @@ -0,0 +1,31 @@ +// Copyright 2023 - Nym Technologies SA +// 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 = std::result::Result; + +#[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), +} diff --git a/common/credential-utils/src/lib.rs b/common/credential-utils/src/lib.rs new file mode 100644 index 0000000000..6e5fbc3158 --- /dev/null +++ b/common/credential-utils/src/lib.rs @@ -0,0 +1,5 @@ +pub mod errors; +pub mod recovery_storage; +pub mod utils; + +pub use errors::{Error, Result}; diff --git a/common/credential-utils/src/recovery_storage.rs b/common/credential-utils/src/recovery_storage.rs new file mode 100644 index 0000000000..1793553c62 --- /dev/null +++ b/common/credential-utils/src/recovery_storage.rs @@ -0,0 +1,64 @@ +// Copyright 2023 - Nym Technologies SA +// 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 { + create_dir_all(&recovery_dir)?; + Ok(Self { recovery_dir }) + } + + pub fn unconsumed_vouchers(&self) -> Result> { + 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 { + 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)?) + } +} diff --git a/common/credential-utils/src/utils.rs b/common/credential-utils/src/utils.rs new file mode 100644 index 0000000000..2f92b53c90 --- /dev/null +++ b/common/credential-utils/src/utils.rs @@ -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( + 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(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( + client: &C, + recovery_storage: &RecoveryStorage, + shared_storage: &PersistentStorage, +) -> Result +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::()?; + + 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) +} diff --git a/common/credentials/Cargo.toml b/common/credentials/Cargo.toml index 1f4c444b31..aff9cf01ab 100644 --- a/common/credentials/Cargo.toml +++ b/common/credentials/Cargo.toml @@ -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" } diff --git a/common/credentials/src/coconut/utils.rs b/common/credentials/src/coconut/utils.rs index 025297f167..b7fda42f73 100644 --- a/common/credentials/src/coconut/utils.rs +++ b/common/credentials/src/coconut/utils.rs @@ -1,6 +1,10 @@ // Copyright 2021 - Nym Technologies SA // 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 { @@ -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); diff --git a/common/network-defaults/src/lib.rs b/common/network-defaults/src/lib.rs index 1704608043..fe353b16ae 100644 --- a/common/network-defaults/src/lib.rs +++ b/common/network-defaults/src/lib.rs @@ -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; @@ -388,7 +388,7 @@ fn fix_deprecated_environmental_variables() { } } -pub fn setup_env(config_env_file: Option<&PathBuf>) { +pub fn setup_env>(config_env_file: Option

    ) { match std::env::var(var_names::CONFIGURED) { // if the configuration is not already set in the env vars Err(std::env::VarError::NotPresent) => { diff --git a/explorer-api/src/main.rs b/explorer-api/src/main.rs index 0df77aeaf9..1c00c1548f 100644 --- a/explorer-api/src/main.rs +++ b/explorer-api/src/main.rs @@ -36,7 +36,7 @@ async fn main() { dotenv().ok(); setup_logging(); let args = commands::Cli::parse(); - setup_env(args.config_env_file.as_ref()); + setup_env(args.config_env_file); let mut explorer_api = ExplorerApi::new(); explorer_api.run().await; } diff --git a/nym-connect/desktop/Cargo.lock b/nym-connect/desktop/Cargo.lock index e915498a01..ecfe3bf851 100644 --- a/nym-connect/desktop/Cargo.lock +++ b/nym-connect/desktop/Cargo.lock @@ -4068,6 +4068,7 @@ version = "0.1.0" dependencies = [ "bls12_381 0.5.0", "cosmrs", + "log", "nym-api-requests", "nym-coconut-interface", "nym-crypto", diff --git a/sdk/lib/socks5-listener/src/lib.rs b/sdk/lib/socks5-listener/src/lib.rs index 1a3e22ca6c..98dac2755d 100644 --- a/sdk/lib/socks5-listener/src/lib.rs +++ b/sdk/lib/socks5-listener/src/lib.rs @@ -58,7 +58,7 @@ async fn is_shutdown_handle_set() -> bool { fn set_default_env() { if !ENV_SET.swap(true, Ordering::SeqCst) { - setup_env(None); + setup_env::<&str>(None); } } diff --git a/sdk/rust/nym-sdk/Cargo.toml b/sdk/rust/nym-sdk/Cargo.toml index f3d1d47aae..6986e0ef15 100644 --- a/sdk/rust/nym-sdk/Cargo.toml +++ b/sdk/rust/nym-sdk/Cargo.toml @@ -14,6 +14,7 @@ nym-gateway-requests = { path = "../../../gateway/gateway-requests" } nym-bandwidth-controller = { path = "../../../common/bandwidth-controller" } nym-credentials = { path = "../../../common/credentials" } nym-credential-storage = { path = "../../../common/credential-storage" } +nym-credential-utils = { path = "../../../common/credential-utils" } nym-network-defaults = { path = "../../../common/network-defaults" } nym-sphinx = { path = "../../../common/nymsphinx" } nym-task = { path = "../../../common/task" } @@ -30,11 +31,12 @@ url = { workspace = true } toml = "0.5.10" [dev-dependencies] +anyhow = { workspace = true } dotenvy = { workspace = true } pretty_env_logger = "0.4.0" reqwest = { workspace = true, features = ["json", "socks"] } thiserror = { workspace = true } -tokio = { version = "1", features = ["full"] } +tokio = { workspace = true, features = ["full"] } nym-bin-common = { path = "../../../common/bin-common" } # extra dependencies for libp2p examples diff --git a/sdk/rust/nym-sdk/examples/bandwidth.rs b/sdk/rust/nym-sdk/examples/bandwidth.rs index b7f452fa92..377dd48527 100644 --- a/sdk/rust/nym-sdk/examples/bandwidth.rs +++ b/sdk/rust/nym-sdk/examples/bandwidth.rs @@ -1,46 +1,43 @@ +use futures::StreamExt; +use nym_network_defaults::setup_env; use nym_sdk::mixnet; use nym_sdk::mixnet::MixnetMessageSender; #[tokio::main] -async fn main() { +async fn main() -> anyhow::Result<()> { nym_bin_common::logging::setup_logging(); // right now, only sandbox has coconut setup // this should be run from the `sdk/rust/nym-sdk` directory - dotenvy::from_path("../../../envs/sandbox.env").unwrap(); + setup_env(Some("../../../envs/sandbox.env")); let sandbox_network = mixnet::NymNetworkDetails::new_from_env(); + let mnemonic = String::from("my super secret mnemonic"); let mixnet_client = mixnet::MixnetClientBuilder::new_ephemeral() .network_details(sandbox_network) .enable_credentials_mode() .build() - .await - .unwrap(); + .await?; - let bandwidth_client = mixnet_client - .create_bandwidth_client(String::from("very secret mnemonic")) - .unwrap(); + let bandwidth_client = mixnet_client.create_bandwidth_client(mnemonic)?; // Get a bandwidth credential worth 1000000 unym for the mixnet_client - bandwidth_client.acquire(1000000).await.unwrap(); + bandwidth_client.acquire(1000000).await?; // Connect using paid bandwidth credential - let mut client = mixnet_client.connect_to_mixnet().await.unwrap(); + let mut client = mixnet_client.connect_to_mixnet().await?; let our_address = client.nym_address(); // Send a message throughout the mixnet to ourselves client .send_plain_message(*our_address, "hello there") - .await - .unwrap(); + .await?; println!("Waiting for message"); - if let Some(received) = client.wait_for_messages().await { - for r in received { - println!("Received: {}", String::from_utf8_lossy(&r.message)); - } - } + let received = client.next().await.unwrap(); + println!("Received: {}", String::from_utf8_lossy(&received.message)); client.disconnect().await; + Ok(()) } diff --git a/sdk/rust/nym-sdk/src/mixnet/paths.rs b/sdk/rust/nym-sdk/src/mixnet/paths.rs index 5c09752280..288218f5c5 100644 --- a/sdk/rust/nym-sdk/src/mixnet/paths.rs +++ b/sdk/rust/nym-sdk/src/mixnet/paths.rs @@ -51,7 +51,8 @@ impl StoragePaths { /// /// This function will return an error if it is passed a path to an existing file instead of a /// directory. - pub fn new_from_dir(dir: &Path) -> Result { + pub fn new_from_dir>(dir: P) -> Result { + let dir = dir.as_ref(); if dir.is_file() { return Err(Error::ExpectedDirectory(dir.to_owned())); } diff --git a/tools/nym-cli/src/coconut/mod.rs b/tools/nym-cli/src/coconut/mod.rs new file mode 100644 index 0000000000..ea4fd45381 --- /dev/null +++ b/tools/nym-cli/src/coconut/mod.rs @@ -0,0 +1,26 @@ +use nym_cli_commands::context::{create_query_client, create_signing_client, ClientArgs}; +use nym_network_defaults::NymNetworkDetails; + +pub(crate) async fn execute( + global_args: ClientArgs, + coconut: nym_cli_commands::coconut::Coconut, + network_details: &NymNetworkDetails, +) -> anyhow::Result<()> { + match coconut.command { + nym_cli_commands::coconut::CoconutCommands::IssueCredentials(args) => { + nym_cli_commands::coconut::issue_credentials::execute( + args, + create_signing_client(global_args, network_details)?, + ) + .await? + } + nym_cli_commands::coconut::CoconutCommands::RecoverCredentials(args) => { + nym_cli_commands::coconut::recover_credentials::execute( + args, + create_query_client(network_details)?, + ) + .await? + } + } + Ok(()) +} diff --git a/tools/nym-cli/src/main.rs b/tools/nym-cli/src/main.rs index a5acae77b9..16b771d33e 100644 --- a/tools/nym-cli/src/main.rs +++ b/tools/nym-cli/src/main.rs @@ -7,6 +7,7 @@ use nym_bin_common::logging::setup_logging; use nym_cli_commands::context::{get_network_details, ClientArgs}; use nym_validator_client::nyxd::AccountId; +mod coconut; mod completion; mod validator; @@ -61,6 +62,8 @@ pub(crate) enum Commands { Account(nym_cli_commands::validator::account::Account), /// Sign and verify messages Signature(nym_cli_commands::validator::signature::Signature), + /// Coconut related stuff + Coconut(nym_cli_commands::coconut::Coconut), /// Query chain blocks Block(nym_cli_commands::validator::block::Block), /// Manage and execute WASM smart contracts @@ -101,6 +104,7 @@ async fn execute(cli: Cli) -> anyhow::Result<()> { Commands::Signature(signature) => { validator::signature::execute(signature, &network_details, mnemonic).await? } + Commands::Coconut(coconut) => coconut::execute(args, coconut, &network_details).await?, Commands::Block(block) => validator::block::execute(block, &network_details).await?, Commands::Cosmwasm(cosmwasm) => { validator::cosmwasm::execute(args, cosmwasm, &network_details).await? From 84d893198b2992f9ceb2a3082bf5560cbd0b0db7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jon=20H=C3=A4ggblad?= Date: Sat, 26 Aug 2023 21:09:33 +0200 Subject: [PATCH 340/361] Revert "Replace Arc with Rc on clippy's suggestion" This reverts commit b050ae72de668f56b1d922cb0e2fe719a44cf6bc. --- clients/webassembly/src/storage/mod.rs | 7 +++---- clients/webassembly/src/tester/mod.rs | 7 +++---- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/clients/webassembly/src/storage/mod.rs b/clients/webassembly/src/storage/mod.rs index febbd9ecd5..f4ab912657 100644 --- a/clients/webassembly/src/storage/mod.rs +++ b/clients/webassembly/src/storage/mod.rs @@ -1,8 +1,6 @@ // Copyright 2023 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use std::rc::Rc; - use crate::client::config::Config; use crate::storage::error::ClientStorageError; use js_sys::Promise; @@ -10,6 +8,7 @@ use nym_client_core::client::base_client::storage::gateway_details::PersistedGat use nym_crypto::asymmetric::{encryption, identity}; use nym_gateway_client::SharedKeys; use nym_sphinx::acknowledgements::AckKey; +use std::sync::Arc; use wasm_bindgen::prelude::*; use wasm_bindgen_futures::future_to_promise; use wasm_utils::storage::{IdbVersionChangeEvent, WasmStorage}; @@ -44,7 +43,7 @@ mod v1 { pub struct ClientStorage { #[allow(dead_code)] pub(crate) name: String, - pub(crate) inner: Rc, + pub(crate) inner: Arc, } #[wasm_bindgen] @@ -89,7 +88,7 @@ impl ClientStorage { .await?; Ok(ClientStorage { - inner: Rc::new(inner), + inner: Arc::new(inner), name, }) } diff --git a/clients/webassembly/src/tester/mod.rs b/clients/webassembly/src/tester/mod.rs index 6433c029c7..ba70062f3b 100644 --- a/clients/webassembly/src/tester/mod.rs +++ b/clients/webassembly/src/tester/mod.rs @@ -29,7 +29,6 @@ use nym_validator_client::client::IdentityKey; use nym_validator_client::QueryReqwestRpcNyxdClient; use rand::rngs::OsRng; use std::collections::HashSet; -use std::rc::Rc; use std::sync::atomic::{AtomicBool, AtomicU32, Ordering}; use std::sync::{Arc, Mutex as SyncMutex}; use std::time::Duration; @@ -43,7 +42,7 @@ pub(crate) mod helpers; pub type NodeTestMessage = TestMessage; type LockedGatewayClient = - Rc>>; + Arc>>; pub(crate) const DEFAULT_TEST_TIMEOUT: Duration = Duration::from_secs(10); pub(crate) const DEFAULT_TEST_PACKETS: u32 = 20; @@ -211,7 +210,7 @@ impl NymNodeTesterBuilder { test_in_progress: Arc::new(AtomicBool::new(false)), current_test_nonce: Default::default(), tester: Arc::new(SyncMutex::new(tester)), - gateway_client: Rc::new(AsyncMutex::new(gateway_client)), + gateway_client: Arc::new(AsyncMutex::new(gateway_client)), processed_receiver: ReceivedReceiverWrapper::new(processed_receiver), _task_manager: task_manager, }) @@ -344,7 +343,7 @@ impl NymNodeTester { )); let processed_receiver_clone = self.processed_receiver.clone(); - let gateway_client_clone = Rc::clone(&self.gateway_client); + let gateway_client_clone = Arc::clone(&self.gateway_client); let tester_marker = TestMarker::new(Arc::clone(&self.test_in_progress)); // start doing async things (send packets and watch for anything coming back) From 2e7ddcb195da42b8dab2f32f20be1d29b5764a9d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jon=20H=C3=A4ggblad?= Date: Sat, 26 Aug 2023 21:49:14 +0200 Subject: [PATCH 341/361] Allow arc_with_non_send_sync in wasm client --- clients/webassembly/src/lib.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/clients/webassembly/src/lib.rs b/clients/webassembly/src/lib.rs index af6d657940..a4d3aeaad4 100644 --- a/clients/webassembly/src/lib.rs +++ b/clients/webassembly/src/lib.rs @@ -1,6 +1,10 @@ // Copyright 2021-2023 - Nym Technologies SA // 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")] From 0812a0f599018e46311d4d54e07637211b6cb43f Mon Sep 17 00:00:00 2001 From: pierre Date: Mon, 28 Aug 2023 14:37:32 +0200 Subject: [PATCH 342/361] disable privacy switch on active connection --- nym-connect/desktop/src/pages/menu/PrivacyLevelSettings.tsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/nym-connect/desktop/src/pages/menu/PrivacyLevelSettings.tsx b/nym-connect/desktop/src/pages/menu/PrivacyLevelSettings.tsx index 9935ef8f4c..81582a2797 100644 --- a/nym-connect/desktop/src/pages/menu/PrivacyLevelSettings.tsx +++ b/nym-connect/desktop/src/pages/menu/PrivacyLevelSettings.tsx @@ -2,9 +2,10 @@ import React, { ChangeEvent, useState } from 'react'; import * as Sentry from '@sentry/react'; import { Box, FormControl, FormControlLabel, FormHelperText, Stack, Switch, Typography } from '@mui/material'; import { useClientContext } from '../../context/main'; +import { ConnectionStatusKind } from '../../types'; export const PrivacyLevelSettings = () => { - const { userData, setPrivacyLevel } = useClientContext(); + const { userData, setPrivacyLevel, connectionStatus } = useClientContext(); const [speedBoost, setSpeedBoost] = useState(userData?.privacy_level !== 'High'); const [loading, setLoading] = useState(false); @@ -29,7 +30,7 @@ export const PrivacyLevelSettings = () => { From cfc86ba9f5aaa3991b5d7ca68d1c14c4bd87fcb6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jon=20H=C3=A4ggblad?= Date: Mon, 28 Aug 2023 11:32:55 +0200 Subject: [PATCH 343/361] nym-connect: select gateway based on latency in medium mode --- .../topology_control/geo_aware_provider.rs | 2 +- common/client-core/src/config/mod.rs | 9 +++++ common/client-core/src/init/helpers.rs | 2 +- nym-connect/desktop/Cargo.lock | 2 + nym-connect/desktop/Cargo.toml | 1 + nym-connect/desktop/src-tauri/Cargo.toml | 2 + nym-connect/desktop/src-tauri/src/main.rs | 4 +- .../src-tauri/src/operations/directory/mod.rs | 39 +++++++++++++++++++ nym-connect/desktop/src-tauri/src/tasks.rs | 2 +- nym-connect/desktop/src/context/main.tsx | 7 +++- 10 files changed, 65 insertions(+), 5 deletions(-) diff --git a/common/client-core/src/client/topology_control/geo_aware_provider.rs b/common/client-core/src/client/topology_control/geo_aware_provider.rs index dabece05e5..5239f87411 100644 --- a/common/client-core/src/client/topology_control/geo_aware_provider.rs +++ b/common/client-core/src/client/topology_control/geo_aware_provider.rs @@ -303,7 +303,7 @@ impl GeoAwareTopologyProvider { 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()); diff --git a/common/client-core/src/config/mod.rs b/common/client-core/src/config/mod.rs index f3cee8200f..c1d5b9d3cb 100644 --- a/common/client-core/src/config/mod.rs +++ b/common/client-core/src/config/mod.rs @@ -498,6 +498,15 @@ pub enum GroupBy { 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 { fn default() -> Self { Topology { diff --git a/common/client-core/src/init/helpers.rs b/common/client-core/src/init/helpers.rs index adfd9ad623..6237321a1b 100644 --- a/common/client-core/src/init/helpers.rs +++ b/common/client-core/src/init/helpers.rs @@ -149,7 +149,7 @@ async fn measure_latency(gateway: &gateway::Node) -> Result( +pub async fn choose_gateway_by_latency( rng: &mut R, gateways: &[gateway::Node], ) -> Result { diff --git a/nym-connect/desktop/Cargo.lock b/nym-connect/desktop/Cargo.lock index ecfe3bf851..e2b2acee27 100644 --- a/nym-connect/desktop/Cargo.lock +++ b/nym-connect/desktop/Cargo.lock @@ -4014,8 +4014,10 @@ dependencies = [ "nym-socks5-client-core", "nym-sphinx", "nym-task", + "nym-topology", "nym-validator-client", "pretty_env_logger", + "rand 0.7.3", "rand 0.8.5", "reqwest", "rust-embed", diff --git a/nym-connect/desktop/Cargo.toml b/nym-connect/desktop/Cargo.toml index 3ee3f20be3..5322fc260c 100644 --- a/nym-connect/desktop/Cargo.toml +++ b/nym-connect/desktop/Cargo.toml @@ -1,2 +1,3 @@ [workspace] members = ["src-tauri"] +resolver = "2" diff --git a/nym-connect/desktop/src-tauri/Cargo.toml b/nym-connect/desktop/src-tauri/Cargo.toml index 630adca45b..10a049af6a 100644 --- a/nym-connect/desktop/src-tauri/Cargo.toml +++ b/nym-connect/desktop/src-tauri/Cargo.toml @@ -30,6 +30,7 @@ itertools = "0.10.5" log = { version = "0.4", features = ["serde"] } pretty_env_logger = "0.4.0" rand = "0.8" +rand-07 = { package = "rand", version = "0.7.3" } reqwest = { version = "0.11.18", features = ["json", "socks"] } rust-embed = { version = "6.4.2", features = ["include-exclude"] } serde = { version = "1.0", features = ["derive"] } @@ -58,6 +59,7 @@ nym-bin-common = { path = "../../../common/bin-common"} nym-socks5-client-core = { path = "../../../common/socks5-client-core" } nym-sphinx = { path = "../../../common/nymsphinx" } nym-task = { path = "../../../common/task" } +nym-topology = { path = "../../../common/topology" } nym-validator-client = { path = "../../../common/client-libs/validator-client" } [dev-dependencies] diff --git a/nym-connect/desktop/src-tauri/src/main.rs b/nym-connect/desktop/src-tauri/src/main.rs index fc36d44c96..9897caf0e8 100644 --- a/nym-connect/desktop/src-tauri/src/main.rs +++ b/nym-connect/desktop/src-tauri/src/main.rs @@ -86,8 +86,10 @@ fn main() { crate::operations::connection::status::get_connection_status, crate::operations::connection::status::get_gateway_connection_status, crate::operations::connection::status::start_connection_health_check_task, - crate::operations::directory::get_services, + crate::operations::directory::get_gateway_with_low_latency, + crate::operations::directory::get_gateway_with_low_latency_from_list, crate::operations::directory::get_gateways, + crate::operations::directory::get_services, crate::operations::export::export_keys, crate::operations::window::hide_window, crate::operations::growth::test_and_earn::growth_tne_get_client_id, diff --git a/nym-connect/desktop/src-tauri/src/operations/directory/mod.rs b/nym-connect/desktop/src-tauri/src/operations/directory/mod.rs index 3842720161..522488d060 100644 --- a/nym-connect/desktop/src-tauri/src/operations/directory/mod.rs +++ b/nym-connect/desktop/src-tauri/src/operations/directory/mod.rs @@ -11,6 +11,7 @@ use nym_api_requests::models::GatewayBondAnnotated; use nym_bin_common::version_checker::is_minor_version_compatible; use nym_config::defaults::var_names::{NETWORK_NAME, NYM_API}; use nym_contracts_common::types::Percent; +use nym_topology::gateway; use nym_validator_client::nym_api::Client as ApiClient; use std::str::FromStr; use std::sync::Arc; @@ -164,3 +165,41 @@ pub async fn get_gateways() -> Result> { Ok(filtered_gateways) } + +async fn select_gateway_by_latency(gateways: Vec) -> Result { + let gateways_as_nodes: Vec = gateways + .into_iter() + .filter_map(|g| g.gateway_bond.try_into().ok()) + .collect(); + + let mut rng = rand_07::rngs::OsRng; + let selected_gateway = + nym_client_core::init::helpers::choose_gateway_by_latency(&mut rng, &gateways_as_nodes) + .await?; + Ok(selected_gateway) +} + +#[tauri::command] +pub async fn get_gateway_with_low_latency() -> Result { + let all_gateways = fetch_gateways().await?; + let selected_gateway = select_gateway_by_latency(all_gateways).await?; + Ok(Gateway { + identity: selected_gateway.identity().to_base58_string(), + }) +} + +#[tauri::command] +pub async fn get_gateway_with_low_latency_from_list(gateways: Vec) -> Result { + log::debug!("Selecting a gateway with low latency"); + let gateways = gateways.into_iter().map(|g| g.identity).collect_vec(); + let all_gateways = fetch_gateways().await?; + let gateways_union_set: Vec = all_gateways + .into_iter() + .filter(|g| gateways.contains(g.identity())) + .collect(); + let selected_gateway = select_gateway_by_latency(gateways_union_set).await?; + log::debug!("Selected gateway: {}", selected_gateway); + Ok(Gateway { + identity: selected_gateway.identity().to_base58_string(), + }) +} diff --git a/nym-connect/desktop/src-tauri/src/tasks.rs b/nym-connect/desktop/src-tauri/src/tasks.rs index 4af366ca48..b18cb8277d 100644 --- a/nym-connect/desktop/src-tauri/src/tasks.rs +++ b/nym-connect/desktop/src-tauri/src/tasks.rs @@ -57,7 +57,7 @@ fn override_config_from_env(config: &mut Config, privacy_level: &PrivacyLevel) { .provider_mix_address .parse() .expect("failed to parse provider mix address"); - log::warn!("Using geo-aware mixnode selection baseon the location of: {address}"); + log::warn!("Using geo-aware mixnode selection based on the location of: {address}"); config .core .base diff --git a/nym-connect/desktop/src/context/main.tsx b/nym-connect/desktop/src/context/main.tsx index 288c9be5a9..3eb88f63c6 100644 --- a/nym-connect/desktop/src/context/main.tsx +++ b/nym-connect/desktop/src/context/main.tsx @@ -214,7 +214,12 @@ export const ClientContextProvider: FCWithChildren = ({ children }) => { const setGateway = async () => { if (gateways) { - const randomGateway = getRandomFromList(gateways); + let randomGateway; + if (userData?.privacy_level === 'Medium') { + randomGateway = await invoke('get_gateway_with_low_latency_from_list', { gateways }); + } else { + randomGateway = getRandomFromList(gateways); + } const withUserDefinitions = await buildGateway(randomGateway); await invoke('set_gateway', { gateway: shouldUseUserGateway ? userDefinedGateway.address : withUserDefinitions.identity, From 7f0a02f6ec1559b5b54f168d1f52755c0f3fe497 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jon=20H=C3=A4ggblad?= Date: Mon, 28 Aug 2023 14:10:51 +0200 Subject: [PATCH 344/361] Also filter on performance --- nym-connect/desktop/src-tauri/src/main.rs | 2 +- .../src-tauri/src/operations/directory/mod.rs | 73 ++++++++++++------- nym-connect/desktop/src/context/main.tsx | 2 +- 3 files changed, 47 insertions(+), 30 deletions(-) diff --git a/nym-connect/desktop/src-tauri/src/main.rs b/nym-connect/desktop/src-tauri/src/main.rs index 9897caf0e8..9a3c8c7b6c 100644 --- a/nym-connect/desktop/src-tauri/src/main.rs +++ b/nym-connect/desktop/src-tauri/src/main.rs @@ -87,9 +87,9 @@ fn main() { crate::operations::connection::status::get_gateway_connection_status, crate::operations::connection::status::start_connection_health_check_task, crate::operations::directory::get_gateway_with_low_latency, - crate::operations::directory::get_gateway_with_low_latency_from_list, crate::operations::directory::get_gateways, crate::operations::directory::get_services, + crate::operations::directory::select_gateway_with_low_latency_from_list, crate::operations::export::export_keys, crate::operations::window::hide_window, crate::operations::growth::test_and_earn::growth_tne_get_client_id, diff --git a/nym-connect/desktop/src-tauri/src/operations/directory/mod.rs b/nym-connect/desktop/src-tauri/src/operations/directory/mod.rs index 522488d060..05e0b9de6e 100644 --- a/nym-connect/desktop/src-tauri/src/operations/directory/mod.rs +++ b/nym-connect/desktop/src-tauri/src/operations/directory/mod.rs @@ -135,35 +135,16 @@ async fn fetch_gateways() -> Result> { Ok(gateways) } -#[tauri::command] -pub async fn get_gateways() -> Result> { - log::trace!("Fetching gateways"); - let all_gateways = fetch_gateways().await?; - log::trace!("Received: {:#?}", all_gateways); - - let filtered_gateways = all_gateways - .iter() +fn filter_out_low_performance_gateways( + gateways: Vec, +) -> Vec { + gateways + .into_iter() .filter(|g| { g.node_performance.most_recent > Percent::from_percentage_value(GATEWAY_PERFORMANCE_SCORE_THRESHOLD).unwrap() }) - .map(|g| Gateway { - identity: g.identity().clone(), - }) - .collect_vec(); - log::trace!("Filtered: {:#?}", filtered_gateways); - - if filtered_gateways.is_empty() { - log::warn!("No gateways with high enough performance score found! Using all gateways instead as fallback"); - return Ok(all_gateways - .iter() - .map(|g| Gateway { - identity: g.identity().clone(), - }) - .collect_vec()); - } - - Ok(filtered_gateways) + .collect() } async fn select_gateway_by_latency(gateways: Vec) -> Result { @@ -179,17 +160,52 @@ async fn select_gateway_by_latency(gateways: Vec) -> Resul Ok(selected_gateway) } +// Get all gateways satisfying the performance threshold. +#[tauri::command] +pub async fn get_gateways() -> Result> { + log::trace!("Fetching gateways"); + let all_gateways = fetch_gateways().await?; + log::trace!("Received: {:#?}", all_gateways); + + let gateways_filtered = filter_out_low_performance_gateways(all_gateways.clone()) + .into_iter() + .map(|g| Gateway { + identity: g.identity().clone(), + }) + .collect_vec(); + log::trace!("Filtered: {:#?}", gateways_filtered); + + if gateways_filtered.is_empty() { + log::warn!("No gateways with high enough performance score found! Using all gateways instead as fallback"); + return Ok(all_gateways + .iter() + .map(|g| Gateway { + identity: g.identity().clone(), + }) + .collect_vec()); + } + + Ok(gateways_filtered) +} + +// Lookup and select a single gateway with low latency. #[tauri::command] pub async fn get_gateway_with_low_latency() -> Result { + log::trace!("Fetching gateways"); let all_gateways = fetch_gateways().await?; - let selected_gateway = select_gateway_by_latency(all_gateways).await?; + log::trace!("Received: {:#?}", all_gateways); + + let gateways_filtered = filter_out_low_performance_gateways(all_gateways); + let selected_gateway = select_gateway_by_latency(gateways_filtered).await?; + log::debug!("Selected gateway: {}", selected_gateway); Ok(Gateway { identity: selected_gateway.identity().to_base58_string(), }) } +// From a given list of gateways, select the one with low latency. #[tauri::command] -pub async fn get_gateway_with_low_latency_from_list(gateways: Vec) -> Result { +pub async fn select_gateway_with_low_latency_from_list(gateways: Vec) -> Result { log::debug!("Selecting a gateway with low latency"); let gateways = gateways.into_iter().map(|g| g.identity).collect_vec(); let all_gateways = fetch_gateways().await?; @@ -197,7 +213,8 @@ pub async fn get_gateway_with_low_latency_from_list(gateways: Vec) -> R .into_iter() .filter(|g| gateways.contains(g.identity())) .collect(); - let selected_gateway = select_gateway_by_latency(gateways_union_set).await?; + let gateways_filtered = filter_out_low_performance_gateways(gateways_union_set); + let selected_gateway = select_gateway_by_latency(gateways_filtered).await?; log::debug!("Selected gateway: {}", selected_gateway); Ok(Gateway { identity: selected_gateway.identity().to_base58_string(), diff --git a/nym-connect/desktop/src/context/main.tsx b/nym-connect/desktop/src/context/main.tsx index 3eb88f63c6..2f553b5153 100644 --- a/nym-connect/desktop/src/context/main.tsx +++ b/nym-connect/desktop/src/context/main.tsx @@ -216,7 +216,7 @@ export const ClientContextProvider: FCWithChildren = ({ children }) => { if (gateways) { let randomGateway; if (userData?.privacy_level === 'Medium') { - randomGateway = await invoke('get_gateway_with_low_latency_from_list', { gateways }); + randomGateway = await invoke('select_gateway_with_low_latency_from_list', { gateways }); } else { randomGateway = getRandomFromList(gateways); } From b57df35f8ce561c9f567f29515eb8c1578ce2b74 Mon Sep 17 00:00:00 2001 From: benedettadavico Date: Tue, 29 Aug 2023 10:37:22 +0200 Subject: [PATCH 345/361] update changelog and bump versions --- CHANGELOG.md | 20 +++++++++++++++++++ Cargo.lock | 18 ++++++++--------- clients/native/Cargo.toml | 2 +- clients/socks5/Cargo.toml | 2 +- explorer-api/Cargo.toml | 2 +- gateway/Cargo.toml | 2 +- mixnode/Cargo.toml | 2 +- nym-api/Cargo.toml | 2 +- nym-connect/desktop/CHANGELOG.md | 6 ++++++ nym-connect/desktop/package.json | 2 +- nym-connect/desktop/src-tauri/Cargo.toml | 2 +- nym-connect/desktop/src-tauri/tauri.conf.json | 2 +- .../network-requester/Cargo.toml | 2 +- .../network-statistics/Cargo.toml | 2 +- tools/nym-cli/Cargo.toml | 2 +- 15 files changed, 47 insertions(+), 21 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e728e00506..3df8920b43 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,26 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https:// ## [Unreleased] +## [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 + ## [1.1.28] (2023-08-22) - [final step3]: add [rust] support to nyxd client in wasm ([#3743]) diff --git a/Cargo.lock b/Cargo.lock index 1232af2f51..6a09ca15d0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2768,7 +2768,7 @@ checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" [[package]] name = "explorer-api" -version = "1.1.26" +version = "1.1.27" dependencies = [ "chrono", "clap 4.3.21", @@ -5638,7 +5638,7 @@ dependencies = [ [[package]] name = "nym-api" -version = "1.1.27" +version = "1.1.28" dependencies = [ "actix-web", "anyhow", @@ -5785,7 +5785,7 @@ dependencies = [ [[package]] name = "nym-cli" -version = "1.1.26" +version = "1.1.27" dependencies = [ "anyhow", "base64 0.13.1", @@ -5858,7 +5858,7 @@ dependencies = [ [[package]] name = "nym-client" -version = "1.1.26" +version = "1.1.27" dependencies = [ "clap 4.3.21", "dirs 4.0.0", @@ -6153,7 +6153,7 @@ dependencies = [ [[package]] name = "nym-gateway" -version = "1.1.26" +version = "1.1.27" dependencies = [ "anyhow", "async-trait", @@ -6308,7 +6308,7 @@ dependencies = [ [[package]] name = "nym-mixnode" -version = "1.1.27" +version = "1.1.28" dependencies = [ "anyhow", "bs58 0.4.0", @@ -6426,7 +6426,7 @@ dependencies = [ [[package]] name = "nym-network-requester" -version = "1.1.26" +version = "1.1.27" dependencies = [ "anyhow", "async-file-watcher", @@ -6473,7 +6473,7 @@ dependencies = [ [[package]] name = "nym-network-statistics" -version = "1.1.26" +version = "1.1.27" dependencies = [ "dirs 4.0.0", "log", @@ -6638,7 +6638,7 @@ dependencies = [ [[package]] name = "nym-socks5-client" -version = "1.1.26" +version = "1.1.27" dependencies = [ "clap 4.3.21", "lazy_static", diff --git a/clients/native/Cargo.toml b/clients/native/Cargo.toml index b94b1e7b76..351f8fb295 100644 --- a/clients/native/Cargo.toml +++ b/clients/native/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "nym-client" -version = "1.1.26" +version = "1.1.27" authors = ["Dave Hrycyszyn ", "Jędrzej Stuczyński "] description = "Implementation of the Nym Client" edition = "2021" diff --git a/clients/socks5/Cargo.toml b/clients/socks5/Cargo.toml index 8532f9d2b7..a52422b5d0 100644 --- a/clients/socks5/Cargo.toml +++ b/clients/socks5/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "nym-socks5-client" -version = "1.1.26" +version = "1.1.27" authors = ["Dave Hrycyszyn "] description = "A SOCKS5 localhost proxy that converts incoming messages to Sphinx and sends them to a Nym address" edition = "2021" diff --git a/explorer-api/Cargo.toml b/explorer-api/Cargo.toml index a39e3cb52b..10ca849b3d 100644 --- a/explorer-api/Cargo.toml +++ b/explorer-api/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "explorer-api" -version = "1.1.26" +version = "1.1.27" edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/gateway/Cargo.toml b/gateway/Cargo.toml index 2b71aa1542..8e907a9eee 100644 --- a/gateway/Cargo.toml +++ b/gateway/Cargo.toml @@ -3,7 +3,7 @@ [package] name = "nym-gateway" -version = "1.1.26" +version = "1.1.27" authors = [ "Dave Hrycyszyn ", "Jędrzej Stuczyński ", diff --git a/mixnode/Cargo.toml b/mixnode/Cargo.toml index ec0b3f5f94..0a9edd82b9 100644 --- a/mixnode/Cargo.toml +++ b/mixnode/Cargo.toml @@ -3,7 +3,7 @@ [package] name = "nym-mixnode" -version = "1.1.27" +version = "1.1.28" authors = [ "Dave Hrycyszyn ", "Jędrzej Stuczyński ", diff --git a/nym-api/Cargo.toml b/nym-api/Cargo.toml index 506c0dda9a..29be65e7bb 100644 --- a/nym-api/Cargo.toml +++ b/nym-api/Cargo.toml @@ -3,7 +3,7 @@ [package] name = "nym-api" -version = "1.1.27" +version = "1.1.28" authors = [ "Dave Hrycyszyn ", "Jędrzej Stuczyński ", diff --git a/nym-connect/desktop/CHANGELOG.md b/nym-connect/desktop/CHANGELOG.md index 20534c13ef..c7c62a72fc 100644 --- a/nym-connect/desktop/CHANGELOG.md +++ b/nym-connect/desktop/CHANGELOG.md @@ -2,6 +2,12 @@ ## [Unreleased] +## [v1.1.29-snickers] (2023-08-29) + +- NymConnect sometimes fails to connect because the gateway it fetches from the validator-api to use is running an old version (of the gateway binary) ([#3788]) + +[#3788]: https://github.com/nymtech/nym/issues/3788 + ## [1.1.18] (2023-08-22) - refactor(nc-desktop): use userdata storage to save user gateway&sp ([#3723]) diff --git a/nym-connect/desktop/package.json b/nym-connect/desktop/package.json index a3a74ab218..a69bc51486 100644 --- a/nym-connect/desktop/package.json +++ b/nym-connect/desktop/package.json @@ -1,6 +1,6 @@ { "name": "@nym/nym-connect", - "version": "1.1.18", + "version": "1.1.19", "main": "index.js", "license": "MIT", "scripts": { diff --git a/nym-connect/desktop/src-tauri/Cargo.toml b/nym-connect/desktop/src-tauri/Cargo.toml index 630adca45b..ae940cd32d 100644 --- a/nym-connect/desktop/src-tauri/Cargo.toml +++ b/nym-connect/desktop/src-tauri/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "nym-connect" -version = "1.1.18" +version = "1.1.19" description = "nym-connect" authors = ["Nym Technologies SA"] license = "" diff --git a/nym-connect/desktop/src-tauri/tauri.conf.json b/nym-connect/desktop/src-tauri/tauri.conf.json index bfbceb21be..555daaa1b7 100644 --- a/nym-connect/desktop/src-tauri/tauri.conf.json +++ b/nym-connect/desktop/src-tauri/tauri.conf.json @@ -1,7 +1,7 @@ { "package": { "productName": "nym-connect", - "version": "1.1.18" + "version": "1.1.19" }, "build": { "distDir": "../dist", diff --git a/service-providers/network-requester/Cargo.toml b/service-providers/network-requester/Cargo.toml index 9f1fc07c97..cf66ae325e 100644 --- a/service-providers/network-requester/Cargo.toml +++ b/service-providers/network-requester/Cargo.toml @@ -3,7 +3,7 @@ [package] name = "nym-network-requester" -version = "1.1.26" +version = "1.1.27" authors.workspace = true edition.workspace = true rust-version = "1.65" diff --git a/service-providers/network-statistics/Cargo.toml b/service-providers/network-statistics/Cargo.toml index 7ff628cdc6..197ee6e49a 100644 --- a/service-providers/network-statistics/Cargo.toml +++ b/service-providers/network-statistics/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "nym-network-statistics" -version = "1.1.26" +version = "1.1.27" edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/tools/nym-cli/Cargo.toml b/tools/nym-cli/Cargo.toml index e63dcc8225..d571f87d7f 100644 --- a/tools/nym-cli/Cargo.toml +++ b/tools/nym-cli/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "nym-cli" -version = "1.1.26" +version = "1.1.27" authors.workspace = true edition = "2021" From a1482a28874b0c2b44025a7543e356824c9398e3 Mon Sep 17 00:00:00 2001 From: benedettadavico Date: Tue, 29 Aug 2023 10:37:51 +0200 Subject: [PATCH 346/361] update changelog and bump versions --- CHANGELOG.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3df8920b43..2df4f7e732 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,7 +24,7 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https:// [#3758]: https://github.com/nymtech/nym/pull/3758 [#3691]: https://github.com/nymtech/nym/pull/3691 -## [1.1.28] (2023-08-22) +## [v1.1.28] (2023-08-22) - [final step3]: add [rust] support to nyxd client in wasm ([#3743]) - Feature/ephemera upgrade ([#3791]) @@ -39,7 +39,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]) From 2df42e222c254ae8256916b9b0860b14337b2aa1 Mon Sep 17 00:00:00 2001 From: benedettadavico Date: Tue, 29 Aug 2023 14:02:41 +0200 Subject: [PATCH 347/361] version change NC --- nym-connect/desktop/CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nym-connect/desktop/CHANGELOG.md b/nym-connect/desktop/CHANGELOG.md index c7c62a72fc..ab9c1da100 100644 --- a/nym-connect/desktop/CHANGELOG.md +++ b/nym-connect/desktop/CHANGELOG.md @@ -2,7 +2,7 @@ ## [Unreleased] -## [v1.1.29-snickers] (2023-08-29) +## [v1.1.19-snickers] (2023-08-29) - NymConnect sometimes fails to connect because the gateway it fetches from the validator-api to use is running an old version (of the gateway binary) ([#3788]) From f518c8377b98806773f8e8e683955d98fdab1b3e Mon Sep 17 00:00:00 2001 From: mfahampshire Date: Tue, 29 Aug 2023 17:07:34 +0200 Subject: [PATCH 348/361] version bump --- documentation/dev-portal/book.toml | 4 ++-- documentation/docs/book.toml | 4 ++-- documentation/operators/book.toml | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/documentation/dev-portal/book.toml b/documentation/dev-portal/book.toml index 5946ace450..1026b8e77b 100644 --- a/documentation/dev-portal/book.toml +++ b/documentation/dev-portal/book.toml @@ -48,8 +48,8 @@ assets_version = "2.0.0" # do not edit: managed by `mdbook-admonish install` # https://gitlab.com/tglman/mdbook-variables/ [preprocessor.variables.variables] minimum_rust_version = "1.66" -platform_release_version = "v1.1.28" -wallet_release_version = "v1.2.7" +platform_release_version = "1.1.29" +wallet_release_version = "1.2.8" [preprocessor.last-changed] command = "mdbook-last-changed" diff --git a/documentation/docs/book.toml b/documentation/docs/book.toml index 0305c52c51..846bf5ee2a 100644 --- a/documentation/docs/book.toml +++ b/documentation/docs/book.toml @@ -48,8 +48,8 @@ assets_version = "2.0.0" # do not edit: managed by `mdbook-admonish install` # https://gitlab.com/tglman/mdbook-variables/ [preprocessor.variables.variables] minimum_rust_version = "1.66" -platform_release_version = "v1.1.28" -wallet_release_version = "v1.2.7" +platform_release_version = "1.1.29" +wallet_release_version = "1.2.8" [preprocessor.last-changed] command = "mdbook-last-changed" diff --git a/documentation/operators/book.toml b/documentation/operators/book.toml index d6c3a61c5b..02f3878356 100644 --- a/documentation/operators/book.toml +++ b/documentation/operators/book.toml @@ -48,8 +48,8 @@ assets_version = "2.0.0" # do not edit: managed by `mdbook-admonish install` # https://gitlab.com/tglman/mdbook-variables/ [preprocessor.variables.variables] minimum_rust_version = "1.66" -platform_release_version = "v1.1.28" -wallet_release_version = "v1.2.7" +platform_release_version = "1.1.29" +wallet_release_version = "1.2.8" [preprocessor.last-changed] command = "mdbook-last-changed" From ca613ad3aa22cd3afb283625afbb3d56c19b71a3 Mon Sep 17 00:00:00 2001 From: mfahampshire Date: Tue, 29 Aug 2023 17:07:52 +0200 Subject: [PATCH 349/361] change shebang to nixos-compatible one --- documentation/bump_versions.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/documentation/bump_versions.sh b/documentation/bump_versions.sh index 6201119a77..03e698faac 100755 --- a/documentation/bump_versions.sh +++ b/documentation/bump_versions.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash # this takes two args: platform release version and wallet release version. # it then uses sed to bump them in the three book.toml files. # From 2a7a681b7d83cc32ec878b7c13dad0067395ca28 Mon Sep 17 00:00:00 2001 From: mfahampshire Date: Fri, 28 Jul 2023 14:30:01 +0200 Subject: [PATCH 350/361] * started on cosmos tutorial * edited summary accordingly * edited links in other pages for new ts tutorial structure * removed ipfs coming soon page --- documentation/dev-portal/src/SUMMARY.md | 7 +- .../src/infrastructure/node-types.md | 7 +- .../src/integrations/mixnet-integration.md | 197 +++++++++--------- .../src/tutorials/cosmos-service/intro.md | 4 + .../src/tutorials/cosmos-service/overview.md | 57 +++++ .../tutorials/cosmos-service/preparing-env.md | 21 ++ .../src/tutorials/ipfs-service-provider.md | 1 - .../simple-service-provider.md | 0 8 files changed, 188 insertions(+), 106 deletions(-) create mode 100644 documentation/dev-portal/src/tutorials/cosmos-service/intro.md create mode 100644 documentation/dev-portal/src/tutorials/cosmos-service/overview.md create mode 100644 documentation/dev-portal/src/tutorials/cosmos-service/preparing-env.md delete mode 100644 documentation/dev-portal/src/tutorials/ipfs-service-provider.md rename documentation/dev-portal/src/tutorials/{ => simple-service-provider}/simple-service-provider.md (100%) diff --git a/documentation/dev-portal/src/SUMMARY.md b/documentation/dev-portal/src/SUMMARY.md index b9b4d3252b..3a739efd24 100644 --- a/documentation/dev-portal/src/SUMMARY.md +++ b/documentation/dev-portal/src/SUMMARY.md @@ -31,14 +31,17 @@ # Tutorials -- [Simple Service Provider](tutorials/simple-service-provider.md) +- [(Rust SDK) Cosmos Blockchain Service](tutorials/cosmos-service/intro.md) + - [Tutorial Overview](tutorials/cosmos-service/overview.md) + - [Preparing Your Environment](tutorials/cosmos-service/preparing-env.md) +- [(Typescript) Simple Service Provider](tutorials/simple-service-provider/simple-service-provider.md) - [Tutorial Overview](tutorials/simple-service-provider/overview.md) - [Preparing Your User Client Environment](tutorials/simple-service-provider/preparating-env.md) - [Building Your User Client](tutorials/simple-service-provider/user-client.md) - [Preparing Your Service Provider Environment](tutorials/simple-service-provider/preparating-env2.md) - [Building Your Service Provider](tutorials/simple-service-provider/service-provider.md) - [Sending a Message Through the Mixnet](tutorials/simple-service-provider/sending-message.md) -- [IPFS Service Provider (coming soon)](tutorials/ipfs-service-provider.md) + # Community Resources diff --git a/documentation/dev-portal/src/infrastructure/node-types.md b/documentation/dev-portal/src/infrastructure/node-types.md index 464dedb80f..589b9ca576 100644 --- a/documentation/dev-portal/src/infrastructure/node-types.md +++ b/documentation/dev-portal/src/infrastructure/node-types.md @@ -36,12 +36,11 @@ For example, a Service Provider could receive a request to check a mail server a Maybe you would like to concentrate on building a application that uses the mixnet: -* Explore the Tutorials section of the Developer Portal. Our in-depth tutorial on [Building a Simple Service Provider](../tutorials/simple-service-provider.md) give a good understanding of building User Clients and Service Providers in TypeScript, and how to configure Nym Websocket Clients for seamless communication with the mixnet. +* Explore the Tutorials section of the Developer Portal. Our in-depth tutorial on [Building a Simple Service Provider](../tutorials/simple-service-provider/simple-service-provider.md) give a good understanding of building User Clients and Service Providers in TypeScript, and how to configure Nym Websocket Clients for seamless communication with the mixnet. * Get started with using the Nym Mixnet quickly and easily by exploring the [Quickstart](../quickstart/overview.md) options, such a NymConnect, proxying traffic through the Nym Socks5 client, or dive into integrating Nym into your existing application with the [Integrations](../integrations/integration-options.md) section. -Or perhaps you a developer that would like to run a infrastructure node such as a Gateway, Mix node or Network Requestor: -* Check out the [Network Overview](https://nymtech.net/docs/architecture/network-overview.html) docs page. +Or perhaps you a developer that would like to run a infrastructure node such as a Gateway, Mix node or Network Requestor: +* Check out the [Network Overview](https://nymtech.net/docs/architecture/network-overview.html) docs page. * Take a look at our [Node Setup Guide](https://nymtech.net/docs/nodes/setup-guides.html) with our Nym Docs, containing setup guides for setting up you own infrastructure node. - diff --git a/documentation/dev-portal/src/integrations/mixnet-integration.md b/documentation/dev-portal/src/integrations/mixnet-integration.md index e4a328bf24..9df20518bd 100644 --- a/documentation/dev-portal/src/integrations/mixnet-integration.md +++ b/documentation/dev-portal/src/integrations/mixnet-integration.md @@ -1,19 +1,19 @@ # Integrating with Nym for network privacy -If you are wanting to integrate Nym by using the Mixnet as a transport layer for application traffic, you will have to run one of the three Nym clients in order to connect to the Mixnet. +If you are wanting to integrate Nym by using the Mixnet as a transport layer for application traffic, you will have to run one of the three Nym clients in order to connect to the Mixnet. ## Connecting applications to the mixnet -### SDK support +### SDK support If your app is written in Typescript or Rust, then you can use the [Typescript](https://nymtech.net/docs/sdk/typescript.html) or [Rust](https://nymtech.net/docs/sdk/rust.html) SDKs. These SDKs abstract away much of the messaging logic from your app, and allow you to run a Nym client as part of your application process, instead of having to run them seperately. ### Choosing a client -In order to connect your application to the mixnet, you need to select one of three clients to use. These clients do the majority of the heavy-lifting with regards to cryptographic operations and routing under the hood, and all do basically the same thing: create a connection to a gateway, encrypt and decrypt packets sent to and received from the mixnet, and send cover traffic to hide the flow of actual app traffic from observers. +In order to connect your application to the mixnet, you need to select one of three clients to use. These clients do the majority of the heavy-lifting with regards to cryptographic operations and routing under the hood, and all do basically the same thing: create a connection to a gateway, encrypt and decrypt packets sent to and received from the mixnet, and send cover traffic to hide the flow of actual app traffic from observers. -As outlined in the [clients overview documentation](https://nymtech.net/docs/clients/overview.html) there are three clients availiable to developers to use when connecting applications to the mixnet: +As outlined in the [clients overview documentation](https://nymtech.net/docs/clients/overview.html) there are three clients availiable to developers to use when connecting applications to the mixnet: #### Websocket client -Your first option is the native websocket client. This is a compiled program that can run on Linux, Mac OS X, and Windows machines. It runs as a persistent process on a desktop or server machine. You can connect to it with any language that supports websockets. +Your first option is the native websocket client. This is a compiled program that can run on Linux, Mac OS X, and Windows machines. It runs as a persistent process on a desktop or server machine. You can connect to it with any language that supports websockets. -You can see an example of how to connect to and manage interactions with this client in the [Simple Service Provider tutorial](../tutorials/simple-service-provider.md). +You can see an example of how to connect to and manage interactions with this client in the [Simple Service Provider tutorial](../tutorials/simple-service-provider/simple-service-provider.md). #### Webassembly client If you’re working in JavaScript or Typescript in the browser, or building an edge computing app, you’ll likely want to choose the webassembly client. @@ -27,16 +27,16 @@ You can find example code in the [examples section](https://github.com/nymtech/n #### SOCKS client This client is useful for allowing existing applications to use the Nym mixnet without any code changes. All that’s necessary is that they can use one of the SOCKS5, SOCKS4a, or SOCKS4 proxy protocols (which many applications can - crypto wallets, browsers, chat applications etc). -It’s less flexible as a way of writing custom applications than the other clients, but able to be used to proxy application traffic through the mixnet without having to make any code changes. +It’s less flexible as a way of writing custom applications than the other clients, but able to be used to proxy application traffic through the mixnet without having to make any code changes. -You can find examples of how to utilise this client in the [Quickstart](../quickstart/socks-proxy.md) section, and the [SOCKS5 documentation](https://nymtech.net/docs/clients/socks5-client.html). +You can find examples of how to utilise this client in the [Quickstart](../quickstart/socks-proxy.md) section, and the [SOCKS5 documentation](https://nymtech.net/docs/clients/socks5-client.html). -## Recommended infrastructure setup -In order to ensure uptime and reliability, it is recommended that you run some pieces of mixnet infrastructure. What infrastructure is necessary to run depends on the architecture of your application, and the endpoints that it needs to hit! +## Recommended infrastructure setup +In order to ensure uptime and reliability, it is recommended that you run some pieces of mixnet infrastructure. What infrastructure is necessary to run depends on the architecture of your application, and the endpoints that it needs to hit! -* If you're running a purely P2P application, then just integrating clients and having some method of sharing addresses should be enough to route your traffic through the mixnet. -* If you're wanting to place the mixnet between your users' application instances and a server-based backend, you can use the [network requester](https://nymtech.net/docs/nodes/network-requester-setup.html) service provider binary to proxy these requests to your application backend, with the mixnet 'between' the user and your service, in order to prevent metadata leakage being broadcast to the internet. -* If you're wanting to route RPC requests through the mixnet to a blockchain, you will need to look into setting up some sort of service that does the transaction broadcasting for you. You can find examples of such projects on the [community applications](../community-resources/community-applications-and-guides.md) page. +* If you're running a purely P2P application, then just integrating clients and having some method of sharing addresses should be enough to route your traffic through the mixnet. +* If you're wanting to place the mixnet between your users' application instances and a server-based backend, you can use the [network requester](https://nymtech.net/docs/nodes/network-requester-setup.html) service provider binary to proxy these requests to your application backend, with the mixnet 'between' the user and your service, in order to prevent metadata leakage being broadcast to the internet. +* If you're wanting to route RPC requests through the mixnet to a blockchain, you will need to look into setting up some sort of service that does the transaction broadcasting for you. You can find examples of such projects on the [community applications](../community-resources/community-applications-and-guides.md) page. ## Example application traffic flow ### Initialization @@ -44,36 +44,36 @@ First, we need to initalise an app and connect it to Nym. ``` - +-----------+ - | Gateway | +-----------+ - ^ - | - | - | - | - | - | - +-------------------+ - | +---------------+ | - | | Nym client | | - | +---------------+ | - | ^ | - | | | - | | | - | | | - | v | - | +---------------+ | - | | Your app code | | - | +---------------+ | - +-------------------+ - Your Local Machine + | Gateway | + +-----------+ + ^ + | + | + | + | + | + | + +-------------------+ + | +---------------+ | + | | Nym client | | + | +---------------+ | + | ^ | + | | | + | | | + | | | + | v | + | +---------------+ | + | | Your app code | | + | +---------------+ | + +-------------------+ + Your Local Machine ``` At the bottom we have an app. It consists of two parts: -* your application specific logic -* your Nym client - either running as a standalone process, or as part of the process of your app code if you're using an SDK +* your application specific logic +* your Nym client - either running as a standalone process, or as part of the process of your app code if you're using an SDK Nym apps have a stable, potentially long-lasting relation to a gateway node. A client will register itself with a gateway, and get back an authentication token that it can then use to retrieve messages from the gateway later on. @@ -89,40 +89,40 @@ The Nym client part of the app accepts messages from your code and automatically The app has now connected to the Gateway, but we haven't sent a message to ourselves yet. Let's do that now. ``` - - +----------+ +----------+ +----------+ - | Mix Node |<-----------> | Mix Node |<----------->| Mix Node | - | Layer 1 | | Layer 2 | | Layer 3 | - +----------+ +----------+ +----------+ - ^ ^ - | | + + +----------+ +----------+ +----------+ + | Mix Node |<-----------> | Mix Node |<----------->| Mix Node | + | Layer 1 | | Layer 2 | | Layer 3 | + +----------+ +----------+ +----------+ + ^ ^ + | | |<--------------------------------------------------+ - | - v - +--------------+ - | Your gateway | - +--------------+ - ^ - | - | - v - +-------------------+ - | +---------------+ | - | | Nym client | | - | +---------------+ | - | ^ | - | | | - | | | - | v | - | +---------------+ | - | | Your app code | | - | +---------------+ | - +-------------------+ - Your Local Machine** + | + v + +--------------+ + | Your gateway | + +--------------+ + ^ + | + | + v + +-------------------+ + | +---------------+ | + | | Nym client | | + | +---------------+ | + | ^ | + | | | + | | | + | v | + | +---------------+ | + | | Your app code | | + | +---------------+ | + +-------------------+ + Your Local Machine** ** note that depending on the technical setup, the Nym client running on this machine may -be either a seperate process or embedded in the same process as the app code via one of our SDKs. +be either a seperate process or embedded in the same process as the app code via one of our SDKs. ``` Let's say your code code pokes a message `hello world` into the Nym client. The Nym client automatically wraps that message up into a layer encrypted Sphinx packet, adds some routing information and encryption, and sends it to its own gateway. The gateway strips the first layer of encryption, ending up with the address of the first mixnode it should forward to, and a Sphinx packet. @@ -139,38 +139,37 @@ Messages are end-to-end encrypted. Although the gateway knows our app's IP when The process for sending messages to other apps is exactly the same, you simply specify a different recipient address. Address discovery happens outside the Nym system: in the case of a Service Provider app, the service provider has presumably advertised its own address. If you're sending to a friend of yours, you'll need to get a hold of their address out of band, maybe through a private messaging app such as Signal. ``` - - +----------+ +----------+ +----------+ - | Mix Node |<-----------> | Mix Node |<----------->| Mix Node | - | Layer 1 | | Layer 2 | | Layer 3 | - +----------+ +----------+ +----------+ - ^ ^ - | | - | | - v v - +--------------+ +-----------------+ - | Your gateway | | Service gateway | - +--------------+ +-----------------+ - ^ ^ - | | - | | - v v - +-------------------+ +-------------------+ - | +---------------+ | | +---------------+ | - | | Nym client | | | | Nym Client | | - | +---------------+ | | +---------------+ | - | ^ | | ^ | - | | | | | | - | | | | | | - | v | | v | - | +---------------+ | | +---------------+ | - | | Your app code | | | | Service Code | | - | +---------------+ | | +---------------+ | - +-------------------+ +-------------------+ - Your Local Machine** Service Provider Machine** + + +----------+ +----------+ +----------+ + | Mix Node |<-----------> | Mix Node |<----------->| Mix Node | + | Layer 1 | | Layer 2 | | Layer 3 | + +----------+ +----------+ +----------+ + ^ ^ + | | + | | + v v + +--------------+ +-----------------+ + | Your gateway | | Service gateway | + +--------------+ +-----------------+ + ^ ^ + | | + | | + v v + +-------------------+ +-------------------+ + | +---------------+ | | +---------------+ | + | | Nym client | | | | Nym Client | | + | +---------------+ | | +---------------+ | + | ^ | | ^ | + | | | | | | + | | | | | | + | v | | v | + | +---------------+ | | +---------------+ | + | | Your app code | | | | Service Code | | + | +---------------+ | | +---------------+ | + +-------------------+ +-------------------+ + Your Local Machine** Service Provider Machine** ** note that depending on the technical setup, the Nym client running on these machines may -be either a seperate process or embedded in the same process as the app code via one of our SDKs. +be either a seperate process or embedded in the same process as the app code via one of our SDKs. ``` - diff --git a/documentation/dev-portal/src/tutorials/cosmos-service/intro.md b/documentation/dev-portal/src/tutorials/cosmos-service/intro.md new file mode 100644 index 0000000000..1b9e40096a --- /dev/null +++ b/documentation/dev-portal/src/tutorials/cosmos-service/intro.md @@ -0,0 +1,4 @@ +# Building a Cosmos Blockchain Service with the Rust SDK + +This tutorial is for Rust developers wanting to interact with the Rust SDK and take a first step at building a service with which to interact with a blockchain. +The key here is to think of the service as a proxy: it interacts with the blockchain _on the client's behalf_, shielding it from the Validator it interacts with, whilst also being shielded from the client by the mixnet! diff --git a/documentation/dev-portal/src/tutorials/cosmos-service/overview.md b/documentation/dev-portal/src/tutorials/cosmos-service/overview.md new file mode 100644 index 0000000000..a24a381662 --- /dev/null +++ b/documentation/dev-portal/src/tutorials/cosmos-service/overview.md @@ -0,0 +1,57 @@ +# Tutorial Overview + +This tutorial involves writing two pieces of code in Rust: + +- A client side binary used to construct a blockchain query and send this query to a service, which will interact with a Cosmos SDK blockchain on our behalf. +- A service which will listen out for requests from the mixnet, act on those requests, and anonymously reply to the client sending the requests. + +You will learn how to do the following with the Rust SDK: +- Create clients with manual storage settings. +- Parse incoming traffic from the mixnet and reply anonymously using [SURBs](). + +> Services usually run on remote servers to assure reliable uptime and to unlink sender and receiver metadata. For demonstration purposes however, you will run both components on your local machine, looping messages through the mixnet to yourself. + + +``` + +----------+ +----------+ +----------+ + | Mix Node |<-----------> | Mix Node |<----------->| Mix Node | + | Layer 1 | | Layer 2 | | Layer 3 | + +----------+ +----------+ +----------+ + ^ ^ + | | + |<--------------------------------------------------+ + | + v + +--------------+ + | Your gateway | + +--------------+ + ^ + | + | + v ++-------------------------------------------+ +| | +| +-------------+ +--------------+ | +| | Client code | | Service code | | +| +-------------+ +--------------+ | +| | ++-------------------------------------------+ + Your Local Machine +``` + +You can find the code for these components [here](). You can use it as a reference while building or simply download it and follow along as you progress through the tutorial. + +Notice that this tutorial attempts to use very few external libraries. This tutorial is not showing you how to build production-grade code, but **to understand how to connect and send messages to, as well as recieve messages from, the mixnet.** + +```admonish note title="Sidenote: What is a Service / Service Provider?" +'Service' or 'Service Provider' are catchall names used to refer to any type of app that can communicate with the mixnet via a Nym client - in this case, one embedded in your app process via the Rust SDK. + +The first SP to have been released is the [Network Requester](https://nymtech.net/docs/nodes/network-requester-setup.html) - a binary that receives a network request from the mixnet, performs that request (e.g. authenticating with a message server and receiving new messages for a user) and then passes the response back to the user who requested it anonymously, shielding their metadata from the message server. + +The SP you will build in this tutorial is far more simple than this, showing you how to approach building something that can: +* connect to the mixnet, +* listen for messages, and +* perform some action with them - in this case, query a Cosmos SDK blockchain. + +However, once you see how easy it is to integrate with the mixnet for traffic transport, you will be able to build apps with real-world uses easily. +``` diff --git a/documentation/dev-portal/src/tutorials/cosmos-service/preparing-env.md b/documentation/dev-portal/src/tutorials/cosmos-service/preparing-env.md new file mode 100644 index 0000000000..a763be73f3 --- /dev/null +++ b/documentation/dev-portal/src/tutorials/cosmos-service/preparing-env.md @@ -0,0 +1,21 @@ +# Preparing Your Environment + +## Prerequisites +* `Rust` & `cargo` + +## Creating your Project Structure + +* Make a new cargo project: +``` + +``` + +* Create the following directory structure and files: +``` + +``` + +* Finally add the following dependencies - you can just copy and paste this into your `Cargo.toml` file: +``` +TODO pull validator client code etc from the monorepo instead of workspace loading +``` diff --git a/documentation/dev-portal/src/tutorials/ipfs-service-provider.md b/documentation/dev-portal/src/tutorials/ipfs-service-provider.md deleted file mode 100644 index b51f035be4..0000000000 --- a/documentation/dev-portal/src/tutorials/ipfs-service-provider.md +++ /dev/null @@ -1 +0,0 @@ -# Building a Image Upload Service Provider with IPFS (coming soon) diff --git a/documentation/dev-portal/src/tutorials/simple-service-provider.md b/documentation/dev-portal/src/tutorials/simple-service-provider/simple-service-provider.md similarity index 100% rename from documentation/dev-portal/src/tutorials/simple-service-provider.md rename to documentation/dev-portal/src/tutorials/simple-service-provider/simple-service-provider.md From c022486e63e950d663fc219a08b12a52729c6435 Mon Sep 17 00:00:00 2001 From: mfahampshire Date: Fri, 28 Jul 2023 16:31:08 +0200 Subject: [PATCH 351/361] cont. with first pass at tutorial --- documentation/dev-portal/src/SUMMARY.md | 3 +++ .../src/tutorials/cosmos-service/client.md | 1 + .../dev-portal/src/tutorials/cosmos-service/lib.md | 13 +++++++++++++ .../src/tutorials/cosmos-service/preparing-env.md | 13 ++++++++++++- .../src/tutorials/cosmos-service/service.md | 1 + 5 files changed, 30 insertions(+), 1 deletion(-) create mode 100644 documentation/dev-portal/src/tutorials/cosmos-service/client.md create mode 100644 documentation/dev-portal/src/tutorials/cosmos-service/lib.md create mode 100644 documentation/dev-portal/src/tutorials/cosmos-service/service.md diff --git a/documentation/dev-portal/src/SUMMARY.md b/documentation/dev-portal/src/SUMMARY.md index 3a739efd24..d0089366d7 100644 --- a/documentation/dev-portal/src/SUMMARY.md +++ b/documentation/dev-portal/src/SUMMARY.md @@ -34,6 +34,9 @@ - [(Rust SDK) Cosmos Blockchain Service](tutorials/cosmos-service/intro.md) - [Tutorial Overview](tutorials/cosmos-service/overview.md) - [Preparing Your Environment](tutorials/cosmos-service/preparing-env.md) + - [Preparing Your Lib](tutorials/cosmos-service/lib.md) + - [Preparing Your Client](tutorials/cosmos-service/client.md) + - [Preparing Your Service](tutorials/cosmos-service/service.md) - [(Typescript) Simple Service Provider](tutorials/simple-service-provider/simple-service-provider.md) - [Tutorial Overview](tutorials/simple-service-provider/overview.md) - [Preparing Your User Client Environment](tutorials/simple-service-provider/preparating-env.md) diff --git a/documentation/dev-portal/src/tutorials/cosmos-service/client.md b/documentation/dev-portal/src/tutorials/cosmos-service/client.md new file mode 100644 index 0000000000..c6db01fb6a --- /dev/null +++ b/documentation/dev-portal/src/tutorials/cosmos-service/client.md @@ -0,0 +1 @@ +# Preparing Your Client diff --git a/documentation/dev-portal/src/tutorials/cosmos-service/lib.md b/documentation/dev-portal/src/tutorials/cosmos-service/lib.md new file mode 100644 index 0000000000..9988384581 --- /dev/null +++ b/documentation/dev-portal/src/tutorials/cosmos-service/lib.md @@ -0,0 +1,13 @@ +# Preparing Your Lib + +We'll now move on to preparing shared data structures and functions in `src/lib.rs`. + +These include the Request and Response types we'll be passing between the Client and the Service through the mixnet, as well as shared functions such as client creation, and parsing incoming messages. + +## Data Structures + +## Shared Functions + +### Client Creation + +### Parsing Incoming diff --git a/documentation/dev-portal/src/tutorials/cosmos-service/preparing-env.md b/documentation/dev-portal/src/tutorials/cosmos-service/preparing-env.md index a763be73f3..5a98938837 100644 --- a/documentation/dev-portal/src/tutorials/cosmos-service/preparing-env.md +++ b/documentation/dev-portal/src/tutorials/cosmos-service/preparing-env.md @@ -7,12 +7,23 @@ * Make a new cargo project: ``` - +cargo new nym-cosmos-service ``` * Create the following directory structure and files: ``` +. +├── Cargo.toml +├── bin +│   ├── client.rs +│   └── service.rs +└── src + ├── client.rs + ├── lib.rs + ├── main.rs + └── service.rs +3 directories, 7 files ``` * Finally add the following dependencies - you can just copy and paste this into your `Cargo.toml` file: diff --git a/documentation/dev-portal/src/tutorials/cosmos-service/service.md b/documentation/dev-portal/src/tutorials/cosmos-service/service.md new file mode 100644 index 0000000000..1090443702 --- /dev/null +++ b/documentation/dev-portal/src/tutorials/cosmos-service/service.md @@ -0,0 +1 @@ +# Preparing Your Service From 204b2e1101368c85414354bdf61a96d2da6586ec Mon Sep 17 00:00:00 2001 From: mfahampshire Date: Mon, 31 Jul 2023 15:38:19 +0200 Subject: [PATCH 352/361] working lib setup for tutorial --- documentation/dev-portal/book.toml | 1 - .../src/tutorials/cosmos-service/intro.md | 5 +- .../src/tutorials/cosmos-service/lib.md | 87 ++++++++++++++++++- .../src/tutorials/cosmos-service/overview.md | 2 +- .../tutorials/cosmos-service/preparing-env.md | 42 ++++++++- 5 files changed, 126 insertions(+), 11 deletions(-) diff --git a/documentation/dev-portal/book.toml b/documentation/dev-portal/book.toml index 1026b8e77b..65d15a7283 100644 --- a/documentation/dev-portal/book.toml +++ b/documentation/dev-portal/book.toml @@ -38,7 +38,6 @@ chapter-line-height = "2em" section-line-height = "1.5em" # if true, never read and touch the files in theme dir -turn-off = false [preprocessor.admonish] command = "mdbook-admonish" diff --git a/documentation/dev-portal/src/tutorials/cosmos-service/intro.md b/documentation/dev-portal/src/tutorials/cosmos-service/intro.md index 1b9e40096a..1510d31e2e 100644 --- a/documentation/dev-portal/src/tutorials/cosmos-service/intro.md +++ b/documentation/dev-portal/src/tutorials/cosmos-service/intro.md @@ -1,4 +1,7 @@ # Building a Cosmos Blockchain Service with the Rust SDK This tutorial is for Rust developers wanting to interact with the Rust SDK and take a first step at building a service with which to interact with a blockchain. -The key here is to think of the service as a proxy: it interacts with the blockchain _on the client's behalf_, shielding it from the Validator it interacts with, whilst also being shielded from the client by the mixnet! + +The key here is to think of the service as a proxy: it interacts with the blockchain _on the client's behalf_, shielding it from the Validator it interacts with, whilst also being shielded from the client by the mixnet. + +> This service also nicely highlights the limitations of the mixnet - even though with this code your metadata is shielded from the Validator, and even the service does not know your Nym address, application-level information such as a blockchain address is not made private, in virtue of the fact that using the mixnet provides solely network-level privacy. For information on what application-level privacy Nym offers, check out the [coconut credential SDK example](). diff --git a/documentation/dev-portal/src/tutorials/cosmos-service/lib.md b/documentation/dev-portal/src/tutorials/cosmos-service/lib.md index 9988384581..ef041b1838 100644 --- a/documentation/dev-portal/src/tutorials/cosmos-service/lib.md +++ b/documentation/dev-portal/src/tutorials/cosmos-service/lib.md @@ -1,13 +1,92 @@ # Preparing Your Lib -We'll now move on to preparing shared data structures and functions in `src/lib.rs`. +Now move on to preparing shared data structures and functions in `src/lib.rs`. -These include the Request and Response types we'll be passing between the Client and the Service through the mixnet, as well as shared functions such as client creation, and parsing incoming messages. +These include the request and response types the client and the service will be passing through the mixnet, as well as shared functions such as client creation, and message parsing. -## Data Structures +## Dependencies +The dependecies for the shared `lib` file are the following: +```rust +use cosmrs::{tendermint, AccountId}; +use nym_sdk::mixnet::{ + AnonymousSenderTag, MixnetClient, MixnetClientBuilder, ReconstructedMessage, StoragePaths, +}; +use serde::{Deserialize, Serialize}; +use std::path::PathBuf; +pub mod client; +pub mod service; +``` + +Since this is the file where client creation and message parsing are handled, the various `nym_sdk` imports, as well as `serde`'s (de)serialisation functionality, are required. This file also imports the `client` and `service` specific logic not found in `bin/`, as well as `PathBuf` to read filepaths, and `cosmrs` types for defining Nyx blockchain accounts. + +## Constants +Below this are the chain-related `const` variables. These have been hardcoded for this demo. + +```rust +pub const DEFAULT_VALIDATOR_RPC: &str = "https://sandbox-validator1.nymtech.net"; +pub const DEFAULT_DENOM: &str = "unym"; +pub const DEFAULT_PREFIX: &str = "n"; +``` + +These define the RPC endpoint your service will use to interact with the blockchain - in this case the Sandbox testnet - as well as the coin denomination and Bech32-prefix of the blockchain accounts. + +## Shared Data Structures +Define the following structs for our different request and responses that will be serialised and sent through the mixnet between your client and service binaries: + +```rust +#[derive(Deserialize, Serialize, Debug)] +pub struct SequenceRequest { + pub validator: String, + pub signer_address: AccountId, +} + +#[derive(Deserialize, Serialize, Debug)] +pub struct SequenceRequestResponse { + pub account_number: u64, + pub sequence: u64, + pub chain_id: tendermint::chain::Id, +} + +#[derive(Debug, Deserialize, Serialize)] +#[serde(untagged)] +pub enum RequestTypes { + Sequence(SequenceRequest), +} + +#[derive(Debug, Deserialize, Serialize)] +#[serde(untagged)] +pub enum ResponseTypes { + Sequence(SequenceRequestResponse), +} +``` + +TODO explainer of each ## Shared Functions +Now to define functions shared by the `client` and `service` binaries. ### Client Creation +The following function is called on startup by each binary, with the `config_path` being a filepath for storing client config. -### Parsing Incoming +```rust +// create our client with specified path for key storage +pub async fn create_client(config_path: PathBuf) -> MixnetClient { + let config_dir = config_path; + let storage_paths = StoragePaths::new_from_dir(&config_dir).unwrap(); + let client = MixnetClientBuilder::new_with_default_storage(storage_paths) + .await + .unwrap() + .build() + .await + .unwrap(); + + client.connect_to_mixnet().await.unwrap() +} +``` + +> If keys and config already exist at this location, re-running this function **will not** overwrite them. + +### Parsing Incoming messages +TODO smoosh them into one function with the return type being an `Option` instead + +Next to define two functions: one for listening _for_ messages from the mixnet (used by our `service`), and one for listening out for a _reply_ after sending a message to another Nym client (in this case, when sending a message from the `client` to the `service`). diff --git a/documentation/dev-portal/src/tutorials/cosmos-service/overview.md b/documentation/dev-portal/src/tutorials/cosmos-service/overview.md index a24a381662..13acb800c9 100644 --- a/documentation/dev-portal/src/tutorials/cosmos-service/overview.md +++ b/documentation/dev-portal/src/tutorials/cosmos-service/overview.md @@ -2,7 +2,7 @@ This tutorial involves writing two pieces of code in Rust: -- A client side binary used to construct a blockchain query and send this query to a service, which will interact with a Cosmos SDK blockchain on our behalf. +- A client side binary used to construct a blockchain query and send this query to a service, which will interact with a Cosmos SDK blockchain on our behalf (bear in mind this principle works for all blockchains - we're just utilising the `cosmrs` library to interact with the Nyx blockchain in this tutorial). - A service which will listen out for requests from the mixnet, act on those requests, and anonymously reply to the client sending the requests. You will learn how to do the following with the Rust SDK: diff --git a/documentation/dev-portal/src/tutorials/cosmos-service/preparing-env.md b/documentation/dev-portal/src/tutorials/cosmos-service/preparing-env.md index 5a98938837..341c89a216 100644 --- a/documentation/dev-portal/src/tutorials/cosmos-service/preparing-env.md +++ b/documentation/dev-portal/src/tutorials/cosmos-service/preparing-env.md @@ -20,13 +20,47 @@ cargo new nym-cosmos-service └── src ├── client.rs ├── lib.rs - ├── main.rs └── service.rs -3 directories, 7 files +3 directories, 6 files ``` -* Finally add the following dependencies - you can just copy and paste this into your `Cargo.toml` file: +* Add the following dependencies to your `Cargo.toml` file: ``` -TODO pull validator client code etc from the monorepo instead of workspace loading +[dependencies] +anyhow = "1.0.72" +clap = { version = "4.0", features = ["derive"] } +bip39 = { version = "2.0.0", features = ["zeroize"] } +cosmrs = "=0.14.0" +TODO +# tokio = { workspace = true, features = ["rt-multi-thread", "macros"] } +tokio = "1.24.1" +bs58 = "0.5.0" +serde = "1.0.152" +serde_json = "1.0.91" ``` + +These are non Nym-specific dependencies for the project. `anyhow` is for catch-all error handling, `clap` is for setting up the CLI commands, `cosmrs` for cosmos-specific types and functionality, `tokio` for the async/await environment, and `serde` for (de)serialisation. + +* Next add Nym-specific dependencies. Since these libraries are not yet on [crates io](https://crates.io) then you need to import them from the Nym monorepo: +``` +nym-sdk = { git = "https://github.com/nymtech/nym" } +nym-sphinx-addressing = { git = "https://github.com/nymtech/nym" } +nym-validator-client = { git = "https://github.com/nymtech/nym" } +nym-bin-common = { git = "https://github.com/nymtech/nym" } +nym-sphinx-anonymous-replies = { git = "https://github.com/nymtech/nym" } +``` + +The `sphinx` dependencies are for packet- and address-related functionality, the `validator-client` for Nyx blockchain specific configs, `common` for client logging, and the `sdk` for SDK functionality: creating and managing client storage and connections, and sending and receiving messages to and from the mixnet. + +* Finally add the following underneath your `[dependencies]`: +``` +[[bin]] +name = "client" +path = "bin/client.rs" + +[[bin]] +name = "service" +path = "bin/service.rs" +``` +This defines multiple binaries to run in a single cargo project, as outlined [here](https://doc.rust-lang.org/cargo/reference/cargo-targets.html#binaries). From 2922306e25c10fff9debe177d85e22989fa555a7 Mon Sep 17 00:00:00 2001 From: mfahampshire Date: Mon, 31 Jul 2023 15:38:49 +0200 Subject: [PATCH 353/361] added tree output for created client with storage example --- documentation/docs/src/sdk/rust.md | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/documentation/docs/src/sdk/rust.md b/documentation/docs/src/sdk/rust.md index 70015751e4..c53fde7b01 100644 --- a/documentation/docs/src/sdk/rust.md +++ b/documentation/docs/src/sdk/rust.md @@ -52,6 +52,26 @@ The example above involves ephemeral keys - if we want to create and then mainta As seen in the example above, the `mixnet::MixnetClientBuilder::new()` function handles checking for keys in a storage location, loading them if present, or creating them and storing them if not, making client key management very simple. +Assuming our client config is stored in `/tmp/mixnet-client`, the following files are generated: +``` +$ tree /tmp/mixnet-client + +mixnet-client +├── ack_key.pem +├── db.sqlite +├── db.sqlite-shm +├── db.sqlite-wal +├── gateway_details.json +├── gateway_shared.pem +├── persistent_reply_store.sqlite +├── private_encryption.pem +├── private_identity.pem +├── public_encryption.pem +└── public_identity.pem + +1 directory, 11 files +``` + ### Manually handling storage If you're integrating mixnet functionality into an existing app and want to integrate saving client configs and keys into your existing storage logic, you can manually perform the actions taken automatically above (`examples/manually_handle_keys_and_config.rs`) @@ -70,7 +90,7 @@ The number of SURBs is set [here](https://github.com/nymtech/nym/blob/release/{{ You can read more about how SURBs function under the hood [here](../architecture/traffic-flow.md#private-replies-using-surbs). -In order to reply to an incoming message using SURBs, you can construct a `recipient` from the `sender_tag` sent along with the message you wish to reply to: +In order to reply to an incoming message using SURBs, you can construct a `recipient` from the `sender_tag` sent along with the message you wish to reply to: ```rust,noplayground {{#include ../../../../sdk/rust/nym-sdk/examples/surb-reply.rs}} From c3d38fb904930cb0cc2243cda0f078b778d85f4f Mon Sep 17 00:00:00 2001 From: mfahampshire Date: Wed, 2 Aug 2023 08:59:37 +0200 Subject: [PATCH 354/361] continued working on dev portal tutorial --- .../src/tutorials/cosmos-service/client.md | 22 ++++++ .../src/tutorials/cosmos-service/lib.md | 71 +++++++++++++++++-- 2 files changed, 88 insertions(+), 5 deletions(-) diff --git a/documentation/dev-portal/src/tutorials/cosmos-service/client.md b/documentation/dev-portal/src/tutorials/cosmos-service/client.md index c6db01fb6a..c3f6c6ee10 100644 --- a/documentation/dev-portal/src/tutorials/cosmos-service/client.md +++ b/documentation/dev-portal/src/tutorials/cosmos-service/client.md @@ -1 +1,23 @@ # Preparing Your Client + +Start by creating the startup logic of your `client` - creating a Nym client and connecting to the mixnet (or just connecting if your client has been started before and config already exists for it), and defining commands. + +Start in `bin/client.rs`. + +## Dependencies +Import the following dependencies: +``` +use clap::{Args, Parser, Subcommand}; +use nym_sdk::mixnet::Recipient; +use nym_validator_client::nyxd::AccountId; +use nym_cosmos_service::create_client; +use nym_bin_common::logging::setup_logging; +``` + +`clap` is used so different commands can be passed to the `client` (even though we're only defining one function in this first part of the tutorial, more will be added in subsequent chapters). `nym_sdk::mixnet::Recipient` is the type used to define the recipient of a mixnet message, `nym_bin_common::logging::setup_logging` is the logging setup for `client`'s Nym client, and `nym_cosmos_service::create_client` imports the `create_client` function created on the previous page. + +## CLI Command with Clap + +< simple account query first - then maybe a contract query > + +## diff --git a/documentation/dev-portal/src/tutorials/cosmos-service/lib.md b/documentation/dev-portal/src/tutorials/cosmos-service/lib.md index ef041b1838..37a2f46a22 100644 --- a/documentation/dev-portal/src/tutorials/cosmos-service/lib.md +++ b/documentation/dev-portal/src/tutorials/cosmos-service/lib.md @@ -17,7 +17,7 @@ pub mod client; pub mod service; ``` -Since this is the file where client creation and message parsing are handled, the various `nym_sdk` imports, as well as `serde`'s (de)serialisation functionality, are required. This file also imports the `client` and `service` specific logic not found in `bin/`, as well as `PathBuf` to read filepaths, and `cosmrs` types for defining Nyx blockchain accounts. +Since this is the file where client creation and message parsing are handled, the various `nym_sdk` imports, as well as `serde`'s (de)serialisation functionality, are required. `PathBuf` is for reading filepaths, and `cosmrs` types are required for defining Nyx blockchain accounts. ## Constants Below this are the chain-related `const` variables. These have been hardcoded for this demo. @@ -44,7 +44,7 @@ pub struct SequenceRequest { pub struct SequenceRequestResponse { pub account_number: u64, pub sequence: u64, - pub chain_id: tendermint::chain::Id, + pub chain_id: [tendermint](tendermint)::chain::Id, } #[derive(Debug, Deserialize, Serialize)] @@ -60,7 +60,11 @@ pub enum ResponseTypes { } ``` -TODO explainer of each +The above data types are pretty straightforward. Even though there are only one instance of a request type (sent from client to service) and one of a response type (service -> client) so far, a pair of enums has been defined to contain additional response or request types that will be added in the future. + +`SequenceRequest` will be used when requesting the service to query the chain on the client's behalf for an address' sequence information (used for offline signing). You can see the information that will be returned from the chain to the service, and from the service to the client, in `SequenceRequestResponse`. + +> Although `SequenceResponse` would have been more succinct, there is already a `cosmrs` type with this name. As such the response type was given a different name to avoid confusion. ## Shared Functions Now to define functions shared by the `client` and `service` binaries. @@ -87,6 +91,63 @@ pub async fn create_client(config_path: PathBuf) -> MixnetClient { > If keys and config already exist at this location, re-running this function **will not** overwrite them. ### Parsing Incoming messages -TODO smoosh them into one function with the return type being an `Option` instead - Next to define two functions: one for listening _for_ messages from the mixnet (used by our `service`), and one for listening out for a _reply_ after sending a message to another Nym client (in this case, when sending a message from the `client` to the `service`). + +```rust +pub async fn listen_and_parse_response(client: &mut MixnetClient) -> anyhow::Result { + let mut message: Vec = Vec::new(); + + // get the actual message - discard the empty vec sent along with the SURB topup request + while let Some(new_message) = client.wait_for_messages().await { + if new_message.is_empty() { + println!("got a request for more SURBs from service - sending additional SURBs to sender"); + continue; + } + message = new_message; + break; + } + + // parse vec -> JSON String + let mut parsed = String::new(); + if let Some(r) = message.iter().next() { + parsed = String::from_utf8(r.message.clone())?; + } + let sp_response: crate::ResponseTypes = serde_json::from_str(&parsed)?; + Ok(sp_response) +} + +pub async fn listen_and_parse_request( + client: &mut MixnetClient, +) -> anyhow::Result<(RequestTypes, AnonymousSenderTag)> { + let mut message: Vec = Vec::new(); + + while let Some(new_message) = client.wait_for_messages().await { + if new_message.is_empty() { + println!("got empty vec - probably the SURBs sent along with the request"); + continue; + } + message = new_message; + break; + } + + // parse vec -> JSON String + let mut parsed = String::new(); + if let Some(r) = message.iter().next() { + parsed = String::from_utf8(r.message.clone())?; + } + let client_request: crate::RequestTypes = serde_json::from_str(&parsed)?; + + // get the sender_tag for anon reply + let return_recipient = message[0].sender_tag.unwrap(); + + Ok((client_request, return_recipient)) +} +``` + +Aside from the return types that each function has, the main difference is that any incoming requests to the `service` will also have SURB packets attached to it. + +< smol explanation of SURBs + link > + +As such, this function returns a tuple containing the `RequestType` _and_ the `sender_tag` used by the `service` to identify which bucket of pre-addressed replies it will use to respond to a request. + +< note concerning parsing out the empty vecs > From b5cde68e62964939b60ee8c46e5abb6b0276db11 Mon Sep 17 00:00:00 2001 From: mfahampshire Date: Wed, 2 Aug 2023 23:29:33 +0200 Subject: [PATCH 355/361] continued working on tutorial; finished bin/service --- .../tutorials/cosmos-service/client-src.md | 53 +++++++ .../src/tutorials/cosmos-service/client.md | 70 ++++++++- .../src/tutorials/cosmos-service/intro.md | 2 +- .../src/tutorials/cosmos-service/lib.md | 133 +++++++++--------- .../src/tutorials/cosmos-service/overview.md | 4 +- .../tutorials/cosmos-service/preparing-env.md | 13 +- .../tutorials/cosmos-service/service-src.md | 1 + .../src/tutorials/cosmos-service/service.md | 70 +++++++++ 8 files changed, 260 insertions(+), 86 deletions(-) create mode 100644 documentation/dev-portal/src/tutorials/cosmos-service/client-src.md create mode 100644 documentation/dev-portal/src/tutorials/cosmos-service/service-src.md diff --git a/documentation/dev-portal/src/tutorials/cosmos-service/client-src.md b/documentation/dev-portal/src/tutorials/cosmos-service/client-src.md new file mode 100644 index 0000000000..7b8085fc54 --- /dev/null +++ b/documentation/dev-portal/src/tutorials/cosmos-service/client-src.md @@ -0,0 +1,53 @@ +# Preparing Your Client pt2 + +Open `src/client.rs`. This is where the logic of the command from the `match` statement in `bin/client.rs` is defined. + +# Dependencies +```rust +use crate::{handle_response, wait_for_non_empty_message, RequestTypes, DEFAULT_VALIDATOR_RPC}; +use cosmrs::AccountId; +use nym_sdk::mixnet::MixnetClient; +use nym_sphinx_addressing::clients::Recipient; +use nym_validator_client::nyxd::Coin +``` + +# Querying via the Mixnet +The following is used to construct a `BalanceRequest`, send this to the supplied `service` address, and then handle the response, matching it to a `ResponseType`, in this case the only expected response, a `BalanceResponse`. + +The actual sending of the request is performed by `client.send_bytes`: sending the serialised `BalanceRequest` to the supplied Nym address (the `Recipient` imported from the `nym_sphinx_addressing` crate). It is sending the default number of SURBs along with the message, defined [here](LINK_TO_SURB_DEFAULT_NUMBEr). + +```rust +pub async fn query_balance( + account: AccountId, + client: &mut MixnetClient, + sp_address: Recipient, +) -> anyhow::Result { + // construct balance request + let message = RequestTypes::Balance(crate::BalanceRequest { + validator: DEFAULT_VALIDATOR_RPC.to_owned(), // rpc endpoint for broadcaster to use + account, + }); + + // send serialised request to service via mixnet + client + .send_bytes(sp_address, message.serialize(), Default::default()) + .await; + + let received = wait_for_non_empty_message(client).await?; + + // listen for response from service + let sp_response = handle_response(received)?; + + // match JSON -> ResponseType + let res = match sp_response { + crate::ResponseTypes::Balance(response) => { + println!("{:#?}", response); + response.balance + } + }; + + Ok(res) +} +``` + +That is all of the client code written: now to move on to the `service` that will be interacting with the blockchain on behalf of the `client`. diff --git a/documentation/dev-portal/src/tutorials/cosmos-service/client.md b/documentation/dev-portal/src/tutorials/cosmos-service/client.md index c3f6c6ee10..df3751c4dd 100644 --- a/documentation/dev-portal/src/tutorials/cosmos-service/client.md +++ b/documentation/dev-portal/src/tutorials/cosmos-service/client.md @@ -1,23 +1,79 @@ # Preparing Your Client -Start by creating the startup logic of your `client` - creating a Nym client and connecting to the mixnet (or just connecting if your client has been started before and config already exists for it), and defining commands. - -Start in `bin/client.rs`. +Start by creating the startup logic of your `client` in `bin/client.rs` - creating a Nym client and connecting to the mixnet (or just connecting if your client has been started before and config already exists for it), and defining and running commands. ## Dependencies Import the following dependencies: ``` use clap::{Args, Parser, Subcommand}; +use chain_query::{client::query_balance, create_client}; use nym_sdk::mixnet::Recipient; use nym_validator_client::nyxd::AccountId; -use nym_cosmos_service::create_client; use nym_bin_common::logging::setup_logging; ``` -`clap` is used so different commands can be passed to the `client` (even though we're only defining one function in this first part of the tutorial, more will be added in subsequent chapters). `nym_sdk::mixnet::Recipient` is the type used to define the recipient of a mixnet message, `nym_bin_common::logging::setup_logging` is the logging setup for `client`'s Nym client, and `nym_cosmos_service::create_client` imports the `create_client` function created on the previous page. +`clap` is used so different commands can be passed to the `client` (even though we're only defining one function in this first part of the tutorial, more will be added in subsequent chapters). `nym_sdk::mixnet::Recipient` is the type used to define the recipient of a mixnet message, `nym_bin_common::logging::setup_logging` is the logging setup for `client`'s Nym client, and `chain_query` imports the `create_client` and `query_balance` functions created on the previous page. ## CLI Command with Clap +The following simply defines the commands that the client can perform. For the moment, there is only one: the `query_balance` function created in the previous section. -< simple account query first - then maybe a contract query > +As with the data structures, this structure is being used for ease of adding future commands in subsequent tutorials. -## +```rust +#[derive(Debug, Parser)] +#[clap(name = "rust sdk demo - chain query service")] +#[clap(about = "query the sandbox testnet blockchain via the mixnet... part 2 coming soon")] +struct Cli { + #[clap(subcommand)] + command: Option, +} + +#[derive(Debug, Subcommand)] +enum Commands { + QueryBalance(QueryBalance), +} + +#[derive(Debug, Args)] +struct QueryBalance { + /// the account we want to query + account: AccountId, + /// the address of the broadcaster service - this submits txs and queries the chain on our behalf + sp_address: String, +} +``` + +## `main()` +This is the root logic of the `client`. Using `[tokio](URL_TO_DO)` for the async runtime, this function performs the following functions: +* If not already existing, creates a Nym client with config at `/tmp/client`. Otherwise it loads the already existing client from this config. +* Matches the command from the CLI - in this instance, the `QueryBalance` function which will be defined in the next section. This will create a `BalanceRequest` and send this to the `service`, before returning the response sent back to it to the main thread and printing this to the console. +* Performs a proper shutdown of the running Nym client. + +```rust +#[tokio::main] +async fn main() -> anyhow::Result<()> { + setup_logging(); + let cli = Cli::parse(); + let mut client = create_client("/tmp/client2".into()).await; + let our_address = client.nym_address(); + println!("\nclient's nym address: {our_address}"); + + match cli.command { + Some(Commands::QueryBalance(QueryBalance { + account, + sp_address, + })) => { + println!("\nsending bank balance request to service via mixnet"); + let sp_address = Recipient::try_from_base58_string(sp_address).unwrap(); + let returned_balance = query_balance(account, &mut client, sp_address).await?; + println!("\nreturned balance is: {}", returned_balance); + } + None => { + println!("\nno command specified - nothing to do") + } + } + println!("\ndisconnecting client"); + client.disconnect().await; + println!("client disconnected"); + Ok(()) +} +``` diff --git a/documentation/dev-portal/src/tutorials/cosmos-service/intro.md b/documentation/dev-portal/src/tutorials/cosmos-service/intro.md index 1510d31e2e..342bca9132 100644 --- a/documentation/dev-portal/src/tutorials/cosmos-service/intro.md +++ b/documentation/dev-portal/src/tutorials/cosmos-service/intro.md @@ -4,4 +4,4 @@ This tutorial is for Rust developers wanting to interact with the Rust SDK and t The key here is to think of the service as a proxy: it interacts with the blockchain _on the client's behalf_, shielding it from the Validator it interacts with, whilst also being shielded from the client by the mixnet. -> This service also nicely highlights the limitations of the mixnet - even though with this code your metadata is shielded from the Validator, and even the service does not know your Nym address, application-level information such as a blockchain address is not made private, in virtue of the fact that using the mixnet provides solely network-level privacy. For information on what application-level privacy Nym offers, check out the [coconut credential SDK example](). +> This service also nicely highlights the limitations of the mixnet - even though with this code your metadata is shielded from the Validator, and even the service does not know your Nym address, application-level information such as a blockchain address is not made private, in virtue of the fact that using the mixnet provides solely network-level privacy. For information on what application-level privacy Nym offers, check out the [coconut credential SDK example](RELATIVE_PATH). diff --git a/documentation/dev-portal/src/tutorials/cosmos-service/lib.md b/documentation/dev-portal/src/tutorials/cosmos-service/lib.md index 37a2f46a22..13caff08aa 100644 --- a/documentation/dev-portal/src/tutorials/cosmos-service/lib.md +++ b/documentation/dev-portal/src/tutorials/cosmos-service/lib.md @@ -7,17 +7,20 @@ These include the request and response types the client and the service will be ## Dependencies The dependecies for the shared `lib` file are the following: ```rust -use cosmrs::{tendermint, AccountId}; +use anyhow::bail; +use cosmrs::AccountId; use nym_sdk::mixnet::{ AnonymousSenderTag, MixnetClient, MixnetClientBuilder, ReconstructedMessage, StoragePaths, }; +use nym_validator_client::nyxd::Coin; use serde::{Deserialize, Serialize}; use std::path::PathBuf; + pub mod client; pub mod service; ``` -Since this is the file where client creation and message parsing are handled, the various `nym_sdk` imports, as well as `serde`'s (de)serialisation functionality, are required. `PathBuf` is for reading filepaths, and `cosmrs` types are required for defining Nyx blockchain accounts. +Since this is the file where client creation and message parsing are handled, the various `nym_sdk` imports, as well as `serde`'s (de)serialisation functionality, are required. `PathBuf` is for reading filepaths, `cosmrs` types are required for defining Nyx blockchain accounts, and the `Coin` type from the `nyxd_validator_client` is for our Coin balance request and response. `anyhow` is for easy error handing. ## Constants Below this are the chain-related `const` variables. These have been hardcoded for this demo. @@ -34,37 +37,53 @@ These define the RPC endpoint your service will use to interact with the blockch Define the following structs for our different request and responses that will be serialised and sent through the mixnet between your client and service binaries: ```rust -#[derive(Deserialize, Serialize, Debug)] -pub struct SequenceRequest { +#[derive(Debug, Deserialize, Serialize, PartialEq)] +pub struct BalanceRequest { pub validator: String, - pub signer_address: AccountId, + pub account: AccountId, } -#[derive(Deserialize, Serialize, Debug)] -pub struct SequenceRequestResponse { - pub account_number: u64, - pub sequence: u64, - pub chain_id: [tendermint](tendermint)::chain::Id, +#[derive(Debug, Deserialize, Serialize, PartialEq)] +pub struct BalanceResponse { + pub balance: Coin, } -#[derive(Debug, Deserialize, Serialize)] -#[serde(untagged)] +#[derive(Debug, Deserialize, Serialize, PartialEq)] pub enum RequestTypes { - Sequence(SequenceRequest), + Balance(BalanceRequest), } -#[derive(Debug, Deserialize, Serialize)] -#[serde(untagged)] +impl RequestTypes { + pub fn serialize(&self) -> Vec { + serde_json::to_vec(self).expect("serde failure") + } + + pub fn try_deserialize>(raw: M) -> anyhow::Result { + serde_json::from_slice(raw.as_ref()).map_err(Into::into) + } +} + +#[derive(Debug, Deserialize, Serialize, PartialEq)] pub enum ResponseTypes { - Sequence(SequenceRequestResponse), + Balance(BalanceResponse), +} + +impl ResponseTypes { + pub fn serialize(&self) -> Vec { + serde_json::to_vec(self).expect("serde failure") + } + + pub fn try_deserialize>(raw: M) -> anyhow::Result { + serde_json::from_slice(raw.as_ref()).map_err(Into::into) + } } ``` The above data types are pretty straightforward. Even though there are only one instance of a request type (sent from client to service) and one of a response type (service -> client) so far, a pair of enums has been defined to contain additional response or request types that will be added in the future. -`SequenceRequest` will be used when requesting the service to query the chain on the client's behalf for an address' sequence information (used for offline signing). You can see the information that will be returned from the chain to the service, and from the service to the client, in `SequenceRequestResponse`. +`BalanceRequest` will be used when requesting the service to query the token balance of the supplied address on the client's behalf. You can see the information that will be returned from the chain to the service, and from the service to the client, in `BalanceResponse`. -> Although `SequenceResponse` would have been more succinct, there is already a `cosmrs` type with this name. As such the response type was given a different name to avoid confusion. +Custom serialistion and deserialisation have been implemented for each enum for ease of future modification if required, and testing. ## Shared Functions Now to define functions shared by the `client` and `service` binaries. @@ -90,64 +109,38 @@ pub async fn create_client(config_path: PathBuf) -> MixnetClient { > If keys and config already exist at this location, re-running this function **will not** overwrite them. -### Parsing Incoming messages -Next to define two functions: one for listening _for_ messages from the mixnet (used by our `service`), and one for listening out for a _reply_ after sending a message to another Nym client (in this case, when sending a message from the `client` to the `service`). +### Listening for & Parsing Incoming messages +Next to define two functions: one for listening _for_ messages from the mixnet (used by `service`), and one for handling a _response_ to a request (used by `client`). + +Both functions attempt to deserialise the vec of `ReconstructedMessages` that are reconstructed by the client from delivered Sphinx packets after decryption. + +`handle_request` performs one additional function - parsing the `sender_tag` from the incoming reconstructed message. This is the randomised alphanumeric string used to identify a bucket of _SURBs_ (_S_ingle _U_se _R_eply _B_locks) that are sent along with any outgoing message by default. More information about them can be found [here](LINK_TO_SURBS_DOCS) but all that is necessary to know for now is that these are pre-addressed packets that clients send out with their messages. Any reply to their message that is to be sent back to them back be written to the payload of these packets, but without the entity seeing the destination address. This allows for services to _anonymously reply_ to clients without being able to doxx them. ```rust -pub async fn listen_and_parse_response(client: &mut MixnetClient) -> anyhow::Result { - let mut message: Vec = Vec::new(); - - // get the actual message - discard the empty vec sent along with the SURB topup request - while let Some(new_message) = client.wait_for_messages().await { - if new_message.is_empty() { - println!("got a request for more SURBs from service - sending additional SURBs to sender"); - continue; - } - message = new_message; - break; - } - - // parse vec -> JSON String - let mut parsed = String::new(); - if let Some(r) = message.iter().next() { - parsed = String::from_utf8(r.message.clone())?; - } - let sp_response: crate::ResponseTypes = serde_json::from_str(&parsed)?; - Ok(sp_response) +pub fn handle_response(message: ReconstructedMessage) -> anyhow::Result { + ResponseTypes::try_deserialize(message.message) } -pub async fn listen_and_parse_request( - client: &mut MixnetClient, -) -> anyhow::Result<(RequestTypes, AnonymousSenderTag)> { - let mut message: Vec = Vec::new(); - - while let Some(new_message) = client.wait_for_messages().await { - if new_message.is_empty() { - println!("got empty vec - probably the SURBs sent along with the request"); - continue; - } - message = new_message; - break; - } - - // parse vec -> JSON String - let mut parsed = String::new(); - if let Some(r) = message.iter().next() { - parsed = String::from_utf8(r.message.clone())?; - } - let client_request: crate::RequestTypes = serde_json::from_str(&parsed)?; - - // get the sender_tag for anon reply - let return_recipient = message[0].sender_tag.unwrap(); - - Ok((client_request, return_recipient)) +pub fn handle_request( + message: ReconstructedMessage, +) -> anyhow::Result<(RequestTypes, Option)> { + let request = RequestTypes::try_deserialize(message.message)?; + Ok((request, message.sender_tag)) } ``` -Aside from the return types that each function has, the main difference is that any incoming requests to the `service` will also have SURB packets attached to it. +Before moving on to the `client` and `service` code, one more function is needed. This allows for both binaries to parse empty incoming messages that they might receive. This is necessary as incoming SURBs as well as requests for more SURBs contain empty data fields for the moment. -< smol explanation of SURBs + link > +```rust +pub async fn wait_for_non_empty_message( + client: &mut MixnetClient, +) -> anyhow::Result { + while let Some(mut new_message) = client.wait_for_messages().await { + if !new_message.is_empty() { + return Ok(new_message.pop().unwrap()); + } + } -As such, this function returns a tuple containing the `RequestType` _and_ the `sender_tag` used by the `service` to identify which bucket of pre-addressed replies it will use to respond to a request. - -< note concerning parsing out the empty vecs > + bail!("did not receive any non-empty message") +} +``` diff --git a/documentation/dev-portal/src/tutorials/cosmos-service/overview.md b/documentation/dev-portal/src/tutorials/cosmos-service/overview.md index 13acb800c9..d9f365d72c 100644 --- a/documentation/dev-portal/src/tutorials/cosmos-service/overview.md +++ b/documentation/dev-portal/src/tutorials/cosmos-service/overview.md @@ -2,7 +2,7 @@ This tutorial involves writing two pieces of code in Rust: -- A client side binary used to construct a blockchain query and send this query to a service, which will interact with a Cosmos SDK blockchain on our behalf (bear in mind this principle works for all blockchains - we're just utilising the `cosmrs` library to interact with the Nyx blockchain in this tutorial). +- A client side binary used to (for now) construct a blockchain query and send this query to a service, which will interact with a Cosmos SDK blockchain on our behalf (bear in mind this principle works for all blockchains - we're just utilising the `cosmrs` library to interact with the Nyx blockchain in this tutorial). This query will be to query the balance of an account, in preparation for spending these tokens on a credential in a subsequent tutorial. - A service which will listen out for requests from the mixnet, act on those requests, and anonymously reply to the client sending the requests. You will learn how to do the following with the Rust SDK: @@ -44,7 +44,7 @@ You can find the code for these components [here](). You can use it as a referen Notice that this tutorial attempts to use very few external libraries. This tutorial is not showing you how to build production-grade code, but **to understand how to connect and send messages to, as well as recieve messages from, the mixnet.** ```admonish note title="Sidenote: What is a Service / Service Provider?" -'Service' or 'Service Provider' are catchall names used to refer to any type of app that can communicate with the mixnet via a Nym client - in this case, one embedded in your app process via the Rust SDK. +'Service' or 'Service Provider' are catchall names used to refer to any type of app that can communicate with the mixnet via a Nym client - in this case, one embedded in its app process via the Rust SDK. The first SP to have been released is the [Network Requester](https://nymtech.net/docs/nodes/network-requester-setup.html) - a binary that receives a network request from the mixnet, performs that request (e.g. authenticating with a message server and receiving new messages for a user) and then passes the response back to the user who requested it anonymously, shielding their metadata from the message server. diff --git a/documentation/dev-portal/src/tutorials/cosmos-service/preparing-env.md b/documentation/dev-portal/src/tutorials/cosmos-service/preparing-env.md index 341c89a216..474a3add08 100644 --- a/documentation/dev-portal/src/tutorials/cosmos-service/preparing-env.md +++ b/documentation/dev-portal/src/tutorials/cosmos-service/preparing-env.md @@ -28,16 +28,17 @@ cargo new nym-cosmos-service * Add the following dependencies to your `Cargo.toml` file: ``` [dependencies] -anyhow = "1.0.72" clap = { version = "4.0", features = ["derive"] } -bip39 = { version = "2.0.0", features = ["zeroize"] } cosmrs = "=0.14.0" -TODO -# tokio = { workspace = true, features = ["rt-multi-thread", "macros"] } -tokio = "1.24.1" -bs58 = "0.5.0" +tokio = { version = "1.24.1", features = ["rt-multi-thread", "macros"] } serde = "1.0.152" serde_json = "1.0.91" +nym-sdk = { git = "https://github.com/nymtech/nym", rev = "5dacf0c8f8775de6168d4da808fdce56e1ac2706" } +nym-sphinx-addressing = { git = "https://github.com/nymtech/nym", rev = "5dacf0c8f8775de6168d4da808fdce56e1ac2706" } +nym-validator-client = { git = "https://github.com/nymtech/nym", rev = "5dacf0c8f8775de6168d4da808fdce56e1ac2706" } +nym-bin-common = { git = "https://github.com/nymtech/nym", rev = "5dacf0c8f8775de6168d4da808fdce56e1ac2706" } +nym-sphinx-anonymous-replies = { git = "https://github.com/nymtech/nym", rev = "5dacf0c8f8775de6168d4da808fdce56e1ac2706" } +anyhow = "1.0.72" ``` These are non Nym-specific dependencies for the project. `anyhow` is for catch-all error handling, `clap` is for setting up the CLI commands, `cosmrs` for cosmos-specific types and functionality, `tokio` for the async/await environment, and `serde` for (de)serialisation. diff --git a/documentation/dev-portal/src/tutorials/cosmos-service/service-src.md b/documentation/dev-portal/src/tutorials/cosmos-service/service-src.md new file mode 100644 index 0000000000..cc140cc11f --- /dev/null +++ b/documentation/dev-portal/src/tutorials/cosmos-service/service-src.md @@ -0,0 +1 @@ +# Preparing Your Service pt2 diff --git a/documentation/dev-portal/src/tutorials/cosmos-service/service.md b/documentation/dev-portal/src/tutorials/cosmos-service/service.md index 1090443702..8b4c3377bd 100644 --- a/documentation/dev-portal/src/tutorials/cosmos-service/service.md +++ b/documentation/dev-portal/src/tutorials/cosmos-service/service.md @@ -1 +1,71 @@ # Preparing Your Service +In `bin/src.rs` define the startup and response logic of the `service`. Client connection / config reading happens as it does in `bin/client.rs`. + +## Dependencies +``` +use chain_query::{ + create_client, handle_request, + service::{create_broadcaster, get_balance}, + BalanceResponse, RequestTypes, ResponseTypes, +}; +use nym_sphinx_anonymous_replies::{self, requests::AnonymousSenderTag}; +use nym_bin_common::logging::setup_logging +``` + +The imports from `chain_query` are most of the data types and functions defined in the previous sections of this tutorial. + +The `AnonymousSenderTag` type is used for SURBs. + +## main() +Also using [tokio](URL) for the async runtime, `main` does the following: +* If not already existing, creates a Nym client with config at `/tmp/client`. Otherwise it loads the already existing client from this config. +* Create a `broadcaster` - this is used by the service to interact with the blockchain, using the consts defined in `src/lib.rs` as chain config. +* Listen out for incoming messages, and in much the same way as the `client`, handle and match the incoming request. +* Using the `sender_tag`, anonymously reply to the Nym client in `client` with the response from the blockchain. + +```rust +#[tokio::main] +async fn main() -> anyhow::Result<()> { + setup_logging(); + let mut client = create_client("/tmp/service2".into()).await; + let our_address = client.nym_address(); + println!("\nservice's nym address: {our_address}"); + // the httpclient we will use to broadcast our query to the blockchain + let broadcaster = create_broadcaster().await?; + println!("listening for messages, press CTRL-C to exit"); + + while let Some(received) = client.wait_for_messages().await { + for msg in received { + let request = match handle_request(msg) { + Ok(request) => request, + Err(err) => { + eprintln!("failed to handle received request: {err}"); + continue; + } + }; + + let return_recipient: AnonymousSenderTag = request.1.expect("no sender tag received"); + match request.0 { + RequestTypes::Balance(request) => { + println!("\nincoming balance request for: {}\n", request.account); + + let balance: BalanceResponse = + get_balance(broadcaster.clone(), request.account).await?; + + let response = ResponseTypes::Balance(balance); + + println!("response from chain: {:#?}", response); + + println!("\nreturn recipient surb bucket: {}", &return_recipient); + println!("\nsending response to {}", &return_recipient); + // send response back to anon requesting client via mixnet + client + .send_str_reply(return_recipient, &serde_json::to_string(&response)?) + .await; + } + } + } + } + + Ok(()) +``` From 441fbf82556c488f3077b87cba505b8d845f9ffa Mon Sep 17 00:00:00 2001 From: mfahampshire Date: Wed, 2 Aug 2023 23:29:56 +0200 Subject: [PATCH 356/361] added new pages for client and service /src/ files --- documentation/dev-portal/src/SUMMARY.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/documentation/dev-portal/src/SUMMARY.md b/documentation/dev-portal/src/SUMMARY.md index d0089366d7..bece5f7237 100644 --- a/documentation/dev-portal/src/SUMMARY.md +++ b/documentation/dev-portal/src/SUMMARY.md @@ -36,7 +36,9 @@ - [Preparing Your Environment](tutorials/cosmos-service/preparing-env.md) - [Preparing Your Lib](tutorials/cosmos-service/lib.md) - [Preparing Your Client](tutorials/cosmos-service/client.md) + - [Preparing Your Client pt2](tutorials/cosmos-service/client-src.md) - [Preparing Your Service](tutorials/cosmos-service/service.md) + - [Preparing Your Service pt2](tutorials/cosmos-service/service-src.md) - [(Typescript) Simple Service Provider](tutorials/simple-service-provider/simple-service-provider.md) - [Tutorial Overview](tutorials/simple-service-provider/overview.md) - [Preparing Your User Client Environment](tutorials/simple-service-provider/preparating-env.md) From c62b3443492b0fc73b10b1ca05adec645fa2cde4 Mon Sep 17 00:00:00 2001 From: mfahampshire Date: Thu, 3 Aug 2023 10:53:23 +0200 Subject: [PATCH 357/361] finished first pass at tutorial --- documentation/dev-portal/src/SUMMARY.md | 33 +++++++------- .../tutorials/cosmos-service/client-src.md | 2 +- .../src/tutorials/cosmos-service/intro.md | 2 +- .../src/tutorials/cosmos-service/lib.md | 6 +-- .../src/tutorials/cosmos-service/overview.md | 39 +++++++--------- .../src/tutorials/cosmos-service/querying.md | 43 ++++++++++++++++++ .../tutorials/cosmos-service/service-src.md | 44 +++++++++++++++++++ .../src/tutorials/cosmos-service/service.md | 2 +- .../dev-portal/src/tutorials/rust-sdk.md | 5 +++ .../dev-portal/src/tutorials/rust.md | 1 + .../dev-portal/src/tutorials/typescript.md | 3 ++ 11 files changed, 135 insertions(+), 45 deletions(-) create mode 100644 documentation/dev-portal/src/tutorials/cosmos-service/querying.md create mode 100644 documentation/dev-portal/src/tutorials/rust-sdk.md create mode 100644 documentation/dev-portal/src/tutorials/rust.md create mode 100644 documentation/dev-portal/src/tutorials/typescript.md diff --git a/documentation/dev-portal/src/SUMMARY.md b/documentation/dev-portal/src/SUMMARY.md index bece5f7237..2095d6bc30 100644 --- a/documentation/dev-portal/src/SUMMARY.md +++ b/documentation/dev-portal/src/SUMMARY.md @@ -31,21 +31,24 @@ # Tutorials -- [(Rust SDK) Cosmos Blockchain Service](tutorials/cosmos-service/intro.md) - - [Tutorial Overview](tutorials/cosmos-service/overview.md) - - [Preparing Your Environment](tutorials/cosmos-service/preparing-env.md) - - [Preparing Your Lib](tutorials/cosmos-service/lib.md) - - [Preparing Your Client](tutorials/cosmos-service/client.md) - - [Preparing Your Client pt2](tutorials/cosmos-service/client-src.md) - - [Preparing Your Service](tutorials/cosmos-service/service.md) - - [Preparing Your Service pt2](tutorials/cosmos-service/service-src.md) -- [(Typescript) Simple Service Provider](tutorials/simple-service-provider/simple-service-provider.md) - - [Tutorial Overview](tutorials/simple-service-provider/overview.md) - - [Preparing Your User Client Environment](tutorials/simple-service-provider/preparating-env.md) - - [Building Your User Client](tutorials/simple-service-provider/user-client.md) - - [Preparing Your Service Provider Environment](tutorials/simple-service-provider/preparating-env2.md) - - [Building Your Service Provider](tutorials/simple-service-provider/service-provider.md) - - [Sending a Message Through the Mixnet](tutorials/simple-service-provider/sending-message.md) +- [Rust SDK](tutorials/rust-sdk.md) + - [Blockchain Service pt1](tutorials/cosmos-service/intro.md) + - [Tutorial Overview](tutorials/cosmos-service/overview.md) + - [Preparing Your Environment](tutorials/cosmos-service/preparing-env.md) + - [Preparing Your Lib](tutorials/cosmos-service/lib.md) + - [Preparing Your Client](tutorials/cosmos-service/client.md) + - [Preparing Your Client pt2](tutorials/cosmos-service/client-src.md) + - [Preparing Your Service](tutorials/cosmos-service/service.md) + - [Preparing Your Service pt2](tutorials/cosmos-service/service-src.md) + - [Querying the Chain](tutorials/cosmos-service/querying.md) +- [Typescript](tutorials/typescript.md) + - [Simple Service Provider](tutorials/simple-service-provider/simple-service-provider.md) + - [Tutorial Overview](tutorials/simple-service-provider/overview.md) + - [Preparing Your User Client Environment](tutorials/simple-service-provider/preparating-env.md) + - [Building Your User Client](tutorials/simple-service-provider/user-client.md) + - [Preparing Your Service Provider Environment](tutorials/simple-service-provider/preparating-env2.md) + - [Building Your Service Provider](tutorials/simple-service-provider/service-provider.md) + - [Sending a Message Through the Mixnet](tutorials/simple-service-provider/sending-message.md) # Community Resources diff --git a/documentation/dev-portal/src/tutorials/cosmos-service/client-src.md b/documentation/dev-portal/src/tutorials/cosmos-service/client-src.md index 7b8085fc54..b8b223357b 100644 --- a/documentation/dev-portal/src/tutorials/cosmos-service/client-src.md +++ b/documentation/dev-portal/src/tutorials/cosmos-service/client-src.md @@ -14,7 +14,7 @@ use nym_validator_client::nyxd::Coin # Querying via the Mixnet The following is used to construct a `BalanceRequest`, send this to the supplied `service` address, and then handle the response, matching it to a `ResponseType`, in this case the only expected response, a `BalanceResponse`. -The actual sending of the request is performed by `client.send_bytes`: sending the serialised `BalanceRequest` to the supplied Nym address (the `Recipient` imported from the `nym_sphinx_addressing` crate). It is sending the default number of SURBs along with the message, defined [here](LINK_TO_SURB_DEFAULT_NUMBEr). +The actual sending of the request is performed by `client.send_bytes`: sending the serialised `BalanceRequest` to the supplied Nym address (the `Recipient` imported from the `nym_sphinx_addressing` crate). It is sending the default number of SURBs along with the message, defined [here](https://github.com/nymtech/nym/blob/develop/sdk/rust/nym-sdk/src/mixnet/client.rs#L34). ```rust pub async fn query_balance( diff --git a/documentation/dev-portal/src/tutorials/cosmos-service/intro.md b/documentation/dev-portal/src/tutorials/cosmos-service/intro.md index 342bca9132..5b08d0ecfe 100644 --- a/documentation/dev-portal/src/tutorials/cosmos-service/intro.md +++ b/documentation/dev-portal/src/tutorials/cosmos-service/intro.md @@ -4,4 +4,4 @@ This tutorial is for Rust developers wanting to interact with the Rust SDK and t The key here is to think of the service as a proxy: it interacts with the blockchain _on the client's behalf_, shielding it from the Validator it interacts with, whilst also being shielded from the client by the mixnet. -> This service also nicely highlights the limitations of the mixnet - even though with this code your metadata is shielded from the Validator, and even the service does not know your Nym address, application-level information such as a blockchain address is not made private, in virtue of the fact that using the mixnet provides solely network-level privacy. For information on what application-level privacy Nym offers, check out the [coconut credential SDK example](RELATIVE_PATH). +> This service also nicely highlights the limitations of the mixnet - even though with this code your metadata is shielded from the Validator, and even the service does not know your Nym address, application-level information such as a blockchain address is not made private, in virtue of the fact that using the mixnet provides solely network-level privacy. For information on what application-level privacy Nym offers, check out the [coconut credential SDK example](https://nymtech.net/docs/sdk/rust.html#coconut-credential-generation). diff --git a/documentation/dev-portal/src/tutorials/cosmos-service/lib.md b/documentation/dev-portal/src/tutorials/cosmos-service/lib.md index 13caff08aa..8adb147bd4 100644 --- a/documentation/dev-portal/src/tutorials/cosmos-service/lib.md +++ b/documentation/dev-portal/src/tutorials/cosmos-service/lib.md @@ -79,7 +79,7 @@ impl ResponseTypes { } ``` -The above data types are pretty straightforward. Even though there are only one instance of a request type (sent from client to service) and one of a response type (service -> client) so far, a pair of enums has been defined to contain additional response or request types that will be added in the future. +The above data types are pretty straightforward. Even though there are only one instance of a request type (sent from client -> service) and one of a response type (service -> client) so far, a pair of enums has been defined to contain additional response or request types that will be added in the future. `BalanceRequest` will be used when requesting the service to query the token balance of the supplied address on the client's behalf. You can see the information that will be returned from the chain to the service, and from the service to the client, in `BalanceResponse`. @@ -114,7 +114,7 @@ Next to define two functions: one for listening _for_ messages from the mixnet ( Both functions attempt to deserialise the vec of `ReconstructedMessages` that are reconstructed by the client from delivered Sphinx packets after decryption. -`handle_request` performs one additional function - parsing the `sender_tag` from the incoming reconstructed message. This is the randomised alphanumeric string used to identify a bucket of _SURBs_ (_S_ingle _U_se _R_eply _B_locks) that are sent along with any outgoing message by default. More information about them can be found [here](LINK_TO_SURBS_DOCS) but all that is necessary to know for now is that these are pre-addressed packets that clients send out with their messages. Any reply to their message that is to be sent back to them back be written to the payload of these packets, but without the entity seeing the destination address. This allows for services to _anonymously reply_ to clients without being able to doxx them. +`handle_request` performs one additional function - parsing the `sender_tag` from the incoming reconstructed message. This is the randomised alphanumeric string used to identify a bucket of _SURBs_ (Single Use Reply Blocks) that are sent along with any outgoing message by default. More information about them can be found [here](https://nymtech.net/docs/architecture/traffic-flow.html#private-replies-using-surbs) but all that is necessary to know for now is that these are pre-addressed packets that clients send out with their messages. Any reply to their message that is to be sent back to them back be written to the payload of these packets, but without the entity seeing the destination address. This allows for services to _anonymously reply_ to clients without being able to doxx them. ```rust pub fn handle_response(message: ReconstructedMessage) -> anyhow::Result { @@ -129,7 +129,7 @@ pub fn handle_request( } ``` -Before moving on to the `client` and `service` code, one more function is needed. This allows for both binaries to parse empty incoming messages that they might receive. This is necessary as incoming SURBs as well as requests for more SURBs contain empty data fields for the moment. +Before moving on to the `client` and `service` code, one more function is needed. This allows for both binaries to parse empty incoming messages that they might receive. This is necessary as incoming SURBs as well as requests for more SURBs contain empty data fields. ```rust pub async fn wait_for_non_empty_message( diff --git a/documentation/dev-portal/src/tutorials/cosmos-service/overview.md b/documentation/dev-portal/src/tutorials/cosmos-service/overview.md index d9f365d72c..a422ea4047 100644 --- a/documentation/dev-portal/src/tutorials/cosmos-service/overview.md +++ b/documentation/dev-portal/src/tutorials/cosmos-service/overview.md @@ -13,30 +13,21 @@ You will learn how to do the following with the Rust SDK: ``` - +----------+ +----------+ +----------+ - | Mix Node |<-----------> | Mix Node |<----------->| Mix Node | - | Layer 1 | | Layer 2 | | Layer 3 | - +----------+ +----------+ +----------+ - ^ ^ - | | - |<--------------------------------------------------+ - | - v - +--------------+ - | Your gateway | - +--------------+ - ^ - | - | - v -+-------------------------------------------+ -| | -| +-------------+ +--------------+ | -| | Client code | | Service code | | -| +-------------+ +--------------+ | -| | -+-------------------------------------------+ - Your Local Machine + +TODO + +REDRAW + +ASCII + +DIAGRAM + +FROM + +PREVIOUS + +TUTORIAL + ``` You can find the code for these components [here](). You can use it as a reference while building or simply download it and follow along as you progress through the tutorial. diff --git a/documentation/dev-portal/src/tutorials/cosmos-service/querying.md b/documentation/dev-portal/src/tutorials/cosmos-service/querying.md new file mode 100644 index 0000000000..04a6f92832 --- /dev/null +++ b/documentation/dev-portal/src/tutorials/cosmos-service/querying.md @@ -0,0 +1,43 @@ +# Querying the Chain +Now that all the code has been written, time to query the blockchain. + +To test against the account operating one of the mix nodes on the testnet, use `n1lcutqz94k739s39u26rvexql40ehf42zd27fwe` in the following instructions: + +```sh +# build the binaries +cargo build --release + +# open two console windows. In one run the following +./target/release/service + +# copy the printed Nym address from the console - this is used by the client when running the chain query + +# in the other run +./target/release/client query-balance n1lcutqz94k739s39u26rvexql40ehf42zd27fwe +``` + +The following happens: +* `client` and `service` both start their Nym clients and log the address. `service` is listening for incoming messages from the mixnet. +* `client` sends a request to `service` using the supplied `n1...` address as the Nyx account to query the balance of, and the supplied Nym address to communicate with this instance of `service`. +* `service` queries the Sandbox testnet blockchain using the `broadcaster` http client. It then serialises the response and returns it using SURBs to the `client`. + +All in all, quite simple. By using the service as a proxy, the client never interacts with the blockchain, thus is not revealing metadata to the operator of the chain or anyone watching incoming traffic to it. Furthermore, the client doesn't even need to share its Nym address with the service, as the service is able to reply via SURBs. + +## Creating Sandbox Account +If you wish to create an account on Sandbox to use instead of the supplied account above, the easiest way is by building the `nym-cli` tool and using that to create one: + +```sh +# start from the root of the nym monorepo +cd tools/nym-cli +# build +cargo build --release +# create account using Sandbox testnet environment +../../target/release/nym-cli --config-env-file ../../envs/sandbox.env account create +``` + +However, since this account is fresh, it won't have any tokens. Querying the balance will still work obviously, it will just return `0`. + +## Get Tokens +We're working on getting the faucet up and running again in preparation for part 2 of this tutorial: using the tokens you have privately checked the balance of to generate a [bandwidth credential](https://nymtech.net/docs/bandwidth-credentials.html). + +If you wish to get testnet tokens already then feel free to ask in the [Dev channel](https://matrix.to/#/#dev:nymtech.chat) on Matrix. \ No newline at end of file diff --git a/documentation/dev-portal/src/tutorials/cosmos-service/service-src.md b/documentation/dev-portal/src/tutorials/cosmos-service/service-src.md index cc140cc11f..6fb3324726 100644 --- a/documentation/dev-portal/src/tutorials/cosmos-service/service-src.md +++ b/documentation/dev-portal/src/tutorials/cosmos-service/service-src.md @@ -1 +1,45 @@ # Preparing Your Service pt2 + +Now to define the logic of creating the `broadcaster` for interacting with the blockchain, and querying it in `src/service.rs`. + +## Dependencies +The following dependencies are for creating a client to interact with the blockchain, and deal with the returned `Coin` type expected from the balance query. + +```rust +use crate::{BalanceResponse, DEFAULT_DENOM, DEFAULT_VALIDATOR_RPC}; +use cosmrs::rpc::HttpClient; +use cosmrs::AccountId; +use nym_validator_client::nyxd::{Coin, CosmWasmClient}; +``` + +## Creating a `broadcaster` +The `broadcaster` is an `HttpClient` taken from `cosmrs`, created with the `DEFAULT_VALIDATOR_RPC` as its default endpoint. The service will use this to query the Sandbox testnet chain. + +```rust +pub async fn create_broadcaster() -> anyhow::Result { + let broadcaster: HttpClient = HttpClient::new(DEFAULT_VALIDATOR_RPC)?; + Ok(broadcaster) +} +``` + +## Querying the Chain +Now to write the logic for querying the chain, using the `broadcaster` created in the previous step to query for the balance of the `account` that the client sent via the mixnet in the `BalanceRequest`. This function returns a `BalanceResponse` containing a `Coin` type, denoting the `balance` and `denom` returned by `get_balance()`. + +```rust +pub async fn get_balance( + broadcaster: HttpClient, + account: AccountId, +) -> anyhow::Result { + let balance = broadcaster + .get_balance(&account, DEFAULT_DENOM.to_string()) + .await + .unwrap() + .unwrap(); + Ok(BalanceResponse { + balance: Coin { + amount: balance.amount, + denom: balance.denom, + }, + }) +} +``` \ No newline at end of file diff --git a/documentation/dev-portal/src/tutorials/cosmos-service/service.md b/documentation/dev-portal/src/tutorials/cosmos-service/service.md index 8b4c3377bd..f8313bed8b 100644 --- a/documentation/dev-portal/src/tutorials/cosmos-service/service.md +++ b/documentation/dev-portal/src/tutorials/cosmos-service/service.md @@ -17,7 +17,7 @@ The imports from `chain_query` are most of the data types and functions defined The `AnonymousSenderTag` type is used for SURBs. ## main() -Also using [tokio](URL) for the async runtime, `main` does the following: +Also using tokio for the async runtime, `main` does the following: * If not already existing, creates a Nym client with config at `/tmp/client`. Otherwise it loads the already existing client from this config. * Create a `broadcaster` - this is used by the service to interact with the blockchain, using the consts defined in `src/lib.rs` as chain config. * Listen out for incoming messages, and in much the same way as the `client`, handle and match the incoming request. diff --git a/documentation/dev-portal/src/tutorials/rust-sdk.md b/documentation/dev-portal/src/tutorials/rust-sdk.md new file mode 100644 index 0000000000..f8413de28b --- /dev/null +++ b/documentation/dev-portal/src/tutorials/rust-sdk.md @@ -0,0 +1,5 @@ +# Rust SDK + +The Rust SDK allows developers building applications in Rust to import and interact with Nym clients as they would any other dependency, instead of running the client as a seperate process on their machine. + +[Read the docs](https://nymtech.net/docs/sdk/rust.html). \ No newline at end of file diff --git a/documentation/dev-portal/src/tutorials/rust.md b/documentation/dev-portal/src/tutorials/rust.md new file mode 100644 index 0000000000..5aef071e46 --- /dev/null +++ b/documentation/dev-portal/src/tutorials/rust.md @@ -0,0 +1 @@ +# Rust SDK diff --git a/documentation/dev-portal/src/tutorials/typescript.md b/documentation/dev-portal/src/tutorials/typescript.md new file mode 100644 index 0000000000..8c6e758ff4 --- /dev/null +++ b/documentation/dev-portal/src/tutorials/typescript.md @@ -0,0 +1,3 @@ +# Typescript + +Tutorial code in this section is built to interact with a standalone Nym client. You can read about interacting with standalone clients [here](https://nymtech.net/docs/clients/websocket-client.html#connecting-to-the-local-websocket), although it is usually preferable to use the [Typescript SDK](https://nymtech.net/docs/sdk/typescript.html). \ No newline at end of file From e7e48f0e530a052a02a8c5c77eb9f4931a1cfb19 Mon Sep 17 00:00:00 2001 From: mfahampshire Date: Tue, 8 Aug 2023 10:59:42 +0200 Subject: [PATCH 358/361] added ide config to gitignore --- documentation/dev-portal/.gitignore | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/documentation/dev-portal/.gitignore b/documentation/dev-portal/.gitignore index bb380f03ab..c29699d0c0 100644 --- a/documentation/dev-portal/.gitignore +++ b/documentation/dev-portal/.gitignore @@ -17,4 +17,6 @@ book theme/ theme -theme/* \ No newline at end of file +theme/* + +.idea \ No newline at end of file From c143fef9125f3ff51e96accbabcc4a1d34f873d2 Mon Sep 17 00:00:00 2001 From: mfahampshire Date: Tue, 8 Aug 2023 11:00:11 +0200 Subject: [PATCH 359/361] tweaks to tutorial --- .../tutorials/cosmos-service/client-src.md | 8 +++-- .../src/tutorials/cosmos-service/client.md | 8 ++--- .../src/tutorials/cosmos-service/intro.md | 6 ++-- .../src/tutorials/cosmos-service/lib.md | 33 +++++++++++++++---- .../src/tutorials/cosmos-service/overview.md | 9 +++-- .../tutorials/cosmos-service/preparing-env.md | 19 ++++------- .../tutorials/cosmos-service/service-src.md | 2 +- .../src/tutorials/cosmos-service/service.md | 6 ++-- 8 files changed, 53 insertions(+), 38 deletions(-) diff --git a/documentation/dev-portal/src/tutorials/cosmos-service/client-src.md b/documentation/dev-portal/src/tutorials/cosmos-service/client-src.md index b8b223357b..63fb82b99b 100644 --- a/documentation/dev-portal/src/tutorials/cosmos-service/client-src.md +++ b/documentation/dev-portal/src/tutorials/cosmos-service/client-src.md @@ -8,11 +8,13 @@ use crate::{handle_response, wait_for_non_empty_message, RequestTypes, DEFAULT_V use cosmrs::AccountId; use nym_sdk::mixnet::MixnetClient; use nym_sphinx_addressing::clients::Recipient; -use nym_validator_client::nyxd::Coin +use nym_validator_client::nyxd::Coin; ``` +As well as importing message-handling functionality, request types, and the default RPC endpoint, this file relies on the `AccountId` type to construct blockchain addresses, the `MixnetClient` for interacting with the mixnet, the `Recipient` type to construct mixnet recipient addresses, and the `Coin` type for properly handling the returned balance of the account that will be queried. + # Querying via the Mixnet -The following is used to construct a `BalanceRequest`, send this to the supplied `service` address, and then handle the response, matching it to a `ResponseType`, in this case the only expected response, a `BalanceResponse`. +The following is used to construct a `BalanceRequest`, send this to the supplied `service` address, and then handle the response, matching it to a `ResponseType` (in this case the only expected response, a `BalanceResponse`). The actual sending of the request is performed by `client.send_bytes`: sending the serialised `BalanceRequest` to the supplied Nym address (the `Recipient` imported from the `nym_sphinx_addressing` crate). It is sending the default number of SURBs along with the message, defined [here](https://github.com/nymtech/nym/blob/develop/sdk/rust/nym-sdk/src/mixnet/client.rs#L34). @@ -50,4 +52,4 @@ pub async fn query_balance( } ``` -That is all of the client code written: now to move on to the `service` that will be interacting with the blockchain on behalf of the `client`. +That is all the client code written: now to move on to the `service` that will be interacting with the blockchain on behalf of the `client`. diff --git a/documentation/dev-portal/src/tutorials/cosmos-service/client.md b/documentation/dev-portal/src/tutorials/cosmos-service/client.md index df3751c4dd..be9f142f09 100644 --- a/documentation/dev-portal/src/tutorials/cosmos-service/client.md +++ b/documentation/dev-portal/src/tutorials/cosmos-service/client.md @@ -43,10 +43,10 @@ struct QueryBalance { ``` ## `main()` -This is the root logic of the `client`. Using `[tokio](URL_TO_DO)` for the async runtime, this function performs the following functions: -* If not already existing, creates a Nym client with config at `/tmp/client`. Otherwise it loads the already existing client from this config. -* Matches the command from the CLI - in this instance, the `QueryBalance` function which will be defined in the next section. This will create a `BalanceRequest` and send this to the `service`, before returning the response sent back to it to the main thread and printing this to the console. -* Performs a proper shutdown of the running Nym client. +This is the root logic of the `client`. Using `[tokio](https://tokio.rs/)` for the async runtime, this function performs the following functions: +* If not already existing, create a Nym client with config at `/tmp/client`. Otherwise load the already existing client from this config. +* Matche the command from the CLI - in this instance, the `QueryBalance` function which will be defined in the next section. This creates a `BalanceRequest` and sends this to the `service`, before returning the response back to the main thread and print this to the console. +* Perform a proper shutdown of the Nym client. ```rust #[tokio::main] diff --git a/documentation/dev-portal/src/tutorials/cosmos-service/intro.md b/documentation/dev-portal/src/tutorials/cosmos-service/intro.md index 5b08d0ecfe..cf9dad5fd0 100644 --- a/documentation/dev-portal/src/tutorials/cosmos-service/intro.md +++ b/documentation/dev-portal/src/tutorials/cosmos-service/intro.md @@ -1,7 +1,7 @@ -# Building a Cosmos Blockchain Service with the Rust SDK +# Interacting with a Cosmos SDK Blockchain via the Mixnet with the Rust SDK -This tutorial is for Rust developers wanting to interact with the Rust SDK and take a first step at building a service with which to interact with a blockchain. +This tutorial is for Rust developers wanting to interact with the Rust SDK and take a first step at building a service with which to interact with a Cosmos SDK blockchain. -The key here is to think of the service as a proxy: it interacts with the blockchain _on the client's behalf_, shielding it from the Validator it interacts with, whilst also being shielded from the client by the mixnet. +The key here is to think of the service as a proxy: it interacts with the blockchain _on the client's behalf_, shielding the client from the Validator it interacts with, whilst also being shielded from the client by the mixnet. > This service also nicely highlights the limitations of the mixnet - even though with this code your metadata is shielded from the Validator, and even the service does not know your Nym address, application-level information such as a blockchain address is not made private, in virtue of the fact that using the mixnet provides solely network-level privacy. For information on what application-level privacy Nym offers, check out the [coconut credential SDK example](https://nymtech.net/docs/sdk/rust.html#coconut-credential-generation). diff --git a/documentation/dev-portal/src/tutorials/cosmos-service/lib.md b/documentation/dev-portal/src/tutorials/cosmos-service/lib.md index 8adb147bd4..0be72a690d 100644 --- a/documentation/dev-portal/src/tutorials/cosmos-service/lib.md +++ b/documentation/dev-portal/src/tutorials/cosmos-service/lib.md @@ -5,7 +5,7 @@ Now move on to preparing shared data structures and functions in `src/lib.rs`. These include the request and response types the client and the service will be passing through the mixnet, as well as shared functions such as client creation, and message parsing. ## Dependencies -The dependecies for the shared `lib` file are the following: +The dependencies for the shared `lib` file are the following: ```rust use anyhow::bail; use cosmrs::AccountId; @@ -31,7 +31,7 @@ pub const DEFAULT_DENOM: &str = "unym"; pub const DEFAULT_PREFIX: &str = "n"; ``` -These define the RPC endpoint your service will use to interact with the blockchain - in this case the Sandbox testnet - as well as the coin denomination and Bech32-prefix of the blockchain accounts. +These define the RPC endpoint your service will use to interact with the blockchain - in this case the Sandbox testnet - as well as the expected coin denomination, and Bech32-prefix of addresses. ## Shared Data Structures Define the following structs for our different request and responses that will be serialised and sent through the mixnet between your client and service binaries: @@ -79,17 +79,17 @@ impl ResponseTypes { } ``` -The above data types are pretty straightforward. Even though there are only one instance of a request type (sent from client -> service) and one of a response type (service -> client) so far, a pair of enums has been defined to contain additional response or request types that will be added in the future. +The above data types are pretty straightforward. Even though there are only one instance of a request type (sent from `client` -> mixnet -> `service`) and one of a response type (`service` -> mixnet -> `client`) so far, a pair of enums has been defined to contain additional response and request types that will be added in part 2 of this tutorial, when adding credential functionality. `BalanceRequest` will be used when requesting the service to query the token balance of the supplied address on the client's behalf. You can see the information that will be returned from the chain to the service, and from the service to the client, in `BalanceResponse`. -Custom serialistion and deserialisation have been implemented for each enum for ease of future modification if required, and testing. +Custom serialistion and deserialisation have been implemented for each enum for ease of future modification and testing. ## Shared Functions Now to define functions shared by the `client` and `service` binaries. ### Client Creation -The following function is called on startup by each binary, with the `config_path` being a filepath for storing client config. +The following function is called on startup by each binary, with the `config_path` being a filepath for storing client config: ```rust // create our client with specified path for key storage @@ -107,6 +107,25 @@ pub async fn create_client(config_path: PathBuf) -> MixnetClient { } ``` +If no config files exist at the location designated by `config_path` (in this case `/tmp/service`) then the following files are generated: + +```sh +service +├── ack_key.pem +├── db.sqlite +├── db.sqlite-shm +├── db.sqlite-wal +├── gateway_details.json +├── gateway_shared.pem +├── persistent_reply_store.sqlite +├── private_encryption.pem +├── private_identity.pem +├── public_encryption.pem +└── public_identity.pem + +1 directory, 11 files +``` + > If keys and config already exist at this location, re-running this function **will not** overwrite them. ### Listening for & Parsing Incoming messages @@ -114,7 +133,7 @@ Next to define two functions: one for listening _for_ messages from the mixnet ( Both functions attempt to deserialise the vec of `ReconstructedMessages` that are reconstructed by the client from delivered Sphinx packets after decryption. -`handle_request` performs one additional function - parsing the `sender_tag` from the incoming reconstructed message. This is the randomised alphanumeric string used to identify a bucket of _SURBs_ (Single Use Reply Blocks) that are sent along with any outgoing message by default. More information about them can be found [here](https://nymtech.net/docs/architecture/traffic-flow.html#private-replies-using-surbs) but all that is necessary to know for now is that these are pre-addressed packets that clients send out with their messages. Any reply to their message that is to be sent back to them back be written to the payload of these packets, but without the entity seeing the destination address. This allows for services to _anonymously reply_ to clients without being able to doxx them. +`handle_request` performs one additional function - parsing the `sender_tag` from the incoming reconstructed message. This is the randomised alphanumeric string used to identify a bucket of _SURBs_ (Single Use Reply Blocks) that are sent along with any outgoing message by default. More information about them can be found [here](https://nymtech.net/docs/architecture/traffic-flow.html#private-replies-using-surbs) but all that is necessary to know for now is that these are pre-addressed packets that clients send out with their messages. Any reply to their message that is to be sent back to them back be written to the payload of these packets, but without the replying party being able to see the destination that the reply is being sent to. This allows for services to **anonymously reply to clients without being able to doxx them by knowing their Nym address**. ```rust pub fn handle_response(message: ReconstructedMessage) -> anyhow::Result { @@ -129,7 +148,7 @@ pub fn handle_request( } ``` -Before moving on to the `client` and `service` code, one more function is needed. This allows for both binaries to parse empty incoming messages that they might receive. This is necessary as incoming SURBs as well as requests for more SURBs contain empty data fields. +Before moving on to the `client` and `service` code, one more function is needed. This allows for both binaries to parse empty incoming messages that they might receive. This is necessary as incoming SURBs, as well as requests for more SURBs, contain empty data fields. ```rust pub async fn wait_for_non_empty_message( diff --git a/documentation/dev-portal/src/tutorials/cosmos-service/overview.md b/documentation/dev-portal/src/tutorials/cosmos-service/overview.md index a422ea4047..829ef476ee 100644 --- a/documentation/dev-portal/src/tutorials/cosmos-service/overview.md +++ b/documentation/dev-portal/src/tutorials/cosmos-service/overview.md @@ -2,16 +2,15 @@ This tutorial involves writing two pieces of code in Rust: -- A client side binary used to (for now) construct a blockchain query and send this query to a service, which will interact with a Cosmos SDK blockchain on our behalf (bear in mind this principle works for all blockchains - we're just utilising the `cosmrs` library to interact with the Nyx blockchain in this tutorial). This query will be to query the balance of an account, in preparation for spending these tokens on a credential in a subsequent tutorial. +- A client side binary used to construct a blockchain query and send this query to a service, which will query the Cosmos SDK blockchain and then pass the response back to the client (bear in mind this principle works for all blockchains - we're just utilising the `cosmrs` library to interact with the Sandbox testnet blockchain in this tutorial). This query will be to query the balance of an account, in preparation for spending these tokens on a [bandwidth credential](https://nymtech.net/docs/bandwidth-credentials.html) in a subsequent tutorial. - A service which will listen out for requests from the mixnet, act on those requests, and anonymously reply to the client sending the requests. You will learn how to do the following with the Rust SDK: - Create clients with manual storage settings. -- Parse incoming traffic from the mixnet and reply anonymously using [SURBs](). +- Parse incoming traffic from the mixnet and reply anonymously using [SURBs](https://nymtech.net/docs/architecture/traffic-flow.html#private-replies-using-surbs). > Services usually run on remote servers to assure reliable uptime and to unlink sender and receiver metadata. For demonstration purposes however, you will run both components on your local machine, looping messages through the mixnet to yourself. - ``` TODO @@ -30,9 +29,9 @@ TUTORIAL ``` -You can find the code for these components [here](). You can use it as a reference while building or simply download it and follow along as you progress through the tutorial. +You can find the code for these components [here](https://github.com/nymtech/developer-tutorials). You can use it as a reference while building or simply download it and follow along as you progress through the tutorial. -Notice that this tutorial attempts to use very few external libraries. This tutorial is not showing you how to build production-grade code, but **to understand how to connect and send messages to, as well as recieve messages from, the mixnet.** +Notice that this tutorial attempts to use very few external libraries. This tutorial is not showing you how to build production-grade code, but **to understand how to connect and send messages to, as well as receive messages from, the mixnet.** ```admonish note title="Sidenote: What is a Service / Service Provider?" 'Service' or 'Service Provider' are catchall names used to refer to any type of app that can communicate with the mixnet via a Nym client - in this case, one embedded in its app process via the Rust SDK. diff --git a/documentation/dev-portal/src/tutorials/cosmos-service/preparing-env.md b/documentation/dev-portal/src/tutorials/cosmos-service/preparing-env.md index 474a3add08..e5016e2ef2 100644 --- a/documentation/dev-portal/src/tutorials/cosmos-service/preparing-env.md +++ b/documentation/dev-portal/src/tutorials/cosmos-service/preparing-env.md @@ -33,23 +33,18 @@ cosmrs = "=0.14.0" tokio = { version = "1.24.1", features = ["rt-multi-thread", "macros"] } serde = "1.0.152" serde_json = "1.0.91" +anyhow = "1.0.72" +``` + +These are non Nym-specific dependencies for the project. `clap` is for setting up the CLI commands, `cosmrs` for cosmos-specific types and functionality, `tokio` for the async/await environment, and `serde` for (de)serialisation. `anyhow` is for catch-all error handling. + +* Next add Nym-specific dependencies. Since these libraries are not yet on [crates io](https://crates.io) then you need to import them from the Nym monorepo: +``` nym-sdk = { git = "https://github.com/nymtech/nym", rev = "5dacf0c8f8775de6168d4da808fdce56e1ac2706" } nym-sphinx-addressing = { git = "https://github.com/nymtech/nym", rev = "5dacf0c8f8775de6168d4da808fdce56e1ac2706" } nym-validator-client = { git = "https://github.com/nymtech/nym", rev = "5dacf0c8f8775de6168d4da808fdce56e1ac2706" } nym-bin-common = { git = "https://github.com/nymtech/nym", rev = "5dacf0c8f8775de6168d4da808fdce56e1ac2706" } nym-sphinx-anonymous-replies = { git = "https://github.com/nymtech/nym", rev = "5dacf0c8f8775de6168d4da808fdce56e1ac2706" } -anyhow = "1.0.72" -``` - -These are non Nym-specific dependencies for the project. `anyhow` is for catch-all error handling, `clap` is for setting up the CLI commands, `cosmrs` for cosmos-specific types and functionality, `tokio` for the async/await environment, and `serde` for (de)serialisation. - -* Next add Nym-specific dependencies. Since these libraries are not yet on [crates io](https://crates.io) then you need to import them from the Nym monorepo: -``` -nym-sdk = { git = "https://github.com/nymtech/nym" } -nym-sphinx-addressing = { git = "https://github.com/nymtech/nym" } -nym-validator-client = { git = "https://github.com/nymtech/nym" } -nym-bin-common = { git = "https://github.com/nymtech/nym" } -nym-sphinx-anonymous-replies = { git = "https://github.com/nymtech/nym" } ``` The `sphinx` dependencies are for packet- and address-related functionality, the `validator-client` for Nyx blockchain specific configs, `common` for client logging, and the `sdk` for SDK functionality: creating and managing client storage and connections, and sending and receiving messages to and from the mixnet. diff --git a/documentation/dev-portal/src/tutorials/cosmos-service/service-src.md b/documentation/dev-portal/src/tutorials/cosmos-service/service-src.md index 6fb3324726..0e463362e8 100644 --- a/documentation/dev-portal/src/tutorials/cosmos-service/service-src.md +++ b/documentation/dev-portal/src/tutorials/cosmos-service/service-src.md @@ -3,7 +3,7 @@ Now to define the logic of creating the `broadcaster` for interacting with the blockchain, and querying it in `src/service.rs`. ## Dependencies -The following dependencies are for creating a client to interact with the blockchain, and deal with the returned `Coin` type expected from the balance query. +The following dependencies are for creating a client to interact with the blockchain, and deal with the returned `Coin` type in `BalanceResponse`. ```rust use crate::{BalanceResponse, DEFAULT_DENOM, DEFAULT_VALIDATOR_RPC}; diff --git a/documentation/dev-portal/src/tutorials/cosmos-service/service.md b/documentation/dev-portal/src/tutorials/cosmos-service/service.md index f8313bed8b..055bddfd00 100644 --- a/documentation/dev-portal/src/tutorials/cosmos-service/service.md +++ b/documentation/dev-portal/src/tutorials/cosmos-service/service.md @@ -18,16 +18,16 @@ The `AnonymousSenderTag` type is used for SURBs. ## main() Also using tokio for the async runtime, `main` does the following: -* If not already existing, creates a Nym client with config at `/tmp/client`. Otherwise it loads the already existing client from this config. +* Create a Nym client with config at `/tmp/service`, or load the existing client from this config directory. * Create a `broadcaster` - this is used by the service to interact with the blockchain, using the consts defined in `src/lib.rs` as chain config. * Listen out for incoming messages, and in much the same way as the `client`, handle and match the incoming request. -* Using the `sender_tag`, anonymously reply to the Nym client in `client` with the response from the blockchain. +* Using the `sender_tag`, anonymously reply to the `client` with the response from the blockchain **without having to know the client's Nym address**. ```rust #[tokio::main] async fn main() -> anyhow::Result<()> { setup_logging(); - let mut client = create_client("/tmp/service2".into()).await; + let mut client = create_client("/tmp/service".into()).await; let our_address = client.nym_address(); println!("\nservice's nym address: {our_address}"); // the httpclient we will use to broadcast our query to the blockchain From 27a9557c7b56b8c512b2591d4ef266bbbed2c0b6 Mon Sep 17 00:00:00 2001 From: mfahampshire Date: Tue, 8 Aug 2023 11:49:51 +0200 Subject: [PATCH 360/361] tweak --- .../dev-portal/src/tutorials/cosmos-service/querying.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/documentation/dev-portal/src/tutorials/cosmos-service/querying.md b/documentation/dev-portal/src/tutorials/cosmos-service/querying.md index 04a6f92832..c6fb3a431b 100644 --- a/documentation/dev-portal/src/tutorials/cosmos-service/querying.md +++ b/documentation/dev-portal/src/tutorials/cosmos-service/querying.md @@ -21,7 +21,7 @@ The following happens: * `client` sends a request to `service` using the supplied `n1...` address as the Nyx account to query the balance of, and the supplied Nym address to communicate with this instance of `service`. * `service` queries the Sandbox testnet blockchain using the `broadcaster` http client. It then serialises the response and returns it using SURBs to the `client`. -All in all, quite simple. By using the service as a proxy, the client never interacts with the blockchain, thus is not revealing metadata to the operator of the chain or anyone watching incoming traffic to it. Furthermore, the client doesn't even need to share its Nym address with the service, as the service is able to reply via SURBs. +All in all, quite simple. By using the service as a proxy, the client never interacts with the blockchain, thus is not revealing metadata to the operator of the Validator nor any entities monitoring its incoming and outgoing traffic. Furthermore, the client doesn't even need to share its Nym address with the service, as the service is able to reply via SURBs. ## Creating Sandbox Account If you wish to create an account on Sandbox to use instead of the supplied account above, the easiest way is by building the `nym-cli` tool and using that to create one: From 0e37084f34e9a167a5038f215f2b00d0fea72752 Mon Sep 17 00:00:00 2001 From: mfahampshire Date: Wed, 30 Aug 2023 10:59:18 +0200 Subject: [PATCH 361/361] updated client send method --- .../dev-portal/src/tutorials/cosmos-service/client-src.md | 4 ++-- .../dev-portal/src/tutorials/cosmos-service/service.md | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/documentation/dev-portal/src/tutorials/cosmos-service/client-src.md b/documentation/dev-portal/src/tutorials/cosmos-service/client-src.md index 63fb82b99b..e326d4506d 100644 --- a/documentation/dev-portal/src/tutorials/cosmos-service/client-src.md +++ b/documentation/dev-portal/src/tutorials/cosmos-service/client-src.md @@ -16,7 +16,7 @@ As well as importing message-handling functionality, request types, and the defa # Querying via the Mixnet The following is used to construct a `BalanceRequest`, send this to the supplied `service` address, and then handle the response, matching it to a `ResponseType` (in this case the only expected response, a `BalanceResponse`). -The actual sending of the request is performed by `client.send_bytes`: sending the serialised `BalanceRequest` to the supplied Nym address (the `Recipient` imported from the `nym_sphinx_addressing` crate). It is sending the default number of SURBs along with the message, defined [here](https://github.com/nymtech/nym/blob/develop/sdk/rust/nym-sdk/src/mixnet/client.rs#L34). +The actual sending of the request is performed by `client.send_message`: sending the serialised `BalanceRequest` to the supplied Nym address (the `Recipient` imported from the `nym_sphinx_addressing` crate). It is sending the default number of SURBs along with the message, defined [here](https://github.com/nymtech/nym/blob/develop/sdk/rust/nym-sdk/src/mixnet/client.rs#L34). ```rust pub async fn query_balance( @@ -32,7 +32,7 @@ pub async fn query_balance( // send serialised request to service via mixnet client - .send_bytes(sp_address, message.serialize(), Default::default()) + .send_message(sp_address, message.serialize(), Default::default()) .await; let received = wait_for_non_empty_message(client).await?; diff --git a/documentation/dev-portal/src/tutorials/cosmos-service/service.md b/documentation/dev-portal/src/tutorials/cosmos-service/service.md index 055bddfd00..7c0cc762a0 100644 --- a/documentation/dev-portal/src/tutorials/cosmos-service/service.md +++ b/documentation/dev-portal/src/tutorials/cosmos-service/service.md @@ -60,7 +60,7 @@ async fn main() -> anyhow::Result<()> { println!("\nsending response to {}", &return_recipient); // send response back to anon requesting client via mixnet client - .send_str_reply(return_recipient, &serde_json::to_string(&response)?) + .send_reply(return_recipient, &serde_json::to_string(&response)?) .await; } }

  • b${M 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%0AAgLhgxg0o)9&jB-AvXNfoO1NKUAUt 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ͅ_(pnTUcWV05C*)8SUOlEMe#8y4ZN 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 diff --git a/documentation/operators/src/images/wallet-screenshots/bonding.png b/documentation/operators/src/images/wallet-screenshots/bonding.png new file mode 100644 index 0000000000000000000000000000000000000000..b5cef3bb89bdde0e2d865001d14f9b4d1b1b28cc GIT binary patch literal 56520 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 literal 0 HcmV?d00001 diff --git a/documentation/operators/src/images/wallet-screenshots/node_settings.png b/documentation/operators/src/images/wallet-screenshots/node_settings.png new file mode 100644 index 0000000000000000000000000000000000000000..69568ce6a540cbc1d6c4e0fea6d286451ebc189b GIT binary patch literal 55864 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%0Arw=^F)sf3iGu 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 z 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

    i7oJlPE&Nq*U_W;o}Y=vp5lP^QjkUnEpPy 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